Webhooks para APIs de Assinatura Digital: Arquitetura de Eventos em Tempo Real

Em fluxos de assinatura digital, saber exatamente quando um documento foi visualizado, assinado ou recusado pode ser a diferença entre um processo eficiente e um gargalo operacional. Webhooks permitem que sua aplicação reaja em tempo real a cada evento do ciclo de vida de uma transação de assinatura, eliminando a necessidade de verificações periódicas e habilitando automações sofisticadas.

Se você já conhece a API de assinatura digital da SignDocs e está familiarizado com o fluxo transacional completo, este artigo vai aprofundar o mecanismo de eventos que mantém toda a arquitetura sincronizada: os webhooks.

Neste guia técnico, vamos cobrir desde os fundamentos da arquitetura event-driven até a implementação prática de receivers seguros, passando por verificação HMAC, idempotência, estratégias de retry e integrações com sistemas corporativos.

Por que eventos em tempo real são essenciais para fluxos de assinatura

Uma transação de assinatura digital não é um processo instantâneo. Desde a criação do envelope até a conclusão com todas as assinaturas coletadas, podem se passar minutos, horas ou até dias. Durante esse período, diversos eventos ocorrem:

  • O documento é enviado para os signatários
  • Cada signatário abre o link e visualiza o documento
  • Signatários aplicam suas assinaturas em momentos diferentes
  • Um signatário pode recusar a assinatura
  • O documento pode expirar antes da conclusão
  • O remetente pode cancelar a transação

Sem um mecanismo de notificação em tempo real, sua aplicação ficaria "no escuro" sobre o andamento dessas etapas. Isso impacta diretamente a experiência do usuário final, a eficiência operacional e a capacidade de automatizar processos downstream como atualizações de CRM, disparo de faturamento ou notificações internas.

Webhooks resolvem esse problema ao inverter o fluxo de comunicação: em vez de sua aplicação perguntar repetidamente "mudou alguma coisa?", a API de assinatura avisa proativamente sempre que algo relevante acontece. Esse padrão, conhecido como event-driven architecture, é a base de sistemas modernos e escaláveis.

Polling vs. Webhooks: comparativo técnico

Antes de aprofundarmos na implementação de webhooks, é importante entender as duas abordagens principais para acompanhar mudanças de estado em APIs e por que webhooks são a escolha superior para a maioria dos cenários.

Polling (consulta periódica)

No modelo de polling, sua aplicação faz requisições HTTP periódicas ao endpoint de status da API. Por exemplo, a cada 30 segundos você consulta GET /transactions/{id}/status para verificar se houve mudança.

Webhooks (push notification)

No modelo de webhooks, você registra uma URL (endpoint) no sistema da API. Sempre que um evento relevante ocorre, a API envia um HTTP POST com os detalhes do evento para essa URL.

Critério Polling Webhooks
Latência Variável (depende do intervalo). Pode ser de segundos a minutos Quase zero. Notificação imediata após o evento
Consumo de recursos Alto. Requisições constantes mesmo sem mudanças Baixo. Tráfego apenas quando há eventos reais
Escalabilidade Ruim. Cresce linearmente com o número de transações monitoradas Excelente. Volume proporcional apenas aos eventos reais
Rate limiting Risco alto de atingir limites da API Sem risco (a API controla o envio)
Complexidade de implementação Simples (loop + GET request) Moderada (endpoint HTTPS + verificação de segurança)
Confiabilidade Alta (você controla as requisições) Alta (com retry e idempotência adequados)
Custo em produção Elevado (chamadas desnecessárias = custo computacional) Otimizado (processamento sob demanda)
Recomendação: Para aplicações em produção, webhooks devem ser o mecanismo primário de acompanhamento de eventos. Mantenha o polling apenas como fallback de reconciliação, executando uma verificação periódica (por exemplo, a cada hora) para detectar eventos que possam ter sido perdidos por falhas temporárias.

