Developer

Webhook

Signing & 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_at is older than you expect. sent_at is 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