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.