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 eventid.
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-timestampheaders. - 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.