Create a webhook endpoint
1
Choose the events you need
Subscribe to the specific event types your integration depends on, such as
payment.completed or refund.completed.2
Create the webhook
Call
POST /v1/aggregators/{{aggregator_id}}/webhooks with the URL and event list.3
Store the webhook secret
Safepay returns a webhook secret (base64-encoded). Store it in your secret manager. Base64-decode it when computing the HMAC signing key.
Webhook headers
Safepay includes headers that identify the event and allow you to verify authenticity. Always read:X-SFPY-SIGNATUREfor the HMAC signatureX-SFPY-TIMESTAMPfor the event timestamp
Signature verification
Safepay computes the webhook signature overtimestamp + '.' + raw_body: the X-SFPY-TIMESTAMP value, a literal period, then the raw HTTP request body bytes. Use the signature header and timestamp header to verify authenticity before parsing JSON.
1
Extract headers
Read
X-SFPY-SIGNATURE and X-SFPY-TIMESTAMP from the incoming request.2
Build the signing payload
Read the raw HTTP request body bytes exactly as received. Build the signing payload as
timestamp + '.' + raw_body. Use the X-SFPY-TIMESTAMP header value exactly as received, without reformatting it.Do not parse, prettify, re-serialize, or otherwise modify the request body before verification. Any modification to the body bytes will cause signature verification to fail.
3
Compute HMAC
Base64-decode the webhook secret and use the decoded bytes as the HMAC-SHA256 key. Compute the HMAC over the full signing payload (
timestamp + '.' + raw_body), not the raw body alone.4
Format and compare signatures
Format the expected signature as
sha256= plus a lowercase hexadecimal digest, and compare it to the X-SFPY-SIGNATURE header using a constant-time compare (for example sha256=abcdef...).Pass the raw request body bytes to
Verify as body. Only after verification succeeds should you parse the JSON and process the event.Retry behavior
Safepay retries failed deliveries up to 5 attempts using exponential backoff:- Attempt 1: 1 second
- Attempt 2: 2 seconds
- Attempt 3: 4 seconds
- Attempt 4: 8 seconds
- Attempt 5: 16 seconds
200 OK as soon as you persist the event to stop further retries.