Verifying signatures

Every webhook POST Aigeon sends includes an X-Aigeon-Signature header. Verifying it before processing the payload protects you from spoofed requests.

Header format

X-Aigeon-Signature: t=1745000000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
  • t — the Unix timestamp Aigeon used when computing the signature. Use it for replay-attack prevention.
  • v1 — HMAC-SHA256 of <t>.<raw_body> using your signing secret as the key.

Verification algorithm

  1. Parse t and v1 from the header.
  2. Reject the request if t is more than 5 minutes in the past (replay protection).
  3. Compute expected = HMAC-SHA256(key=signing_secret, msg="<t>.<raw_body>").
  4. Use a constant-time comparison between expected and v1.

Node.js example

import { createHmac, timingSafeEqual } from "crypto";

function verifyAigeonSignature(
  rawBody: Buffer,
  sigHeader: string,
  secret: string,
): boolean {
  const parts = Object.fromEntries(
    sigHeader.split(",").map((p) => p.split("=")),
  );
  const t = parts["t"];
  const v1 = parts["v1"];
  if (!t || !v1) return false;

  // Replay attack protection: reject anything older than 5 minutes
  if (Math.abs(Date.now() / 1000 - parseInt(t, 10)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.`)
    .update(rawBody)
    .digest("hex");

  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Express usage:

app.post("/aigeon-hook", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyAigeonSignature(
    req.body,
    req.headers["x-aigeon-signature"] as string,
    process.env.AIGEON_WEBHOOK_SECRET!,
  );
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString());
  // … handle event
  res.sendStatus(200);
});

Use express.raw()not express.json() — so that req.body is the raw Buffer before JSON parsing. Running the body through a JSON parser first changes whitespace and breaks the HMAC.

Python example

import hashlib, hmac, time
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
WEBHOOK_SECRET = os.environ["AIGEON_WEBHOOK_SECRET"]

@app.post("/aigeon-hook")
async def handle_hook(request: Request):
    raw_body = await request.body()
    sig_header = request.headers.get("x-aigeon-signature", "")
    parts = dict(p.split("=", 1) for p in sig_header.split(",") if "=" in p)
    t = parts.get("t", "")
    v1 = parts.get("v1", "")

    if abs(time.time() - int(t)) > 300:
        raise HTTPException(403, "Timestamp too old")

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        f"{t}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, v1):
        raise HTTPException(403, "Invalid signature")

    event = json.loads(raw_body)
    # … handle event
    return {"ok": True}
© Aigeon.ai 2025
All Rights Reserved