Guias de Desenvolvimento

Política e Conformidade

Signing Sessions

Sessão de Confiança

Modelos de Solução

Guias dos SDKs

Webhooks

Webhooks permitem receber notificações em tempo real sobre eventos da API. A SignDocsBrasil envia requisições HTTP POST para a URL registrada quando eventos ocorrem.


Passo 1: Registrar webhook

const webhook = await client.webhooks.register({
  url: 'https://example.com/webhooks/signdocs',
  events: ['TRANSACTION.COMPLETED', 'TRANSACTION.FAILED', 'SIGNING_SESSION.COMPLETED'],
});
console.log(webhook.webhookId);
console.log(webhook.secret); // Salve este valor — só é retornado uma vez!
from signdocs_brasil.models import RegisterWebhookRequest

webhook = client.webhooks.register(RegisterWebhookRequest(
    url='https://example.com/webhooks/signdocs',
    events=['TRANSACTION.COMPLETED', 'TRANSACTION.FAILED', 'SIGNING_SESSION.COMPLETED'],
))
print(webhook.webhook_id)
print(webhook.secret)  # Salve — só é retornado uma vez!
webhook, _ := client.Webhooks.Register(ctx, &signdocs.RegisterWebhookRequest{
    URL:    "https://example.com/webhooks/signdocs",
    Events: []signdocs.WebhookEventType{
        signdocs.WebhookEventTransactionCompleted,
        signdocs.WebhookEventTransactionFailed,
        signdocs.WebhookEventStepCompleted,
    },
})
fmt.Println(webhook.WebhookID)
fmt.Println(webhook.Secret) // Salve — só é retornado uma vez!
RegisterWebhookResponse webhook = client.webhooks().register(
    new RegisterWebhookRequest(
        "https://example.com/webhooks/signdocs",
        List.of("TRANSACTION.COMPLETED", "TRANSACTION.FAILED", "SIGNING_SESSION.COMPLETED")
    ));
System.out.println(webhook.webhookId);
System.out.println(webhook.secret); // Salve — só é retornado uma vez!
$webhook = $client->webhooks->register(new RegisterWebhookRequest(
    url: 'https://example.com/webhooks/signdocs',
    events: ['TRANSACTION.COMPLETED', 'TRANSACTION.FAILED', 'SIGNING_SESSION.COMPLETED'],
));
echo $webhook->webhookId;
echo $webhook->secret; // Salve — só é retornado uma vez!
using SignDocsBrasil.Api;
using SignDocsBrasil.Api.Models;

var webhook = await client.Webhooks.RegisterAsync(new RegisterWebhookRequest
{
    Url = "https://sua-aplicacao.com.br/webhooks/signdocs",
    Events = new[] { "TRANSACTION.COMPLETED", "TRANSACTION.FAILED", "SIGNING_SESSION.COMPLETED" },
});
Console.WriteLine(webhook.WebhookId);
Console.WriteLine(webhook.Secret); // Armazene de forma segura!

Eventos Disponíveis

Os 20 eventos do contrato. Todo evento desta lista tem um produtor no código — se algo aparecer aqui e nunca chegar, é defeito, não expectativa.

