Verify every POST to your webhook URL before you process the body.

Requirements

  1. Read the raw request body bytes (do not re-serialize JSON).
  2. Read signature headers from the delivery (typically webhook-id / svix-id, webhook-timestamp / svix-timestamp, and webhook-signature / svix-signature).
  3. Use the endpoint signing secret from Wazapin (format whsec_…). Store it as a server secret, not in client code.
  4. Reject requests outside the allowed timestamp window (replay protection).
  5. Compare expected and received signatures with a constant-time comparison.
The signing scheme matches the Svix standard used for outbound deliveries. You can use the official Svix libraries or implement the same HMAC steps below.

Node.js

import { Webhook } from 'svix';

const wh = new Webhook(process.env.WAZAPIN_WEBHOOK_SECRET);

app.post('/webhooks/wazapin', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    wh.verify(req.body, req.headers);
  } catch {
    return res.status(403).send('invalid signature');
  }
  const event = JSON.parse(req.body.toString('utf8'));
  res.status(200).send('ok');
});

Python

from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["WAZAPIN_WEBHOOK_SECRET"])

@app.post("/webhooks/wazapin")
async def wazapin_webhook(request: Request):
    payload = await request.body()
    try:
        wh.verify(payload, dict(request.headers))
    except WebhookVerificationError:
        raise HTTPException(status_code=403)
    return {"ok": True}

Go

Use github.com/svix/svix-webhooks with your endpoint whsec_ secret, or implement the same signed content: msgID + "." + timestamp + "." + string(body) with HMAC-SHA256 and base64, matching v1 entries in the signature header.

Failure handling

  • Return 403 when the signature is invalid.
  • Return 400 for malformed JSON after verification succeeds.
  • Return 200 for duplicate events after your idempotency check.
Never verify signatures on re-encoded JSON. Always use the raw HTTP body.