Skip to content

Webhook Security

Every webhook delivery includes a cryptographic signature so you can verify that the request genuinely came from GamifyHost and hasn’t been tampered with.

Each webhook request includes these headers:

HeaderDescriptionExample
X-Webhook-SignatureHMAC-SHA256 signature of the request bodysha256=a1b2c3d4e5f6...
X-Webhook-EventThe event typegame.played
X-Webhook-TimestampWhen the event was dispatched (RFC 3339)2025-07-15T10:30:00Z
  1. GamifyHost serializes the payload to JSON
  2. Computes HMAC-SHA256(secret, jsonBody) using your webhook secret
  3. Sends the signature as sha256=<hex> in the X-Webhook-Signature header

On your server:

  1. Read the raw request body (do not parse and re-serialize — the byte-exact body must match)
  2. Compute HMAC-SHA256(your_secret, raw_body)
  3. Compare with the X-Webhook-Signature header value using a constant-time comparison
  4. Reject the request if the signatures don’t match
import crypto from 'crypto';
function verifyWebhook(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Usage in Express:
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
if (!verifyWebhook(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process event...
res.sendStatus(200);
});
  • Always verify signatures — Never process unverified webhook payloads
  • Use constant-time comparison — Prevents timing attacks (crypto.timingSafeEqual, hmac.compare_digest, hash_equals, etc.)
  • Read the raw body — Parse after verification. Re-serializing JSON may change field order or whitespace, breaking the signature
  • Respond quickly — Return 200 immediately and process the event asynchronously. GamifyHost times out after 10 seconds
  • Handle retries idempotently — The same event may be delivered multiple times (up to 5 retries). Use the payload’s unique IDs (ledgerId, playId) to deduplicate
  • Store your secret securely — Use environment variables or a secrets manager. The secret is only shown once at webhook creation