Skip to content

Webhook signature

Every webhook delivery is signed so you can verify that the request came from the Gowd platform and that the body was not modified in transit.

Each delivery carries the header:

X-Signature-SHA256: sha256=<hex digest>

The value is the string sha256= followed by the hex-encoded HMAC-SHA256 of the raw request body, computed with your webhook secret. Note the sha256= prefix: it is part of the header value, so strip it before comparing digests.

Your webhook secret is different in each environment. You can read your webhook configuration with GET /order/v1/webhook and rotate the secret with POST /order/v1/webhook/rotate-secret.

Compute the HMAC over the raw body exactly as received, before any JSON parsing or re-serialization, and compare it with a constant-time comparison:

import crypto from "crypto";
function isValidSignature(
rawBody: Buffer,
signatureHeader: string,
secret: string,
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const received = signatureHeader.replace(/^sha256=/, "");
return (
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
);
}

Reject deliveries whose signature does not match and respond with a 4xx status. A 2xx response acknowledges the delivery; anything else is retried.