Webhook examples
Express webhook example
Verify Yanola webhook signatures against the raw request body in an Express server.
What this guide covers
- Capture raw request bytes before JSON parsing.
- Verify the Yanola signature and reject invalid requests.
- Acknowledge durable event acceptance quickly and process work asynchronously.
Install and configure
This example uses Express's route-local raw parser and the SDK verifier. Keep the signing secret in server-only environment configuration.
npm install express @yanola/whatsappYANOLA_WEBHOOK_SECRET=your_copy_once_endpoint_secretComplete Express route
import express from "express"
import {
verifyYanolaWebhookSignature,
} from "@yanola/whatsapp"
const signingSecret = process.env.YANOLA_WEBHOOK_SECRET
if (!signingSecret) {
throw new Error("YANOLA_WEBHOOK_SECRET is required")
}
const app = express()
app.post(
"/webhooks/yanola",
express.raw({ type: "application/json" }),
async (req, res) => {
const valid = verifyYanolaWebhookSignature({
secret: signingSecret,
signature: req.get("yanola-signature"),
rawBody: req.body,
})
if (!valid) {
return res.status(400).send("Invalid Yanola signature")
}
const event = JSON.parse(req.body.toString("utf8"))
// Replace this with an atomic database insert or durable queue write
// keyed by event.id before returning success.
await acceptEventOnce(event)
return res.sendStatus(204)
}
)
app.use(express.json())
app.listen(3000)Express runs middleware in registration order. The webhook route must use express.raw before a global express.json parser; otherwise req.body is no longer the exact Buffer Yanola signed.
Deduplicate at durable acceptance
The transaction or queue API is application-specific. The invariant is that the evt_ key and future work are durable before the route returns 2xx.
async function acceptEventOnce(event: { id: string }) {
await database.transaction(async (transaction) => {
const inserted = await transaction.events.insertIfAbsent({
id: event.id,
payload: event,
})
if (inserted) {
await transaction.jobs.enqueue({
kind: "process-yanola-event",
eventId: event.id,
})
}
})
}Deploy the endpoint
- Expose POST /webhooks/yanola through public HTTPS for Live Mode.
- Create the Yanola endpoint with the matching URL and payload mode.
- Copy its secret into YANOLA_WEBHOOK_SECRET.
- Do not enable a body-transforming proxy in front of the route.
- Return 400 for invalid signatures and 2xx only after durable acceptance.
Common Express mistakes
| Mistake | Effect | Correction |
|---|---|---|
app.use(express.json()) before the webhook route | req.body is parsed instead of a Buffer. | Register the raw route first or use an isolated router. |
JSON.stringify(req.body) for verification | Serialization changes signed bytes. | Verify req.body directly. |
Returning 204 before the database or queue write | Accepted work can be lost without retry. | Await durable acceptance. |
Doing slow business work in the route | Yanola times out after 10 seconds. | Queue the work and respond promptly. |
Test locally
Start an HTTPS tunnel (cloudflared or ngrok) to your local receiver, create a Test Mode endpoint with the tunnel's public HTTPS URL, then send a dashboard test event. Confirm one durable evt_ record and a 204 delivery result.