Developer

Signature verification

Prove a delivery came from Trabalance and was not altered.

Every delivery carries Trabalance-Signature: t=<unix seconds>,v1=<hex> where

v1 = HMAC-SHA256( secret, "<t>." + rawBody )

Verify with the endpoint's whsec_… secret (business endpoints) or your app's webhook signing secret (OAuth apps). Reject if the timestamp is more than 5 minutes old — that closes replay.

⚠️Use the raw body

Compute over the exact bytes received. Parsing and re-serialising JSON reorders keys and breaks the signature.

javascript
const crypto = require("crypto");

function verify(secret, rawBody, header, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex"), b = Buffer.from(parts.v1 || "", "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: keep the raw body
app.post("/trabalance/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(process.env.TRABALANCE_WEBHOOK_SECRET, req.body.toString("utf8"), req.get("Trabalance-Signature"))) {
  return res.status(401).end();
}
res.status(200).end();                   // answer first …
const event = JSON.parse(req.body);      // … then do the work
});

The generated SDKs include verifyWebhookSignature / verify_webhook_signature with exactly this logic.