Webhooks
Eventos
envelope.created, envelope.sent, envelope.viewed, signer.authenticated, signer.signed, signer.declined, envelope.completed, envelope.cancelled, envelope.expired.
Assinatura
Cada requisição traz X-Signature: v1=<hex>, X-Event-Id e X-Timestamp. A assinatura é HMAC-SHA256 do texto {X-Event-Id}.{X-Timestamp}.{corpo bruto} com seu whsec_….
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(raw: string, h: Headers, secret: string) {
const ts = Number(h.get('x-timestamp'));
if (Math.abs(Date.now() / 1000 - ts) > 300) return false; // replay
const expected = 'v1=' + createHmac('sha256', secret).update(`${h.get('x-event-id')}.${ts}.${raw}`).digest('hex');
const got = h.get('x-signature') ?? '';
return got.length === expected.length && timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}Entrega e retries
- Responda 2xx em até 10 s. Redirecionamentos não são seguidos.
- Falhas são reenviadas com backoff exponencial; após o limite, a entrega vai para a dead letter (visível e reprocessável no dashboard).
- Deduplique por
X-Event-Id: o mesmo evento pode chegar mais de uma vez. - URLs precisam ser HTTPS públicas; endereços privados são bloqueados (proteção SSRF).
Payload
json
{
"id": "whevt_…",
"object": "event",
"type": "envelope.completed",
"environment": "SANDBOX",
"createdAt": "2026-09-29T17:30:00.000Z",
"data": {
"envelope": { "id": "env_…", "status": "COMPLETED", "title": "Contrato", "externalId": "pedido-123" },
"evidence": { "manifestHash": "d984ae…", "verificationCode": "K3M9-2QXA-7TZP", "verifyUrl": "https://assinapi.com.br/verify?code=…" },
"documents": [{ "id": "doc_…", "filename": "contrato.pdf", "sha256": "…", "finalSha256": "…" }]
}
}Testando localmente
bash
npm install -g @assinapi/cli
assinapi login --token ask_test_…
assinapi listen --forward-to localhost:3000/api/webhooks/assinapi
assinapi trigger envelope.completed