Guias de Desenvolvimento

Política e Conformidade

Signing Sessions

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', 'STEP.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', 'STEP.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", "STEP.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', 'STEP.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", "STEP.COMPLETED" },
});
Console.WriteLine(webhook.WebhookId);
Console.WriteLine(webhook.Secret); // Armazene de forma segura!

Eventos Disponíveis

Evento Descrição
TRANSACTION.CREATED Transação criada
TRANSACTION.COMPLETED Transação finalizada com sucesso
TRANSACTION.CANCELLED Transação cancelada
TRANSACTION.FAILED Transação falhou
TRANSACTION.EXPIRED Transação expirou
TRANSACTION.FALLBACK Fallback acionado
STEP.STARTED Etapa iniciada
STEP.COMPLETED Etapa concluída
STEP.FAILED Etapa falhou
QUOTA.WARNING Limite de cota próximo
API.DEPRECATION_NOTICE Aviso de descontinuação

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"
)

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);