Signing keys
ComingEvery delivery is signed, so your server can tell that it came from Diamra and that nobody changed it on the way. Diamra follows Standard Webhooks (opens in a new tab), so any Standard Webhooks library can check a delivery for you.
Each endpoint has its own signing secret, whsec_ followed by base64. Find it on the endpoint's page in the console. Keep it as you keep an API key: on your server, out of your code. A server behind two endpoints checks each delivery with that endpoint's secret.
Headers
POST https://pos.example.com/diamra/webhooks
content-type: application/json
webhook-id: 7b9e2f1c-3a44-4d2e-9f0b-0c1a2b3c4d5e
webhook-timestamp: 1793210652
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=With a library
import express from "express";
import { Webhook } from "standardwebhooks";
const app = express();
const wh = new Webhook(process.env.DIAMRA_WEBHOOK_SECRET); // whsec_...
// express.raw keeps the body exactly as it was signed.
app.post("/diamra/webhooks", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers); // throws on a bad signature or an old timestamp
} catch {
return res.status(400).end();
}
// event.id, event.type, event.data
res.status(200).end();
});By hand
- Take the body exactly as it arrived. Don't parse it and turn it back into text first.
- Join
webhook-id,webhook-timestampand the body with dots:${id}.${timestamp}.${body}. - Sign that with HMAC-SHA256. The key is the secret's bytes: the base64 after
whsec_, decoded. - Base64 the result and compare it, in constant time, with each
v1,entry inwebhook-signature. One match is enough. - Refuse a timestamp more than five minutes away from your clock.
import crypto from "node:crypto";
function verify(secret, headers, rawBody) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`).digest();
return headers["webhook-signature"].split(" ").some((entry) => {
const [version, signature] = entry.split(",");
if (version !== "v1") return false;
const given = Buffer.from(signature, "base64");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}Roll a secret
Open the endpoint in the console to roll its secret. The old secret keeps signing next to the new one for 24 hours, so put the new secret on your server within a day.