Recebendo webhooks
Formato das chamadas de saída, verificação de assinatura e política de repetição.
A Corvo envia um POST JSON para a URL cadastrada. Responda com HTTP 2xx em até 10 segundos; processamento demorado deve ser enfileirado pelo receptor.
Formato da requisição
- Método:
POST. - Headers:
Content-Type: application/json,X-Corvo-Signature: sha256=<hex>. - Corpo: envelope JSON com
id,type,environment,createdAtedata.
json
{
"id": "evt_identificador",
"type": "nfse.invoice.authorized",
"environment": "test",
"createdAt": "2026-10-10T14:00:00.000Z",
"data": {
"invoiceId": "UUID_DA_NOTA",
"companyId": "UUID_DA_EMPRESA",
"environment": "test",
"status": "authorized",
"documentsStatus": "pending",
"emailStatus": null,
"stage": "authorized",
"accessKey": "CHAVE_DE_ACESSO",
"nfseNumber": 123
}
}data varia por evento. No fluxo fiscal, inclui identificação e estado da nota; autorização acrescenta accessKey/nfseNumber, rejeição acrescenta errorCode/errorMessage, revisão manual acrescenta reason, documentos prontos acrescenta accessKey/nfseNumber/documentsReadyAt, email enviado acrescenta sentAt. O evento de teste contém { "message": "This is a test webhook event" }.
Tipos de evento
| Evento | `data` acrescenta ao estado da nota |
|---|---|
nfse.invoice.authorized | accessKey, nfseNumber. |
nfse.invoice.rejected | errorCode, errorMessage. |
nfse.invoice.failed | Identificação e estado da nota. |
nfse.invoice.reconciliation_required | Identificação e estado da nota. |
nfse.invoice.manual_review | reason. |
nfse.invoice.documents_ready | accessKey, nfseNumber, documentsReadyAt. |
nfse.invoice.email_sent | sentAt. |
nfse.invoice.email_failed | Identificação e estado da nota. |
nfse.invoice.fiscal_discrepancy | invoiceId, status e differences[] (field, local, authorized, delta, kind): diferença entre o cálculo da Corvo e a NFS-e autorizada, acima de R$ 0,01 por tributo. O XML autorizado não é alterado. |
nfse.invoice.cancelled | A nota foi cancelada; inclui reason. |
nfse.invoice.cancellation_rejected | A Sefin recusou o cancelamento; a nota volta a authorized; inclui errorCode e errorMessage. |
webhook.test | { "message": "This is a test webhook event" }. |
Verificação de assinatura
A assinatura é HMAC-SHA256 do corpo JSON enviado, com o secret da configuração, em hexadecimal. Verifique os bytes brutos do corpo recebido — não faça parse e stringify antes de verificar.
javascript
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(rawBody, signatureHeader, secret) {
if (typeof signatureHeader !== 'string') return false;
const match = /^sha256=([a-f0-9]{64})$/.exec(signatureHeader);
if (!match) return false;
const received = Buffer.from(match[1], 'hex');
const expected = createHmac('sha256', secret).update(rawBody).digest();
return timingSafeEqual(received, expected);
}Repetição e idempotência
- O receptor pode receber novamente o mesmo evento: deduplique por
iddo envelope. - Retries preservam o payload original.
- O outbox usa até 5 tentativas, com intervalos após falha de 60 segundos, 160 segundos, 15 minutos e 30 minutos; a execução depende do worker.
- Não assuma ordem global de entrega.