Register an endpoint
whsec_... securely — it is never shown again (rotate by creating a new endpoint). In
live mode the URL must be https. enabled_events entries may be "*", an exact type, or a
namespace wildcard like "invoice.*".
The delivery
Headers:| Header | Value |
|---|---|
Webhook-Id | The event id (evt_...) — also the idempotency key for your handler. |
Webhook-Timestamp | Unix seconds when the delivery was signed. |
Webhook-Signature | v1=<hex> (space-separated list allows multiple v1= during rotation). |
GET /v1/events/:id):
Verifying the signature
The signature isHMAC-SHA256 over the string `${id}.${timestamp}.${rawBody}` using your raw
whsec_... secret, hex-encoded. Verify over the exact raw request body bytes — do not
re-serialize the parsed JSON. Reject deliveries whose timestamp is outside a tolerance window
(default 300s; see WEBHOOK_TOLERANCE_SECS) to blunt replays.
Node.js
Python
2xx fast (do the real work async). Any non-2xx or a timeout (10s) is treated as a failure
and the delivery is retried with exponential backoff on its own schedule.
Events
This list is the complete set of event types, generated from and verified againstEVENT_TYPES
in src/core/events.ts — subscribe to any of them exactly, by namespace wildcard (invoice.*), or
with *.
| Namespace | Types |
|---|---|
fiscal_profile.* | created, updated, deleted, manifest_signed |
invoice.* | created, stamped, stamp_failed, cancel_pending, cancelled, cancel_rejected |
download.* | created, ready, no_data, failed |
fiscal_profile.manifest_signed fires when you sign a manifiesto de cuentas
(POST /v1/fiscal_profiles/:id/manifest); download.created fires when a descarga masiva job is
accepted; download.no_data fires when the SAT confirms a request found zero CFDIs in range.
Each event is also readable at GET /v1/events/:id and listable at GET /v1/events — the event
log is the source of truth deliveries replay from, so a missed webhook can always be reconciled by
polling events. Endpoints only receive events emitted after they were created.