Webhook flow
1
Create a webhook endpoint
Use
POST /v1/aggregators/{{aggregator_id}}/webhooks with the event types you want.2
Receive the event
Safepay sends events with identifying headers, the event timestamp, and a signed payload.
3
Verify and acknowledge
Verify the webhook signature using the timestamp and raw request body (see below), then parse and persist the event, and respond with
200 OK.Headers
Safepay includes headers for event ID, event type, aggregator ID, signature, and timestamp. Always read:X-SFPY-SIGNATUREX-SFPY-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. The webhook secret returned at creation time is base64-encoded; decode it to get the HMAC key.
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. See Webhooks delivery for a full walkthrough.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