Receive webhooks
Three rules make a reliable receiver: answer fast, verify the signature, process async. Here is the shape of it.
1. A receiver that answers fast
app.js (Express)
app.post('/hooks/camelmailer', express.raw({ type: 'application/json' }), (req, res) => { // 1. verify the signature against the raw body (below) // 2. enqueue for processing — do NOT do the work here queue.push(JSON.parse(req.body)); res.status(200).end(); });
Failed deliveries are retried with backoff, so a handler that takes seconds or 500s under load creates duplicate work for itself. Accept, enqueue, return.
2. Verify the signature
Payloads are RSA-signed with the installation key. Fetch the public key once from the dashboard (server settings), then:
const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(rawBody); const ok = verifier.verify(publicKeyPem, signatureFromHeader, 'base64'); if (!ok) return res.status(400).end();
- Verify against the raw request body, because re-encoding the parsed JSON breaks signatures.
- Reject unsigned or badly signed requests loudly (400 + alert).
3. Process idempotently
Retries mean at-least-once delivery. Key your processing on the message id + event type so a replay is a no-op.
Debugging
- The dashboard's webhook request log shows every attempt with request and response. Start there before your own logs.
- Local development: expose your receiver with a tunnel (ngrok, cloudflared) and point a dev webhook at it; disable it afterwards (
POST …/webhooks/{id}/disable).
Event catalogue and creation API: Webhooks and the management reference.
