Verification Examples

Below are minimal implementations that verify the signature, validate timestamp drift, and parse the event.

Node.js (Express)

Read raw body with express.raw({ type: "application/json" }).

import crypto from "crypto";
import express from "express";

const app = express();

app.post("/webhooks/fortisx",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const secret = process.env.FORTISX_WEBHOOK_SECRET || "";
    const sigHeader = req.header("X-Fortisx-Signature") || "";
    const eventId = req.header("X-Fortisx-Event-Id") || "";

    const parts = Object.fromEntries(sigHeader.split(",").map(p => p.trim().split("=")));
    const t = Number(parts.t);
    const v1 = String(parts.v1 || "");

    const now = Math.floor(Date.now() / 1000);
    if (!t || Math.abs(now - t) > 300) return res.status(400).send("invalid timestamp");

    const raw = req.body;
    const expected = crypto.createHmac("sha256", secret).update(raw).digest("hex");

    const valid = v1.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(v1, "utf8"), Buffer.from(expected, "utf8"));
    if (!valid) return res.status(400).send("invalid signature");

    const event = JSON.parse(raw.toString("utf8"));
    res.sendStatus(200);
  }
);

app.listen(3000);

PHP

Read the body from php://input and compute HMAC on the raw bytes.

Python (Flask)

Get raw bytes via request.get_data() and compare with hmac.compare_digest.

Go (net/http)

Read raw bytes with io.ReadAll(r.Body) and compare using subtle.ConstantTimeCompare.

Last updated