All pages

Signing keys

Coming

Every 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

HeaderWhat it holds
webhook-id
The event's id. Retries of the same event keep it, so use it to drop repeats.
webhook-timestamp
When this attempt was signed, in Unix seconds. It changes on each retry.
webhook-signature
v1, then the signature. For 24 hours after you roll the secret it holds two, separated by a space: one from the new secret, one from the old.
Delivery headersHTTP
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

Node, with a libraryJavaScript
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

  1. Take the body exactly as it arrived. Don't parse it and turn it back into text first.
  2. Join webhook-id, webhook-timestamp and the body with dots: ${id}.${timestamp}.${body}.
  3. Sign that with HMAC-SHA256. The key is the secret's bytes: the base64 after whsec_, decoded.
  4. Base64 the result and compare it, in constant time, with each v1, entry in webhook-signature. One match is enough.
  5. Refuse a timestamp more than five minutes away from your clock.
Node, by handJavaScript
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.