Tipos de eventos no ciclo de vida de uma assinatura

Uma API de assinatura digital robusta emite eventos para cada transição relevante de estado. Abaixo, detalhamos os principais tipos de eventos que você pode esperar ao integrar com plataformas como o SignDocs.

Evento Descrição Caso de Uso Típico
transaction.created A transação de assinatura foi criada com sucesso na API Registrar ID da transação no sistema interno; iniciar timer de SLA
transaction.sent Os convites de assinatura foram enviados aos signatários Atualizar status no CRM; notificar responsável interno
signer.viewed Um signatário abriu o link e visualizou o documento Registrar evidência de ciência; alimentar dashboard de acompanhamento
signer.signed Um signatário assinou o documento com sucesso Disparar próxima etapa do fluxo; atualizar progresso da transação
signer.declined Um signatário recusou a assinatura Alertar equipe comercial; iniciar fluxo de renegociação
transaction.completed Todos os signatários assinaram; transação concluída Disparar faturamento; arquivar documento; integrar com ERP
transaction.expired A transação expirou sem que todos assinassem Enviar lembrete; criar nova transação; notificar gestão
transaction.cancelled A transação foi cancelada pelo remetente ou via API Reverter reservas; atualizar pipeline no CRM
document.ready O documento final assinado está disponível para download Fazer download automático; armazenar em repositório; enviar cópia às partes

Veja como um payload típico de webhook se parece. Neste exemplo, o evento signer.signed notifica que um signatário concluiu sua assinatura:

// Payload do webhook: signer.signed { "event": "signer.signed", "event_id": "evt_a1b2c3d4e5f6", "timestamp": "2026-02-18T14:32:07Z", "data": { "transaction_id": "txn_9f8e7d6c5b4a", "transaction_status": "in_progress", "signer": { "id": "sgn_x1y2z3w4", "name": "Maria Silva", "email": "maria@empresa.com.br", "role": "signer", "signed_at": "2026-02-18T14:32:05Z", "ip_address": "203.0.113.42" }, "document": { "id": "doc_m1n2o3p4", "name": "Contrato_Prestacao_Servicos.pdf" }, "signers_completed": 2, "signers_total": 3 } }

Observe que o payload inclui um event_id único, essencial para a lógica de idempotência que veremos adiante, e um timestamp ISO 8601 para ordenação temporal dos eventos.

Arquitetura do receptor de webhooks

Para receber webhooks de forma segura e confiável, seu endpoint precisa atender a alguns requisitos fundamentais. Conforme descrito na documentação de autenticação OAuth2, a segurança deve permear todas as camadas da integração.

Requisitos do endpoint

  1. HTTPS obrigatório: O endpoint deve usar TLS 1.2 ou superior. APIs de assinatura digital não enviam webhooks para URLs HTTP sem criptografia, pois o payload pode conter dados sensíveis dos signatários.
  2. Acessibilidade pública: O endpoint deve ser acessível pela internet. Se sua aplicação estiver em uma rede privada, utilize serviços como ngrok (para desenvolvimento) ou um API Gateway (para produção).
  3. Resposta rápida: Retorne HTTP 200 OK em no máximo 5 a 30 segundos. Processamentos longos devem ser feitos assincronamente.
  4. Método POST: Webhooks são sempre enviados como HTTP POST com body em JSON.

Códigos de resposta e seus significados

A forma como seu endpoint responde ao webhook determina o comportamento da API:

  • 2xx (200, 201, 202, 204): Evento recebido com sucesso. A API não reenviará.
  • 3xx (301, 302): Redirecionamento. A maioria das APIs não segue redirects em webhooks.
  • 4xx (400, 401, 403, 404): Erro do cliente. A API pode tentar novamente dependendo do código específico.
  • 5xx (500, 502, 503): Erro do servidor. A API agendará uma retentativa.
  • Timeout: Se o endpoint não responder dentro do tempo limite, é tratado como falha e uma retentativa será agendada.
