Skip to main content

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.

Headers

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.