Webhooks push event notifications to your server the moment something happens — you don’t have to poll.
Registering an endpoint
From the dashboard (Webhooks → Add webhook) or via the API:
curl -X POST https://api.robase.dev/v1/webhooks \
-H "Authorization: Bearer rb_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/robase",
"events": ["sms.delivered", "sms.failed", "email.bounced"],
"description": "production"
}'
The secret is returned only once on creation. Store it immediately — lose it and you’ll have to rotate.
Event types
| Event | When it fires |
|---|
sms.queued | SMS accepted into our queue |
sms.sent | Handed off to the upstream carrier |
sms.delivered | Carrier confirmed delivery to handset |
sms.failed | Carrier reported permanent failure |
sms.rejected | Pre-carrier reject (invalid number, DND, etc.) |
email.sent | Handed off to SES |
email.delivered | SES confirmed delivery |
email.bounced | Permanent bounce |
email.complained | Recipient marked as spam |
email.opened | Open-tracking pixel fired |
email.clicked | Link was clicked |
Payload shape
Every event uses the same envelope:
{
"id": "evt_01H8XKQJ3Z...",
"type": "sms.delivered",
"created_at": "2026-04-17T10:30:05Z",
"data": {
"sms_id": "01H8XKQJ3Z...",
"to": "+2348012345678",
"from": "SHUTTLERS",
"status": "delivered",
"segments": 1,
"provider": "beem",
"provider_message_id": "beem-7af3c1",
"carrier": "mtn_ng",
"cost": { "amount_kobo": 400, "currency": "NGN" }
}
}
The data object shape matches the resource GET response — same shape as GET /v1/sms/:id.
Signature verification
Every request includes two headers:
Robase-Signature: t=1718637005,v1=3c2a9f...
Robase-Event: sms.delivered
v1 is the hex HMAC-SHA256 of {t}.{body}, signed with your webhook’s secret. Verify it before trusting the payload:
import { verifyWebhook } from '@robase/node';
app.post('/webhooks/robase', async (req, res) => {
const raw = await rawBody(req);
const sig = req.headers['robase-signature'] as string;
const ok = await verifyWebhook(raw.toString('utf8'), sig, process.env.WEBHOOK_SECRET!);
if (!ok) return res.status(401).end();
const event = JSON.parse(raw.toString('utf8'));
await processEvent(event);
res.status(200).end();
});
Reject timestamps older than 5 minutes — stale signatures are replay attempts. Our SDK helpers enforce this by default.
Retry behavior
If your endpoint returns a non-2xx status, we retry with exponential backoff:
1s → 5s → 30s → 2m → 15m → 1h → 6h
Seven attempts total, across roughly 7.5 hours. After that the delivery is marked failed in the dashboard and we stop.
Testing from the dashboard
Webhooks → your webhook → Send test fires a synthetic event at your URL. Use this to prove your signature verification works before shipping. See each delivery attempt (status code, latency, response body) in the Deliveries tab.