Webhooks

Signature verification

Verify HMAC signatures on the raw request body before processing any webhook event.

Verify every delivery before parsing JSON. Use the signing_secret from endpoint creation or rotation (see Register an endpoint).

Delivery headers

  • x-xpend-signature — HMAC-SHA256 hex digest to verify.
  • x-xpend-timestamp — Unix seconds prepended to the body for signing.
  • x-xpend-delivery-id — unique delivery id; use for logging and optional deduplication alongside event id.

Signed payload format: {timestamp}.{rawBody} using your endpoint secret as the HMAC key.

Node.js example

import crypto from "node:crypto";

function verifyXpendSignature(params: {
  rawBody: string;
  signatureHeader: string;
  timestamp: string;
  secret: string;
}) {
  const expected = crypto
    .createHmac("sha256", params.secret)
    .update(`${params.timestamp}.${params.rawBody}`, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(params.signatureHeader, "hex"),
  );
}

Best practices

  • Read the raw request body bytes before JSON parsing.
  • Reject requests with missing or invalid x-xpend-signature / x-xpend-timestamp headers.
  • Keep one active secret per webhook endpoint in your secret manager.
  • Rotate with /v1/webhooks/endpoints/{endpointId}/rotate-secret.
  • During rotation rollout, deploy secret updates before accepting new deliveries.

Common failure mode

Parsing JSON before verification changes the body representation. Verify first using the raw body, then parse.