The contract
Webhooks
Register a destination and niiko notifies you when something resolves — above all what stayed pending_approval, which by definition finishes after you hung up.
Last updated
Settings → Developers → Webhooks. Two events: action.completed and action.failed. The secret is shown once, when the destination is created.
The signature
Every delivery carries a Niiko-Signature header with a timestamp and an HMAC-SHA256 of the timestamp and the body, joined by a dot:
Niiko-Signature: t=<unix seconds>,v1=<hex>
v1 = HMAC-SHA256(secret, t + "." + body)The timestamp is inside what is signed: signing only the body leaves a signature valid forever. Verify that t is recent (±5 minutes) and that the signature matches, with a constant-time comparison.
// Node.js — verify a delivery
import { createHmac, timingSafeEqual } from "node:crypto";
const [t, v1] = /t=(\d+),v1=([0-9a-f]+)/.exec(req.headers["niiko-signature"]).slice(1);
const expected = createHmac("sha256", SECRET).update(t + "." + rawBody).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; // the timestamp is part of the proof
const ok = fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));Retries
If your server does not answer 2xx, delivery is retried with growing backoff (six attempts). A 4xx other than 408 or 429 is not retried: it means the destination is wrong, not busy. After many consecutive failures the destination switches itself off — and the dashboard says why. Each delivery carries the same id on every attempt: use it to deduplicate.