Skip to main content
Robase fires webhook events at every SMS lifecycle transition. Subscribe to the ones you care about when you register a webhook.

Event types

Fires the moment we accept a POST /v1/sms. Useful for reconciling “we intended to send X” vs “we actually sent X” in your audit log.
The upstream carrier accepted the message. This is not delivery — just that the carrier acknowledged the handoff.
The handset confirmed receipt (via the carrier’s delivery report). This is the event you care about for OTP flows.
A permanent failure after send — the carrier tried but couldn’t deliver. Typically: handset off too long, SIM no longer exists, blacklist.
Wallet overage is automatically refunded for permanent failures.
Pre-send rejection — e.g. phone_invalid, dnd_blocked, or carrier-level sender-ID block. No attempt was made to deliver.
Also refunds overage automatically.

Payload envelope

Every event has the same top-level shape:
data matches the shape of GET /v1/sms/:id — if you’ve typed your fetch code, the webhook shape is the same.

Signature verification

Same scheme as email: Robase-Signature: t=<unix>,v1=<hex-hmac>, where the HMAC is over {t}.{body} with your webhook’s secret. See Webhooks for the verification code per language.

Common patterns

OTP flow

Campaign monitoring

Delayed DLRs

African carriers sometimes take minutes or even hours to confirm delivery. Don’t tie UI behavior to sms.delivered — use sms.sent as the “we handed it off” signal, and treat sms.delivered as asynchronous confirmation that may arrive later. For OTP-style flows where you need fast feedback, show the user “Code sent — check your messages” as soon as you get sms.sent, and only escalate to “Didn’t get it?” after a 60-second client-side timeout.

Test-mode webhooks

Test-mode sends fire the same events, with test_mode: true in data. Use this to write idempotent webhook handlers — your real production traffic will go through the same code path with the flag off.