Webhooks
O Siged avisa o seu sistema a cada evento relevante do envelope, com uma requisição
POST assinada. O corpo não leva dado pessoal: use o identificador para consultar o
envelope pela API.
Cabeçalhos
| Cabeçalho | Descrição |
|---|---|
X-Siged-Event | Tipo do evento (ex.: envelope.concluded). |
X-Siged-Event-Id | Id único do evento — use para descartar duplicatas (entrega pelo menos uma vez). |
X-Siged-Delivery-Id | Id da entrega (o mesmo nas novas tentativas e no reenvio). |
X-Siged-Timestamp | Momento da assinatura em segundos Unix (UTC). |
X-Siged-Signature | v1=<HMAC-SHA256 em hexadecimal de "{timestamp}.{corpo cru}" com o segredo de assinatura>. |
Como verificar a assinatura
Recalcule o HMAC-SHA256 de "{X-Siged-Timestamp}.{corpo cru}" com o segredo de assinatura (whsec_…), compare com X-Siged-Signature em tempo constante e recuse carimbo com mais de 5 minutos de diferença (replay). Responda 2xx em até 10 s; processe depois (fila). Redirecionamentos não são seguidos.
const crypto = require('crypto');
// app.use('/webhooks/siged', express.raw({ type: 'application/json' }));
function sigedWebhookValido(req, segredo) {
const ts = req.header('X-Siged-Timestamp');
const recebida = req.header('X-Siged-Signature') || '';
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay
const esperada = 'v1=' + crypto.createHmac('sha256', segredo)
.update(`${ts}.${req.body.toString('utf8')}`) // corpo CRU
.digest('hex');
const a = Buffer.from(esperada), b = Buffer.from(recebida);
return a.length === b.length && crypto.timingSafeEqual(a, b);
} using System.Security.Cryptography;
using System.Text;
static bool SigedWebhookValido(string corpoCru, string? timestamp, string? assinatura, string segredo) {
if (!long.TryParse(timestamp, out var ts) || assinatura is null) return false;
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300) return false; // replay
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(segredo));
var esperada = "v1=" + Convert.ToHexString(
hmac.ComputeHash(Encoding.UTF8.GetBytes($"{ts}.{corpoCru}"))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(esperada), Encoding.ASCII.GetBytes(assinatura));
} Entrega e novas tentativas
Sem 2xx (ou timeout de 10 s): novas tentativas em 1, 5, 15, 60 e 360 minutos; depois disso a entrega fica FALHOU e pode ser reenviada pela tela. A ordem de chegada não é garantida: use o createdAt e consulte o envelope pela API se precisar do estado atual.
Eventos
Envelope criado
envelope.created
Um envelope foi criado (pela tela ou pela API).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "envelope.created",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "COLLECTING",
"stageKey": "COLETA",
"stageName": "Coleta de documentos",
"completeness": "DOCS_COMPLETE",
"missingCount": 0
}
} Mudança de etapa
envelope.stage_changed
O envelope avançou ou voltou de etapa (ex.: devolvido para a coleta).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "envelope.stage_changed",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "IN_REVIEW",
"stageKey": "CURADORIA",
"stageName": "Curadoria",
"completeness": "DOCS_COMPLETE",
"missingCount": 0
}
} Envelope concluído
envelope.concluded
O envelope chegou à etapa final (concluído).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "envelope.concluded",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "COMPLETED",
"stageKey": "CONCLUIDO",
"stageName": "Concluído",
"completeness": "COMPLETED",
"missingCount": 0
}
} Envelope reprovado
envelope.rejected
O envelope foi reprovado numa alçada de aprovação (encerrado).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "envelope.rejected",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "REJECTED",
"stageKey": "FRAUDE",
"stageName": "Alçada de fraude",
"completeness": "DOCS_COMPLETE",
"missingCount": 0
}
} Envelope cancelado
envelope.cancelled
O envelope foi cancelado com justificativa.
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "envelope.cancelled",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "CANCELED",
"stageKey": "COLETA",
"stageName": "Coleta de documentos",
"completeness": "DOCS_COMPLETE",
"missingCount": 0
}
} Documento aprovado
document.approved
Um documento foi aprovado na curadoria (humana ou automática).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "document.approved",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "IN_REVIEW",
"stageKey": "CURADORIA",
"stageName": "Curadoria",
"completeness": "DOCS_COMPLETE",
"missingCount": 0,
"document": {
"documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"requirementCode": "CONTRATO_SOCIAL",
"documentTypeCode": "DOC_CONTRATO_SOCIAL",
"participantId": null,
"status": "APPROVED",
"version": 1
}
}
} Documento reprovado / reenvio solicitado
document.rejected
Um documento foi reprovado; o envelope pede um novo envio (motivo mascarado no corpo).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "document.rejected",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "RETURNED",
"stageKey": "COLETA",
"stageName": "Coleta de documentos",
"completeness": "DOCS_COMPLETE",
"missingCount": 0,
"document": {
"documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"requirementCode": "CONTRATO_SOCIAL",
"documentTypeCode": "DOC_CONTRATO_SOCIAL",
"participantId": null,
"status": "REJECTED",
"version": 1,
"rejectionReason": "Documento ilegível. Envie uma nova digitalização."
}
}
} Revisão por pessoa solicitada
document.human_review_requested
Uma decisão automática da IA foi reaberta: o documento voltou para a curadoria humana (sem o motivo no corpo).
{
"id": "c0ffee00-1234-4abc-9def-0123456789ab",
"type": "document.human_review_requested",
"createdAt": "2026-10-01T13:00:00Z",
"data": {
"envelopeId": "7d3c1f0e-2b4a-4c55-9e1f-0a9b8c7d6e5f",
"protocol": "ENV-20261001-0042",
"externalRef": "PROP-2026-000123",
"typeCode": "CONTA-PJ",
"status": "RETURNED",
"stageKey": "COLETA",
"stageName": "Coleta de documentos",
"completeness": "DOCS_COMPLETE",
"missingCount": 0,
"document": {
"documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"requirementCode": "CONTRATO_SOCIAL",
"documentTypeCode": "DOC_CONTRATO_SOCIAL",
"participantId": null,
"status": "REJECTED",
"version": 1,
"rejectionReason": "Documento ilegível. Envie uma nova digitalização."
}
}
}