Skip to main content
Safepay webhooks notify you when payments, refunds, or settlements change state. Use this guide to create webhook endpoints, verify signatures, and process retries safely.

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-SIGNATURE for the HMAC signature
  • X-SFPY-TIMESTAMP for the event timestamp
The delivery also includes headers for event ID, event type, and aggregator ID.

Signature verification

Safepay computes the webhook signature over timestamp + '.' + 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
Respond with 200 OK as soon as you persist the event to stop further retries.

Event catalog

See also