CPaaS
Guide: verify WeZend webhook signatures in Express (with code)
A complete, working Express endpoint that receives delivery webhooks and rejects anything that isn't genuinely from WeZend.
WWeZend TeamJuly 5, 2026 · 5 min read
WeZend's webhooks are per-message: you pass webhook_url on the send request itself (see Webhooks), and this is the receiving side of that.
The endpoint
const express = require("express");
const crypto = require("crypto");
const app = express();
app.use(express.raw({ type: "application/json" })); // need the raw body to verify
app.post("/hooks/wezend", (req, res) => {
const signature = req.get("X-ZafeConnect-Signature");
const timestamp = req.get("X-ZafeConnect-Timestamp");
const rawBody = req.body.toString("utf8");
const expected = crypto
.createHmac("sha256", process.env.WEZEND_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const isValid =
signature &&
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!isValid) return res.status(401).send("invalid signature");
// Respond fast — do the real work after acknowledging.
res.sendStatus(200);
const payload = JSON.parse(rawBody);
// e.g. payload.event === "message.delivered"
processDeliveryEvent(payload).catch(console.error);
});
Three things worth getting right
- Use the raw body for the signature check, not the parsed/re-serialized JSON — re-serializing can subtly change whitespace or key order and break the HMAC comparison.
- Respond
2xxbefore doing slow work. A failed or slow response triggers a retry (30s, 5 minutes, 30 minutes) — that's fine for a real outage, wasteful for a webhook handler that's just slow. - The header names still say
X-ZafeConnect-*, notX-WeZend-*— a leftover from the platform's original name that hasn't been renamed in the backend yet. Match the header name exactly as shown above; don't assume it follows the current brand.
Where the signing secret comes from
Find (and rotate) it in Settings → Webhook secret, backed by GET /v1/webhook-secret and POST /v1/webhook-secret/rotate.