Webhook Best Practices for Production WhatsApp Apps
Idempotency, retry strategies, signature verification, and handling edge cases — what every developer should know before going live with WhatsApp webhooks.
1. Always Verify Signatures
Every Uno SAP webhook payload includes an X-Uno-Signature header — an HMAC-SHA256 of the request body signed with your webhook secret. Always verify this signature before processing the payload. Always use constant-time comparison.
const crypto = require('crypto')
app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-uno-signature']
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end()
}
res.status(200).end()
processEvent(JSON.parse(req.body))
})
If your framework doesn't support raw body access (like Next.js API routes), verify the signature using the parsed JSON — just make sure you stringify it identically to what Uno SAP sent.
2. Respond Fast, Process Later
Your webhook endpoint must respond with HTTP 200 within 60 seconds. Uno SAP will retry deliveries that time out, but retries add latency and can overwhelm your server. The pattern is simple:
- Receive the webhook → verify signature → enqueue for processing → return 200
- Process the event asynchronously (background job, queue worker, etc.)
- If processing fails, use your own retry logic — don't rely on Uno SAP re-delivery
3. Be Idempotent
Webhooks may be delivered more than once. Network glitches, timeouts, and retries can all cause duplicate deliveries. Every webhook payload includes a unique event ID — use it to deduplicate.
async function handleWebhook(event) {
// Deduplicate by event ID
const exists = await db.events.findUnique({
where: { id: event.id }
})
if (exists) return // Already processed
await processEvent(event)
await db.events.create({ data: { id: event.id } })
}
4. Handle Every Event Type Gracefully
Uno SAP sends 18+ event types — message.received, message.sent, session.connected, session.banned, and more. Your webhook handler should:
- Log unknown event types (don't crash)
- Handle edge cases like
session.banned(alert your team, don't silently ignore) - Separate message events from session lifecycle events — they need different handling
function handleEvent(event) {
switch (event.type) {
case 'message.received':
return handleInboundMessage(event.data)
case 'session.disconnected':
return notifyOps('Session down: ' + event.sessionId)
case 'session.banned':
return notifyUrgent('Session banned: ' + event.sessionId)
default:
console.log('Unhandled event type:', event.type)
}
}
5. Test Before Going Live
Use the POST /v1/webhooks/:id/test endpoint to send test pings to your webhook URL. Verify that your endpoint receives the event, verifies the signature, and returns 200. Check the GET /v1/webhooks/:id/deliveries endpoint to see recent delivery attempts and their response status codes.
"Webhooks are the circulatory system of your WhatsApp integration. If they break silently, your app is blind. Test them, monitor them, and alert on delivery failures."
6. Monitor Delivery Health
Set up monitoring for:
- Delivery failure rate (should be <1%)
- P95 response latency (should be <500ms)
- Duplicate event rate (indicates retry storms)
- Unknown event types appearing (indicates API changes)
Following these practices will keep your WhatsApp integration reliable at scale. If you have questions about specific webhook scenarios, reach out — we're happy to help.