Webhooks
Signature verification
Always verify webhook signatures against the raw request body before processing events.
Required verification inputs
- Raw HTTP request body bytes (exact, unparsed).
x-xpend-signaturefrom the delivery request.x-xpend-timestampfrom the delivery request.x-xpend-delivery-idfor logging and deduplication.- Endpoint signing secret returned at endpoint creation/rotation.
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
- 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.