Documentation Index
Fetch the complete documentation index at: https://docs.politicalcomms.com/llms.txt
Use this file to discover all available pages before exploring further.
Every webhook request is signed with HMAC-SHA256 using your endpoint secret. Validate the signature before trusting the payload.
Webhook endpoints are publicly reachable URLs. Anyone who knows the URL can send a POST to it. Always validate the signature before processing. Don’t take an unsigned or invalid-signature request seriously.
POST /your-webhook-endpoint HTTP/1.1
Host: your-domain.com
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Event-Type: message.delivered
X-Event-ID: msg_7f8a9b0c1d2e3f4g
X-Timestamp: 1705488600
Node.js example
const crypto = require('crypto');
function validateWebhook(req, webhookSecret) {
const signature = req.headers['x-webhook-signature'];
const body = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(body);
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return signature === expectedSignature;
}
app.post('/webhooks', (req, res) => {
if (!validateWebhook(req, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});
Tips
- Use a constant-time comparison for the signature check (e.g.
crypto.timingSafeEqual in Node) to avoid timing attacks.
- Stringify the body identically to how it was received - middleware that reformats JSON will break the signature. Either store the raw body before parsing or use a body parser that preserves the exact byte sequence.
- Reject stale events by checking
X-Timestamp is within (e.g.) 5 minutes of now. Combined with signature validation, this protects against replay attacks.
- Don’t log the secret. It should only ever appear in your secret manager and in memory inside the validation routine.