Start typing to search the documentation.

to navigate·to open

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