Developer
WebhookSigning & verification
Verify ServiceReserve webhook deliveries with the HMAC-SHA256 signature computed from your endpoint's signing secret.
Updated Sep 30, 2026
A webhook-kind endpoint with a Signing secret gets an HMAC-SHA256
signature of the raw request body on every delivery. Verify the
signature before you trust any field in the payload. api and
telegram endpoints are never signed.
The signature header
X-ServiceReserve-Signature: sha256=<hex digest>The digest is computed as:
HMAC_SHA256(key = your-signing-secret, message = raw-request-body)The result is lowercase hex. Compute it over the raw bytes of the request body, before any JSON parsing or whitespace normalization. Nothing else is signed: there is no timestamp or other header in the signed string.
Verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyServiceReserveSignature(
rawBody: string | Buffer,
signatureHeader: string | undefined,
secret: string,
): boolean {
if (!signatureHeader?.startsWith("sha256=")) return false;
const provided = Buffer.from(signatureHeader.slice("sha256=".length), "utf8");
const expected = Buffer.from(
createHmac("sha256", secret).update(rawBody).digest("hex"),
"utf8",
);
// timingSafeEqual throws on unequal lengths, so check first.
if (provided.length !== expected.length) return false;
return timingSafeEqual(provided, expected);
}Replay protection
The signature covers only the body. A captured request stays valid if someone replays it. To guard against that:
- Dedupe on
data.request_id. You need this anyway, because retries can deliver the same event more than once. - Optionally reject deliveries whose
sent_atis older than you expect.sent_atis set when the delivery is queued and stays the same across retries, so allow for the retry window.
What if I didn't set a secret?
With no secret, the request still arrives, but the
X-ServiceReserve-Signature header is missing and the body is
unauthenticated. Set the secret to a long random value (32+ characters
from a CSPRNG) and store it on your server.
Rotation
Change the secret on the Webhooks page and click Save webhooks. New events are signed with the new secret. Each delivery keeps the endpoint settings, including the secret, from the moment it was queued. So deliveries that are already queued or waiting to retry are still signed with the old secret.
To rotate without dropping events, accept both secrets in your verifier for at least an hour, then remove the old one.
Related