Skip to navigation

Signature Verification

View as Markdown

Every webhook request includes headers that identify and sign the event.

HeaderDescription
X-FormantAI-Event-IdUnique event delivery ID.
X-FormantAI-Event-TypeEvent type such as call.completed.
X-FormantAI-SignatureHMAC-SHA256 signature, prefixed with sha256=.
X-FormantAI-TimestampUnix timestamp when the request was sent.
Content-Typeapplication/json.

How signatures are computed

FormantAI signs the raw request body using the webhook target secret.

X-FormantAI-Signature = sha256=HMAC_SHA256(raw_request_body, webhook_secret)

Always verify against the raw body bytes before parsing JSON.

Node.js / Express

const crypto = require("crypto");
const express = require("express");
const app = express();
const WEBHOOK_SECRET = process.env.FORMANT_WEBHOOK_SECRET;
app.post(
"/webhooks/formant",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.header("X-FormantAI-Signature") || "";
const expected = "sha256=" + crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
const valid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!valid) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
console.log(event.event_type, event.event_id);
res.status(204).send();
}
);

Python / FastAPI

import hashlib
import hmac
import json
import os
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = os.environ["FORMANT_WEBHOOK_SECRET"]
@app.post("/webhooks/formant")
async def formant_webhook(
request: Request,
x_formantai_signature: str = Header(default=""),
):
body = await request.body()
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(x_formantai_signature, expected):
raise HTTPException(status_code=401, detail="Invalid signature")
event = json.loads(body)
return {"received": True, "event_id": event["event_id"]}

Best practices

  • Reject missing signatures.
  • Use constant-time comparison.
  • Verify before parsing JSON or doing business logic.
  • Store event_id and ignore duplicates.
  • Keep webhook secrets out of logs and repositories.
  • Rotate secrets if they are exposed.