The event model
You register one webhook per operator + network combination, choosing which event category it covers:
There’s at most one registration per (account, operator, network) —
eventType isn’t part of that key, so registering a second event type for the same operator+network replaces the first rather than adding a second registration. Registering again for an existing combination without update: true in the body returns 409 CONFLICT.
Registering
voice (or both) always succeeds if the operator/network combination itself is valid — there’s no registration-time check that voice is actually enabled the way there is for sms. But you won’t actually receive voice events for a line unless that line’s active plan includes voice support (a plan with no voice allowance leaves voice disabled on the number). Make sure the SIMs you expect voice events for are on a voice-capable plan before assuming a registered voice webhook is misbehaving if nothing arrives.Registering a webhook
"***ENCRYPTED***", plus a one-time signingSecret (format whsec_<64 hex chars>) — see Verifying delivery signatures below.
Manage registrations as a normal REST collection:
signingSecret again — only the create/upsert response does. Store it the moment you get it; losing it means re-registering.
How your endpoint authenticates the caller
Separately from payload signing,authentication (type: basic | bearer | api_key | none) protects your webhook URL — Spenza presents this credential when it calls you, so your endpoint can reject callers that don’t have it. Set it to bearer or basic, not none, so your endpoint isn’t open to anyone who discovers its URL.
Verifying delivery signatures
Every delivery (including test events) carries two headers:X-Spenza-Timestamp is Unix seconds. The v1 value in X-Spenza-Signature is an HMAC-SHA256 computed over the exact string ${timestamp}.${rawBody}, keyed with your registration’s signingSecret — a Stripe-compatible scheme, chosen so most partners can reuse an existing verifier:
rawBody (the exact bytes received), not a re-serialized version of the parsed JSON — re-serialization can silently change key order or whitespace and break the comparison. A 5-minute tolerance window on X-Spenza-Timestamp is documented convention (WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 300) — Spenza doesn’t enforce it on the sending side; it’s your endpoint’s own replay-protection check to make if you want it.
Retry behavior
Delivery retries are configurable per registration viaretryPolicy:
A delivery is considered successful when your endpoint returns any
2xx response — respond fast and do your processing after responding, not before. After 10 consecutive delivery failures, a registration’s status automatically flips to suspended; check GET /api/v1.1/webhooks periodically, or the status field on GET /api/v1.1/webhooks/{id}, to catch this.
Testing a registration
Send a one-off synthetic event without waiting for a real operator event:messageUrl → callbackMessageUrl → voiceUrl → callbackVoiceUrl. It’s recorded in the deliveries list but doesn’t count toward the 10-consecutive-failure auto-suspend threshold, so you can rehearse your handler without risking a live registration’s status.
Inspecting and redelivering past events
deliveryId, webhookId, eventType, status, responseCode, attempt, deliveredAt). If you find a FAILED delivery worth retrying manually (after fixing your handler, for example), redeliver the original event and body to its original endpoint:
202 immediately with a new PENDING delivery record — it runs on a background queue, not the request cycle, so poll the deliveries list for the outcome.
Idempotency
Example payloads
Payload schema is being published. The exact JSON body sent to
messageUrl/voiceUrl on a real inbound SMS or voice event isn’t published yet. Until then, register a webhook against a test endpoint (a request-inspection tool) and use POST /api/v1.1/webhooks/{id}/test or a real event to capture the actual shape before writing a parser.Next steps
- Security & Best Practices for handling the credentials in
authenticationandsigningSecret. - API Reference → Webhooks (sidebar) for the full field-level schema of the registration and delivery objects.

