Skip to main content
Safepay webhooks notify you asynchronously when Raast payments, refunds, or settlements change state. This guide describes the delivery contract. See Webhook events and payloads for the event envelope and JSON examples.

Create a webhook endpoint

1

Choose the events you need

Subscribe to exact event names, such as payment.completed, or use a trailing wildcard to subscribe to a category, such as payment.*, refund.*, or settlement.*. A wildcard is supported only at the end of a filter.
2

Create the webhook

Call POST /v1/aggregators/{{aggregator_id}}/webhooks with your URL, event filters, and whether the endpoint is enabled.
3

Store the webhook secret

The response contains a base64-encoded signing secret. Store it in a secret manager and base64-decode it before using the resulting bytes as the HMAC key.
Treat every secret field as sensitive signing material. Do not expose it in client-side code, logs, support tickets, or source control.

HTTP request contract

Safepay sends an HTTP POST with Content-Type: application/json. Header names are case-insensitive according to HTTP, but Safepay emits the following names: The JSON body is the raw event envelope. The values in X-SFPY-EVENT-ID, X-SFPY-EVENT-TYPE, and X-SFPY-AGGREGATOR-ID correspond to the body’s identifier, webhook_event_type, and aggregator_id fields.

Verify the signature

Safepay signs each individual delivery attempt. The signed message is the exact X-SFPY-TIMESTAMP value, a literal period (.), and the raw HTTP request body bytes:
1

Read the request without changing it

Capture X-SFPY-SIGNATURE, X-SFPY-TIMESTAMP, and the raw request body. Do not parse or re-serialize the body before verification.
2

Check timestamp freshness

Parse the timestamp as RFC 3339 with optional fractional seconds. Reject timestamps outside a tolerance appropriate for your environment; five minutes is a reasonable starting point. Safepay does not enforce this tolerance on your behalf.
3

Compute the expected signature

Base64-decode the stored webhook secret, calculate HMAC-SHA256 over timestamp + '.' + raw_body, then prefix the lowercase hexadecimal digest with sha256=.
4

Compare in constant time

Compare the expected signature with X-SFPY-SIGNATURE using a constant-time comparison. Parse the JSON only after verification succeeds.
Whitespace and property order are part of the signed bytes. Parsing, prettifying, or re-serializing the body before verification will invalidate the signature.

Acknowledge and process safely

Return any 2xx response after you have durably stored the event. Safepay treats the delivery as successful and does not retry it. A 200 OK response is recommended. Do not wait for downstream business logic before acknowledging. A safe handler performs these operations in order:
  1. Read the raw body and required headers.
  2. Verify the timestamp and signature.
  3. Atomically persist the event using X-SFPY-EVENT-ID as a unique key.
  4. Return 200 OK.
  5. Process the stored event asynchronously.
Safepay does not currently publish a contractual response timeout. Return a 2xx promptly and do not depend on a long-running connection remaining open.

Delivery and retry semantics

Webhook delivery is at least once. Your endpoint can receive the same event more than once, for example when Safepay sends a request successfully but does not receive your acknowledgement. Safepay does not guarantee that separate events arrive in creation order, especially when one event is being retried. Always make processing idempotent and compare the resource’s current state before applying a transition. Safepay makes one initial delivery attempt. Transport errors and 5xx responses are retried up to five more times using exponential backoff: Any 2xx response completes the delivery. A terminal non-2xx response below 500, including a 4xx, is not retried and moves the delivery to WD_DEAD. After the final failed retry, the delivery also moves to WD_DEAD.
Automatic redirects should not be part of your integration contract. Register the final HTTPS URL and return a 2xx response directly from it.

Delivery statuses

Use the List Webhook Delivery endpoint to filter by status, event type, event ID, or delivery ID. Use Find Webhook Delivery to inspect the stored payload and last error. There is currently no public API for manually replaying a WD_DEAD delivery. Reconcile the resource through the corresponding Payments, Refunds, or Settlement API and process it idempotently in your system.

Rotate the signing secret

Call PUT /v1/aggregators/{{aggregator_id}}/webhooks/{{webhook_id}}/rotate to generate a new base64-encoded secret. Rotation atomically replaces the stored secret; Safepay does not maintain a server-side overlap period with both secrets. New delivery attempts, including retries of older events, load the current secret and are signed with it. A request already in progress at the moment rotation completes may have been signed with the previous secret. To rotate without rejecting an in-flight request:
  1. Retain the previous secret in your verifier.
  2. Call the rotation endpoint and securely store the returned secret.
  3. Try the new secret first and the previous secret second for a short operational grace period.
  4. Remove the previous secret after in-flight requests have cleared.
Each attempt receives a fresh X-SFPY-TIMESTAMP and signature. The webhook request does not include a secret-version header.

Endpoint changes

  • Disabling an endpoint causes pending deliveries selected afterward to become WD_DEAD.
  • Deleting an endpoint also deletes its stored delivery records.
  • Updating event filters affects future events; it does not create deliveries for past events.

Event catalog

Payment events

Payouts use the payment.* event family because payouts are represented as payments with subject.payment.type set to PAYOUT or SETTLEMENT_PAYOUT. There is no separate payout.* event family.

Refund events

Payment-level payment.refunded and payment.refund_partial events describe the resulting payment state. Refund-level refund.* events describe the lifecycle of an individual refund. Use subject.payment.refunds to identify the refund concerned.

Settlement events

See also