We use cookies to improve your experience and analyze site traffic. By clicking “Accept”, you agree to our use of cookies.

Learn more about our privacy practices
Corvo
Português

Receiving webhooks

Outbound request format, event types, retries and signature verification.

Corvo sends a JSON POST to your registered URL. Respond with HTTP 2xx within 10 seconds; enqueue slow work in your receiver.

Request format

  • Method: POST.
  • Headers: Content-Type: application/json, X-Corvo-Signature: sha256=<hex>.
  • Body: a JSON envelope with id, type, environment, createdAt and data.

json

{
  "id": "evt_identifier",
  "type": "nfse.invoice.authorized",
  "environment": "test",
  "createdAt": "2026-10-10T14:00:00.000Z",
  "data": {
    "invoiceId": "INVOICE_UUID",
    "companyId": "COMPANY_UUID",
    "environment": "test",
    "status": "authorized",
    "documentsStatus": "pending",
    "emailStatus": null,
    "stage": "authorized",
    "accessKey": "ACCESS_KEY",
    "nfseNumber": 123
  }
}

data varies by event; in the fiscal flow it includes the invoice's identification and state. Authorization adds accessKey/nfseNumber, rejection adds errorCode/errorMessage, manual review adds reason, ready documents add accessKey/nfseNumber/documentsReadyAt, and a sent email adds sentAt. The test event carries { "message": "This is a test webhook event" }.

Event types

Event typeWhen
nfse.invoice.authorizedThe invoice was authorized; adds accessKey/nfseNumber.
nfse.invoice.rejectedAuthorization was rejected; adds errorCode/errorMessage.
nfse.invoice.failedProcessing failed.
nfse.invoice.reconciliation_requiredReconciliation is required.
nfse.invoice.manual_reviewManual review is required; adds reason.
nfse.invoice.documents_readyDocuments are ready; adds accessKey/nfseNumber/documentsReadyAt.
nfse.invoice.email_sentThe notification email was sent; adds sentAt.
nfse.invoice.email_failedThe notification email failed.
nfse.invoice.fiscal_discrepancyinvoiceId, status and differences[] (field, local, authorized, delta, kind): difference between Corvo's calculation and the authorized NFS-e, above R$ 0.01 per tax. The authorized XML is not changed.
nfse.invoice.cancelledThe invoice was cancelled; adds reason.
nfse.invoice.cancellation_rejectedThe Sefin refused the cancellation; the invoice is authorized again; adds errorCode and errorMessage.
webhook.testSent by the test route; carries { "message": "This is a test webhook event" }.

Signature verification

The signature is the HMAC-SHA256 of the sent JSON body with the configuration's secret, in hex. Verify the raw received body bytes — do not parse and stringify before verifying.

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

Retries and delivery semantics

  • The outbox makes up to 5 attempts, with intervals after a failure of 60 seconds, 160 seconds, 15 minutes and 30 minutes; execution depends on the worker.
  • Retries preserve the original payload — deduplicate by the event id.
  • The receiver may get the same event again; the current signature has no separate verification timestamp, so deduplication on the receiver is required.
  • Do not assume a global delivery order.