Security · 4 min

Why every Augur webhook is HMAC-signed

Anyone who knows your webhook URL can forge a payload. HMAC signing lets your downstream code verify origin in three lines. Here's how we do it.

2026-05-25

Anyone who knows your generic-webhook URL can POST whatever they want at it. The receiver has no way to tell whether the payload actually came from Augur or from a curious attacker who scraped the URL out of a Slack export.

HMAC signing fixes that. We send every payload with a header:

x-augur-signature: sha256=<hex of HMAC-SHA256(secret, raw-body)>

Each webhook channel has its own auto-generated signing secret (visible under /dashboard/channels). On your side, three lines of Node verify the request before you trust the body:

const expected = "sha256=" +
  crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected))) {
  return res.status(401).end();
}

Why timing-safe compare?

A vanilla === short-circuits on the first mismatched byte — leaking how much of the signature an attacker got right via response timing. timingSafeEqualcompares constant-time regardless of input. Cheap insurance.

Replay protection

The signature alone doesn't stop replay attacks. If an attacker captures one valid payload they can re-send it later. Pair the signature check with a timestamp inside the body (Augur sends dispatched_at) and reject anything more than 5 minutes old. Slack does this; so should you.

Full reference at /docs/webhook.

← Back to blog · Start free