Evento Descrição
TRANSACTION.CREATED Transação criada.
TRANSACTION.COMPLETED Transação finalizada com sucesso; evidência disponível.
TRANSACTION.CANCELLED Transação cancelada pelo integrador ou usuário.
TRANSACTION.FAILED Falha terminal: todas as etapas passaram e a finalização não produziu documento assinado. Só no fluxo hospedado.
TRANSACTION.EXPIRED Transação expirou sem finalização.
TRANSACTION.FALLBACK Fallback acionado (ex. biometria indisponível → foto de documento).
TRANSACTION.DEADLINE_APPROACHING (NT65) Faltam ≤2 dias úteis para o prazo de submissão ao INSS.
STEP.PURPOSE_DISCLOSURE_SENT (NT65) Notificação de divulgação de finalidade enviada ao beneficiário.
ENROLLMENT.EXPIRING A foto de referência será destruída em 30 ou 7 dias (data.daysUntilExpiry).
ENROLLMENT.EXPIRED A foto de referência foi destruída hoje; o usuário precisa de novo cadastro.
QUOTA.WARNING Uso mensal cruzou 80% do limite do plano. Uma vez por dimensão por mês.
API.DEPRECATION_NOTICE Chamada a endpoint deprecated. Uma vez por endpoint por dia.
SIGNING_SESSION.CREATED Sessão de assinatura criada.
SIGNING_SESSION.COMPLETED Sessão concluída — o desfecho por signatário.
SIGNING_SESSION.CANCELLED Sessão cancelada.
SIGNING_SESSION.EXPIRED Sessão expirou.
ENVELOPE.CREATED Envelope multi-assinante criado.
ENVELOPE.ALL_SIGNED Todas as assinaturas do envelope foram concluídas.
ENVELOPE.CANCELLED Envelope cancelado; as assinaturas pendentes são encerradas.
ENVELOPE.EXPIRED Envelope expirou com uma ou mais assinaturas pendentes.

Passo 2: Verificar assinatura HMAC-SHA256

Cada webhook enviado inclui dois headers:

Header Descrição
X-SignDocs-Signature Assinatura HMAC-SHA256 em hexadecimal
X-SignDocs-Timestamp Timestamp Unix (segundos) do envio

Formato de assinatura: HMAC-SHA256("{timestamp}.{body}", secret)

Tolerância padrão: 300 segundos (5 minutos).

import express from 'express';
import { verifyWebhookSignature } from '@signdocs-brasil/api';

app.post('/webhooks/signdocs', express.text({ type: '*/*' }), (req, res) => {
  const valid = verifyWebhookSignature(
    req.body,
    req.headers['x-signdocs-signature'] as string,
    req.headers['x-signdocs-timestamp'] as string,
    webhookSecret,
  );

  if (!valid) {
    return res.status(401).send('Assinatura inválida');
  }

  const payload = JSON.parse(req.body);
  console.log(payload.eventType, payload.transactionId);
  res.status(200).send('OK');
});
from flask import Flask, request
from signdocs_brasil import verify_webhook_signature

@app.route('/webhooks/signdocs', methods=['POST'])
def handle_webhook():
    body = request.get_data(as_text=True)
    signature = request.headers.get('X-SignDocs-Signature')
    timestamp = request.headers.get('X-SignDocs-Timestamp')

    if not verify_webhook_signature(body, signature, timestamp, webhook_secret):
        return 'Assinatura inválida', 401

    payload = request.get_json()
    print(payload['eventType'], payload.get('transactionId'))
    return 'OK', 200
import (
    "io"
    "net/http"
    signdocs "github.com/signdocsbrasil/signdocsbrasil-go/v2"
)

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    valid := signdocs.VerifyWebhookSignature(
        string(body),
        r.Header.Get("X-SignDocs-Signature"),
        r.Header.Get("X-SignDocs-Timestamp"),
        webhookSecret,
    )

    if !valid {
        http.Error(w, "Assinatura inválida", http.StatusUnauthorized)
        return
    }

    // Processar evento...
    w.WriteHeader(http.StatusOK)
}
import com.signdocsbrasil.api.WebhookVerifier;
import java.util.stream.Collectors;

// Em um servlet ou controller Spring
String body = request.getReader().lines().collect(Collectors.joining());
String signature = request.getHeader("X-SignDocs-Signature");
String timestamp = request.getHeader("X-SignDocs-Timestamp");

boolean valid = WebhookVerifier.verifySignature(body, signature, timestamp, webhookSecret);

if (!valid) {
    response.setStatus(401);
    return;
}

// Processar evento...
use SignDocsBrasil\Api\WebhookVerifier;

