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.
{
"event": "otp.status_update",
"meta_message_id": "wamid.HBgLMjAxMjM0NTY3ODkVAgARGBI...",
"status": "delivered"
}{
"event": "account.low_balance",
"balance": 1.42
}Retries
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.
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();
});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.