Every webhook delivery from Run a Call includes an HMAC signature header. Verify it before processing — otherwise anyone with your URL could spoof events.

How signatures work

When you create a webhook subscription, Run a Call generates a signing secret unique to that subscription. The secret is shown once at creation — store it where your receiving code can read it.

For every event delivery, Run a Call:

  1. Builds the JSON payload.
  2. Computes HMAC-SHA256(secret, payload).
  3. Sends the hex digest in the X-Signature header.
  4. POSTs the payload to your URL.

Your endpoint:

  1. Recomputes HMAC-SHA256(secret, raw_request_body) with the secret you stored.
  2. Compares against the X-Signature header.
  3. If they match, the request is authentic.
  4. If they don't match, reject with HTTP 400.

Pseudocode

import crypto from 'crypto';

function verifyWebhook(req, secret) {
  const signature = req.headers['x-signature'];
  const computed = crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computed),
  );
}

Use a constant-time comparison (timingSafeEqual) to prevent timing-attack leaks of the signature.

Use the raw body, not the parsed JSON

Important

The signature is computed over the raw byte sequence of the payload. If your framework parses JSON before you can see the bytes, the re-serialized JSON may have different formatting and the signature won't match.

FrameworkHow to capture raw body
Expressapp.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }))
Next.js Route HandlersRead await req.text() before parsing

Replay protection

Tip

The payload includes a created_at timestamp. Reject events older than a few minutes to prevent an attacker from replaying captured events later.

const ageMinutes = (Date.now() - new Date(event.created_at).getTime()) / 60000;
if (ageMinutes > 5) return reject();

Rotating the secret

If you suspect a secret leaked:

  1. Settings → Integrations → Webhooks.
  2. Open the subscription.
  3. Rotate secret (generates a new one; old one stops working immediately).
  4. Update your endpoint to use the new secret.
Warning

There's no overlap window today — the old secret invalidates the moment the new one is issued. Plan the rotation accordingly (deploy first, then rotate).