$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNDOCS_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_SIGNDOCS_TIMESTAMP'] ?? '';

$valid = WebhookVerifier::verify($body, $signature, $timestamp, $webhookSecret);

if (!$valid) {
    http_response_code(401);
    echo 'Assinatura inválida';
    exit;
}

$payload = json_decode($body, true);
// Processar evento...
using System.Security.Cryptography;
using System.Text;

bool VerifyWebhook(string signature, string timestamp, string body, string secret)
{
    // 1. Verificar frescura do timestamp
    var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    var ts = long.Parse(timestamp);
    if (Math.Abs(now - ts) > 300)
        throw new Exception("Timestamp fora da tolerância. Possível replay attack.");

    // 2. Recalcular a assinatura
    var signingInput = $"{timestamp}.{body}";
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var expectedBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingInput));

    // 3. Comparação timing-safe
    var signatureBytes = Convert.FromHexString(signature);
    if (!CryptographicOperations.FixedTimeEquals(expectedBytes, signatureBytes))
        throw new Exception("Assinatura inválida.");

    return true;
}

Tolerância personalizada

verifyWebhookSignature(body, sig, ts, secret, { toleranceSeconds: 600 });
verify_webhook_signature(body, sig, ts, secret, tolerance_seconds=600)
signdocs.VerifyWebhookSignature(body, sig, ts, secret,
    signdocs.WithToleranceSeconds(600))
WebhookVerifier.verifySignature(body, sig, ts, secret, 600);
WebhookVerifier::verify($body, $sig, $ts, $secret, toleranceSeconds: 600);
VerifyWebhook(sig, ts, body, secret); // Altere a constante 300 para 600 na implementação

Passo 3: Testar webhook

Envia uma entrega de teste para o endpoint registrado.

const test = await client.webhooks.test(webhook.webhookId);
console.log(test.deliveryId);
console.log(test.status);     // "delivered" ou "failed"
console.log(test.statusCode); // HTTP status retornado pelo seu endpoint
test = client.webhooks.test(webhook.webhook_id)
print(test.delivery_id, test.status)
test, _ := client.Webhooks.Test(ctx, webhook.WebhookID)
fmt.Println(test.DeliveryID, test.Status)
WebhookTestResponse test = client.webhooks().test(webhook.webhookId);
System.out.println(test.deliveryId + " " + test.status);
$test = $client->webhooks->test($webhook->webhookId);
echo $test->deliveryId . ' ' . $test->status;
var testResult = await client.Webhooks.TestAsync(webhook.WebhookId);
Console.WriteLine($"Success: {testResult.TestDelivery.Success}");

Gerenciar webhooks

Listar

const webhooks = await client.webhooks.list();
for (const wh of webhooks) {
  console.log(wh.webhookId, wh.url, wh.events, wh.status);
}
webhooks = client.webhooks.list()
for wh in webhooks:
    print(wh.webhook_id, wh.url, wh.events, wh.status)
webhooks, _ := client.Webhooks.List(ctx)
for _, wh := range webhooks {
    fmt.Println(wh.WebhookID, wh.URL, wh.Events, wh.Status)
}
List<Webhook> webhooks = client.webhooks().list();
for (Webhook wh : webhooks) {
    System.out.println(wh.webhookId + " " + wh.url);
}
$webhooks = $client->webhooks->list();
foreach ($webhooks as $wh) {
    echo $wh->webhookId . ' ' . $wh->url . PHP_EOL;
}
var webhooks = await client.Webhooks.ListAsync();
foreach (var wh in webhooks)
    Console.WriteLine($"{wh.WebhookId} — {wh.Status}");

Deletar

await client.webhooks.delete(webhookId);
client.webhooks.delete(webhook_id)
client.Webhooks.Delete(ctx, webhookID)
client.webhooks().delete(webhookId);
$client->webhooks->delete($webhookId);
await client.Webhooks.DeleteAsync(webhook.WebhookId);