Skip to content
Real-time delivery status

Webhooks

Get notified the moment a message's delivery status changes — no polling required.

How webhooks work

Set a webhook URL from your dashboard's Settings and Authevo POSTs a signed event to it.

There are two event types today. otp.status_update fires as WhatsApp relays a delivered, read, or failed status update for a message Authevo sent on your behalf — status is always one of those three. account.low_balance fires once, best-effort (not durably retried), right after a send that leaves your balance below the minimum, so you can alert yourself before the next send fails.

Example payloads
POST — otp.status_update
{
  "event": "otp.status_update",
  "meta_message_id": "wamid.HBgLMjAxMjM0NTY3ODkVAgARGBI...",
  "status": "delivered"
}
POST — account.low_balance
{
  "event": "account.low_balance",
  "balance": 1.42
}

Retries

If your endpoint doesn't return a 2xx response, Authevo retries with escalating backoff — a few immediate attempts, then durable retries at roughly 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, and 12 hours apart — up to 12 attempts total before giving up. Delivery is at-least-once: dedupe by meta_message_id if you must guarantee exactly-once handling on your side.

Verifying the signature

Every webhook request carries an X-Authevo-Signature header — an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret. Verify it before trusting the payload.

Using the official Node.js/TypeScript SDK? It ships a verifyWebhook() helper doing exactly this — import { verifyWebhook } from 'authevo' instead of hand-rolling it.
Verifying in Node.js (raw REST, no SDK)
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

function isValidWebhook(rawBody, signatureHeader, webhookSecret) {
  const expected = createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
  const provided = (signatureHeader || '').replace('sha256=', '');
  const a = Buffer.from(expected);
  const b = Buffer.from(provided);
  return a.length === b.length && timingSafeEqual(a, b);
}

// rawBody must be the exact, unparsed request body — verify BEFORE JSON.parse.
app.post('/webhooks/authevo', (req, res) => {
  const signature = req.headers['x-authevo-signature'];
  if (!isValidWebhook(req.rawBody, signature, process.env.AUTHEVO_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.rawBody);
  // handle event.event === 'otp.status_update'
  res.status(200).end();
});
Your webhook secret is generated once, alongside your API keys, when your account is created — find it, and rotate it, from your dashboard's Settings. Rotating it invalidates the old secret immediately, so update your verification code first.

Setting your webhook URL

Set your webhook URL from your dashboard's Settings — it must be a public https:// URL. Authevo refuses to save a URL that resolves to a private or internal IP address, and won't call it until you do.