Skip to main content
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

EventWhen it fires
sms.queuedSMS accepted into our queue
sms.sentHanded off to the upstream carrier
sms.deliveredCarrier confirmed delivery to handset
sms.failedCarrier reported permanent failure
sms.rejectedPre-carrier reject (invalid number, DND, etc.)
email.sentHanded off to SES
email.deliveredSES confirmed delivery
email.bouncedPermanent bounce
email.complainedRecipient marked as spam
email.openedOpen-tracking pixel fired
email.clickedLink 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.