Register an endpoint
whsec_... securely — it is never shown again (rotate by creating a new endpoint). The one
exception is an Idempotency-Key retry of this same request within 24h, which replays the original
response, secret included (the stored copy is encrypted). 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:
Body (the same object as
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 on the schedule below.
Retries
Each delivery gets up to 35 attempts over about 24 hours, retried independently per endpoint. The first attempt is immediate. The waits after each failure double from 2 seconds to about 34 minutes (2s, 4s, 8s, … 2048s), then retries run once an hour. After the last attempt the delivery staysfailed with next_attempt_at: null and no more retries
are scheduled. If your endpoint was down longer than that, replay the event with
POST /v1/events/:id/resend (the event log keeps it), which starts a fresh 35-attempt schedule.
Timing is approximate, and a large backlog can stretch it: to protect other customers, at most 2
deliveries to the same endpoint run per worker poll. GET /v1/webhook_endpoints/:id/deliveries
shows the next scheduled attempt in next_attempt_at.
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 *.
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.partial fires when some invoices were delivered and some were not;
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.