Padrão recomendado: Receba o webhook, persista o payload em uma fila interna (Redis, RabbitMQ, SQS) e retorne 200 OK imediatamente. Um worker separado processa os eventos da fila de forma assíncrona, sem risco de timeout.

Exemplo de endpoint receptor em Node.js (Express)

const express = require('express'); const app = express(); app.use(express.json({ // Preservar raw body para verificação HMAC verify: (req, res, buf) => { req.rawBody = buf.toString(); } })); app.post('/webhooks/signdocs', (req, res) => { const signature = req.headers['x-signdocs-signature']; const timestamp = req.headers['x-signdocs-timestamp']; // 1. Verificar assinatura HMAC (detalhado na próxima seção) if (!verifySignature(req.rawBody, signature, timestamp)) { return res.status(401).json({ error: 'Invalid signature' }); } // 2. Verificar idempotência const eventId = req.body.event_id; if (isEventAlreadyProcessed(eventId)) { return res.status(200).json({ status: 'already_processed' }); } // 3. Enfileirar para processamento assíncrono enqueueEvent(req.body); // 4. Retornar 200 imediatamente res.status(200).json({ status: 'received' }); }); app.listen(3000, () => { console.log('Webhook receiver listening on port 3000'); });

Verificação de segurança com HMAC-SHA256

A verificação HMAC (Hash-based Message Authentication Code) é o mecanismo padrão para garantir a autenticidade e integridade dos webhooks. Esse aspecto é tão relevante quanto a segurança mTLS em integrações enterprise, pois protege contra payloads forjados.

Como funciona

  1. Ao configurar o webhook, a API gera uma chave secreta (webhook secret) que é compartilhada apenas entre a API e seu servidor.
  2. Quando a API envia um webhook, ela calcula o HMAC-SHA256 do payload usando essa chave secreta e inclui o resultado no header X-Signature.
  3. Ao receber o webhook, seu servidor recalcula o HMAC usando a mesma chave e compara com o valor recebido.
  4. Se os valores coincidem, o webhook é autêntico. Caso contrário, deve ser rejeitado.

Implementação em Python

import hmac import hashlib import time from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = 'whsec_SuaChaveSecretaAqui123456' TIMESTAMP_TOLERANCE = 300 # 5 minutos em segundos def verify_webhook_signature(payload, signature, timestamp): """Verifica a assinatura HMAC-SHA256 do webhook.""" # Validar timestamp para prevenir replay attacks current_time = int(time.time()) if abs(current_time - int(timestamp)) > TIMESTAMP_TOLERANCE: return False # Montar a string de assinatura: timestamp + "." + payload signed_payload = f"{timestamp}.{payload}" # Calcular HMAC-SHA256 expected_signature = hmac.new( WEBHOOK_SECRET.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest() # Comparação segura (timing-safe) contra timing attacks return hmac.compare_digest(expected_signature, signature) @app.route('/webhooks/signdocs', methods=['POST']) def handle_webhook(): payload = 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(payload, signature, timestamp): return jsonify({'error': 'Assinatura inválida'}), 401 event = request.json # Processar o evento... return jsonify({'status': 'received'}), 200

Implementação em Node.js

const crypto = require('crypto'); const WEBHOOK_SECRET = 'whsec_SuaChaveSecretaAqui123456'; const TIMESTAMP_TOLERANCE = 300; // 5 minutos em segundos function verifySignature(payload, signature, timestamp) { // Validar timestamp contra replay attacks const currentTime = Math.floor(Date.now() / 1000); if (Math.abs(currentTime - parseInt(timestamp)) > TIMESTAMP_TOLERANCE) { return false; } // Montar string de assinatura const signedPayload = `${timestamp}.${payload}`; // Calcular HMAC-SHA256 const expectedSignature = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(signedPayload) .digest('hex'); // Comparação timing-safe return crypto.timingSafeEqual( Buffer.from(expectedSignature), Buffer.from(signature) ); }
Segurança crítica: Sempre use comparação timing-safe (hmac.compare_digest em Python ou crypto.timingSafeEqual em Node.js) ao validar assinaturas. Comparações simples com == são vulneráveis a timing attacks, onde um atacante pode inferir caracteres corretos da assinatura medindo tempos de resposta.

Idempotência e deduplicação de eventos

Em sistemas distribuídos, a entrega "exatamente uma vez" (exactly-once delivery) é praticamente impossível de garantir. APIs de webhook operam com semântica de pelo menos uma vez (at-least-once delivery), o que significa que o mesmo evento pode ser entregue mais de uma vez em situações como:

  • Seu endpoint respondeu com timeout, mas já havia processado o evento
  • Uma falha de rede interrompeu a conexão após o processamento
  • A API executou um retry antes de confirmar a entrega anterior

Por isso, seu receptor deve ser idempotente: processar o mesmo evento múltiplas vezes deve produzir o mesmo resultado que processá-lo uma única vez.

Implementação de deduplicação com Redis

import redis import json redis_client = redis.Redis(host='localhost', port=6379, db=0) EVENT_TTL = 86400 * 7 # 7 dias em segundos def process_webhook(event): """Processa webhook com deduplicação baseada em event_id.""" event_id = event['event_id'] dedup_key = f"webhook:processed:{event_id}" # Tentar marcar como "em processamento" atomicamente # SET NX retorna True apenas se a chave não existia is_new = redis_client.set(dedup_key, 'processing', nx=True, ex=EVENT_TTL) if not is_new: # Evento já foi processado ou está sendo processado status = redis_client.get(dedup_key).decode() print(f"Evento {event_id} já tratado (status: {status}). Ignorando.") return {'status': 'duplicate', 'event_id': event_id} try: # Processar conforme o tipo de evento event_type = event['event'] if event_type == 'signer.signed': handle_signer_signed(event['data']) elif event_type == 'transaction.completed': handle_transaction_completed(event['data']) elif event_type == 'signer.declined': handle_signer_declined(event['data']) elif event_type == 'document.ready': handle_document_ready(event['data']) # ... outros tipos de evento # Marcar como concluído com sucesso redis_client.set(dedup_key, 'completed', ex=EVENT_TTL) return {'status': 'processed', 'event_id': event_id} except Exception as e: # Em caso de erro, remover a chave para permitir reprocessamento redis_client.delete(dedup_key) raise e

A estratégia acima utiliza o comando SET NX (set if not exists) do Redis para garantir atomicidade na verificação e marcação de eventos. O TTL de 7 dias assegura que a memória do Redis não cresça indefinidamente enquanto mantém proteção contra duplicatas tardias.

SignDocs: webhooks prontos para produção. A API SignDocs oferece webhooks com entrega garantida, verificação HMAC-SHA256 integrada, painel de monitoramento de entregas e suporte a múltiplos endpoints por conta. Configure seus webhooks em minutos pelo dashboard ou via API, com filtros granulares por tipo de evento. Experimente grátis ou fale com nossa equipe para saber mais sobre a API Enterprise.

Estratégia de retry e tratamento de falhas

Falhas na entrega de webhooks são inevitáveis em sistemas distribuídos. Redes falham, servidores reiniciam, deploys temporariamente derrubam endpoints. Uma estratégia de retry robusta garante que nenhum evento seja perdido permanentemente.

Backoff exponencial

O padrão recomendado é o backoff exponencial: a cada falha consecutiva, o intervalo entre retentativas aumenta exponencialmente, evitando sobrecarregar um servidor que pode estar em recuperação.

Tentativa Intervalo Após Falha Tempo Acumulado Observação
1 (original) Imediato 0s Entrega inicial do evento
2 1 minuto 1 min Primeira retentativa. Cobre erros transitórios rápidos
3 5 minutos 6 min Cobre reinícios de container ou deploy rápido
4 30 minutos 36 min Cobre falhas de rede mais longas
5 2 horas ~2h 36min Cobre manutenções planejadas curtas
6 8 horas ~10h 36min Cobre indisponibilidades prolongadas
7 (última) 24 horas ~34h 36min Última tentativa. Após falha, evento vai para dead-letter queue

O que acontece após todas as tentativas falharem

Quando todas as retentativas são exauridas, boas práticas incluem:

  • Dead-letter queue (DLQ): O evento é movido para uma fila especial de eventos não entregues, acessível via dashboard ou API.
  • Notificação ao administrador: Um alerta é enviado por e-mail ou outro canal informando sobre a falha persistente.
  • Endpoint desativado: Após múltiplas falhas consecutivas (por exemplo, 100 entregas falhadas), o endpoint pode ser automaticamente desativado para proteger ambos os lados.
  • Reconciliação via polling: Seu sistema pode executar uma verificação periódica para identificar transações cujos eventos de webhook não foram entregues.
Dica de arquitetura: Implemente um job de reconciliação que rode a cada hora, consultando GET /transactions?updated_after={last_check} e comparando com os eventos processados. Isso funciona como uma "rede de segurança" que captura qualquer evento perdido por falhas no webhook.

Exemplos práticos de integração com sistemas corporativos

O verdadeiro valor dos webhooks se revela quando conectados a outros sistemas da empresa. Ao conectar os eventos a um CRM, um ERP ou ferramentas internas, a automação dessas conexões elimina trabalho manual e reduz erros.

1. Atualização automática no CRM (Salesforce/HubSpot)

Quando um contrato é assinado, o registro do deal/oportunidade no CRM deve ser atualizado automaticamente:

async function handleTransactionCompleted(eventData) { const { transaction_id, metadata } = eventData; // metadata.crm_deal_id foi incluído na criação da transação const dealId = metadata.crm_deal_id; // Atualizar deal no CRM await crmClient.deals.update(dealId, { stage: 'Contrato Assinado', properties: { contract_signed_date: new Date().toISOString(), contract_transaction_id: transaction_id, contract_status: 'completed' } }); // Criar tarefa de follow-up para o closer await crmClient.tasks.create({ associatedObjectId: dealId, subject: 'Contrato assinado - iniciar onboarding', dueDate: addBusinessDays(new Date(), 2), priority: 'HIGH' }); }

2. Disparo de processo no ERP (SAP/TOTVS)

A conclusão de uma assinatura pode disparar automaticamente a criação de uma ordem de serviço ou nota fiscal:

async function handleDocumentReady(eventData) { const { transaction_id, document } = eventData; // Baixar documento assinado const signedPdf = await signdocsApi.documents.download(document.id); // Enviar para o repositório documental do ERP const erpDocRef = await erpClient.documents.upload({ file: signedPdf, category: 'CONTRATOS_ASSINADOS', metadata: { transaction_id } }); // Criar ordem de serviço vinculada await erpClient.serviceOrders.create({ type: 'PRESTACAO_SERVICO', contractRef: erpDocRef.id, startDate: addBusinessDays(new Date(), 5), status: 'AGUARDANDO_INICIO' }); }

3. Notificação em tempo real no Slack/Teams

Manter a equipe informada sobre o andamento das assinaturas com notificações automáticas:

async function sendSlackNotification(event) { const messages = { 'signer.signed': (d) => `Assinatura recebida: ${d.signer.name} assinou "${d.document.name}" (${d.signers_completed}/${d.signers_total})`, 'transaction.completed': (d) => `Contrato concluído: "${d.document.name}" - todas as assinaturas coletadas!`, 'signer.declined': (d) => `ATENÇÃO: ${d.signer.name} recusou assinar "${d.document.name}". Motivo: ${d.decline_reason || 'não informado'}`, 'transaction.expired': (d) => `Transação expirada: "${d.document.name}" não foi concluída no prazo.` }; const formatMessage = messages[event.event]; if (!formatMessage) return; await fetch(SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel: '#contratos', text: formatMessage(event.data), username: 'SignDocs Bot' }) }); }

Esses exemplos ilustram como uma arquitetura event-driven baseada em webhooks permite que diferentes sistemas reajam de forma coordenada a eventos de assinatura, sem acoplamento direto entre eles. Para entender como a assinatura digital funciona por trás dessas automações, consulte nosso guia introdutório.

Perguntas frequentes sobre webhooks para APIs de assinatura

O que acontece se meu endpoint de webhook ficar fora do ar?

APIs bem projetadas implementam uma estratégia de retry com backoff exponencial. Tipicamente, a API tentará reenviar o evento após 1 minuto, depois 5 minutos, 30 minutos, 2 horas e 24 horas. Se todas as tentativas falharem, o evento é movido para uma dead-letter queue e uma notificação é enviada ao administrador. É fundamental que sua aplicação implemente idempotência para lidar corretamente com eventuais entregas duplicadas após retentativas.

Como garantir que um webhook veio realmente da API e não de um atacante?

A verificação de assinatura HMAC-SHA256 é o padrão da indústria. Cada webhook inclui um header com a assinatura calculada usando uma chave secreta compartilhada entre a API e seu servidor. Ao receber o webhook, você recalcula o HMAC do payload usando a mesma chave e compara com a assinatura recebida. Adicionalmente, valide o timestamp para prevenir ataques de replay e restrinja os IPs de origem quando possível.

Qual a diferença entre polling e webhooks para acompanhar o status de uma assinatura?

No polling, sua aplicação faz requisições periódicas à API para verificar mudanças de status, o que consome recursos desnecessários e tem latência variável. Com webhooks, a API envia notificações automaticamente ao seu endpoint sempre que um evento ocorre, proporcionando latência quase zero, menor consumo de recursos e arquitetura mais escalável. Webhooks são a abordagem recomendada para aplicações em produção.

Preciso responder com algum dado específico ao receber um webhook?

Não. Basta retornar um código HTTP 2xx (preferencialmente 200 OK) dentro do timeout configurado, geralmente de 5 a 30 segundos. Qualquer processamento pesado deve ser feito de forma assíncrona, colocando o evento em uma fila interna. Se seu endpoint retornar 4xx ou 5xx, a API interpretará como falha e agendará uma retentativa.

Posso receber webhooks em múltiplos endpoints ao mesmo tempo?

Sim. A maioria das APIs de assinatura digital permite configurar múltiplos endpoints de webhook, cada um recebendo os mesmos eventos ou filtrado por tipo de evento. Isso é útil para separar responsabilidades: um endpoint para atualizar o CRM, outro para o ERP, outro para notificações internas. Cada endpoint deve implementar sua própria lógica de idempotência e verificação HMAC.

Webhooks funcionam com arquiteturas serverless (AWS Lambda, Azure Functions)?

Sim, e essa é uma combinação muito eficiente. Funções serverless são ideais para processar webhooks porque escalam automaticamente conforme o volume de eventos, você paga apenas pelo tempo de execução, e não precisa manter servidores dedicados. Basta expor a função como um endpoint HTTPS e configurar a URL na API de assinatura. Considere o cold start da função ao definir o timeout de resposta.

Automatize seus fluxos de assinatura com webhooks em tempo real

A API SignDocs oferece webhooks com entrega garantida, verificação HMAC-SHA256, retry automático com backoff exponencial e painel de monitoramento completo. Integre assinatura digital ao seu sistema em minutos.

Comece grátis Fale com nossa equipe sobre a API Enterprise