Webhooks for OTP Delivery Status: Complete Guide
How to implement and operate OTP delivery-status webhooks: signature verification, idempotent handlers, retries, dead-letter queues, and observability.
Polling DLR endpoints wastes both your CPU and the provider’s. Webhooks push delivery status the moment it changes. This guide covers production-grade implementation.
Why Webhooks Beat Polling
- Real-time, sub-second.
- Lower load on both sides.
- Cleaner architecture.
Webhook Shape
POST /webhooks/sm
{
"eventId": "evt_01HQ...",
"type": "otp.delivered",
"createdAt": "2026-05-20T08:01:42Z",
"data": {
"requestId": "req_01HQ...",
"phoneNumberHash": "...",
"status": "delivered",
"deliveredAt": "2026-05-20T08:01:39Z"
}
}
Signature Verification
function verify(headers: Headers, rawBody: string, secret: string) {
const sig = headers.get('x-sm-signature')!;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Always read the raw body before express.json() parses.
Idempotent Handlers
async function onWebhook(event: Event) {
const inserted = await db.insert('webhook_events').values({
id: event.eventId, ...
}).onConflictDoNothing();
if (!inserted.rowCount) return; // already processed
await processEvent(event);
}
Provider Retries and DLQ
- Most providers retry 5–10 times with backoff.
- Return 2xx fast — process async.
- Monitor unprocessed-event lag for DLQ.
Observability
- Webhook receipt count.
- Verification failure count (signal of misconfig or attack).
- Processing latency p95.
- Lag between OTP send and DLR.
FAQ
Compare with polling at our polling vs webhooks guide .
StartMessaging Team
StartMessaging Team