Webhooks
SMS Webhooks
Receive SMS lifecycle events (queued, sending, sent, delivered, failed) at your own endpoint — registration, the signed payload envelope, HMAC-SHA256 verification, and retry behaviour.
SMS webhooks push delivery-lifecycle events to an HTTPS endpoint you control, so
you can react to a message’s progress without polling
GET /sms/status.
Events
| Event | Fired when |
|---|---|
sms.queued |
The message is accepted and queued for sending. |
sms.sending |
The message is being handed to the carrier. |
sms.sent |
The message has been submitted to the carrier. |
sms.delivered |
The carrier confirmed delivery to the handset. |
sms.failed |
The message could not be delivered. |
Register an endpoint
Add and manage webhook endpoints from the SMS app → Webhooks page. For each endpoint you provide:
- URL — your HTTPS receiver.
- Description — optional label (≤ 255 chars).
- Events — the subset of SMS events to subscribe to (at least one).
On creation WRNexus generates a signing secret prefixed whsec_. It is
shown once — copy it immediately and store it as a secret in your environment;
you use it to verify signatures. You can pause, edit, and delete endpoints, and
trigger a test event to confirm wiring.
Payload
Deliveries are POST requests with Content-Type: application/json and a
canonical envelope:
{
"id": "whd_8f1c0e2b-...",
"event": "sms.delivered",
"createdAt": "2026-06-18T14:30:03Z",
"data": {
"messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
"to": "+919812345678",
"status": "delivered"
}
}
id— a unique delivery id. Re-deliveries of the same event reuse it, so you can dedupe idempotently.event— one of the event strings above.data— the message snapshot for the event.
Verify the signature
Every delivery carries an HMAC of the raw request body in the
X-WRNexus-Signature header:
X-WRNexus-Signature: sha256=<hex-hmac>
The HMAC is HMAC-SHA256 keyed by your endpoint’s whsec_ secret. Recompute
it over the raw body and compare in constant time. Always verify before trusting
a payload, and reject on mismatch.
Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express — use the RAW body, not the parsed object:
app.post('/webhooks/sms', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.get('X-WRNexus-Signature'), process.env.WRN_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString());
// handle event.event / event.data …
res.sendStatus(200);
});
Python
import hashlib, hmac
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
Responding & retries
- Acknowledge with any
2xx. Respond quickly; do heavy work asynchronously. - Retries. A non-2xx response or transport error is retried up to 5 attempts with exponential back-off (≈ 1s, 2s, 4s, 8s, capped at 60s).
- Idempotency. Because a retried delivery keeps the same
id, store processed ids and no-op on repeats.
See also
- SMS API — send messages and poll status directly.
- Email Webhooks — the same envelope and signature scheme.
- Authentication — API keys for the send side.