Skip to content

Signatures

Every webhook delivery is signed with HMAC-SHA256 (RFC 2104), in the same timestamped format Stripe's webhooks use: a ShotPay-Signature header of t={unix},v1={hex}, where v1 is the HMAC of {t}.{raw request body} keyed with your endpoint's signing secret.

Verify against the raw bytes before parsing, compare in constant time, and reject a timestamp more than 300 seconds from your own clock:

[$t, $v1] = [/* parsed from the ShotPay-Signature header */];

$expected = hash_hmac('sha256', $t.'.'.$rawBody, $endpointSecret);

if (! hash_equals($expected, $v1) || abs(time() - (int) $t) > 300) {
    abort(400);
}

Each endpoint has its own secret, shown once when the endpoint is created or its secret is rotated. Endpoints belong to a mode, so a sandbox receiver and a live one never share a secret.