Skip to main content

Documentation Index

Fetch the complete documentation index at: https://safepay.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Raast is asynchronous: customers approve or reject requests inside their banking apps, and Safepay pushes the resulting status to your backend via webhooks. Subscribe to these notifications to drive real-time UI updates and back-office automations.

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-SIGNATURE
  • X-SFPY-TIMESTAMP
Use the event ID header for idempotency and safe retries.

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. 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...).
func Verify(secretBase64 string, body []byte, providedSig, providedTS string, tolerance time.Duration) error {
	if tolerance > 0 {
		parsed, err := time.Parse(time.RFC3339Nano, providedTS)
		if err != nil {
			return errInvalidTimestamp
		}
		if delta := time.Since(parsed); delta > tolerance || delta < -tolerance {
			return errTimestampDrift
		}
	}

	decodedSecret, err := base64.StdEncoding.DecodeString(secretBase64)
	if err != nil {
		return errInvalidSecret
	}

	mac := hmac.New(sha256.New, decodedSecret)
	mac.Write([]byte(providedTS))
	mac.Write([]byte{'.'})
	mac.Write(body)

	expected := fmt.Sprintf("sha256=%s", hex.EncodeToString(mac.Sum(nil)))
	if !hmac.Equal([]byte(expected), []byte(providedSig)) {
		return errSignatureMismatch
	}

	return nil
}
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

Event catalog

EventCategoryDescription
payment.createdPaymentsA new payment request was created (initiated by customer).
payment.pending_authorizationPaymentsPayment is awaiting authorization (for example, Pay Later checks).
payment.authorizedPaymentsPayment has been authorized and funds are on hold.
payment.completedPaymentsPayment has been captured or charged successfully.
payment.settledPaymentsPayment funds have been settled to the merchant.
payment.refundedPaymentsPayment was fully refunded.
payment.refund_partialPaymentsPayment was partially refunded.
payment.rejectedPaymentsPayment was rejected before authorization.
payment.failedPaymentsPayment processing failed.
payment.reversedPaymentsPayment was reversed after completion.
payment.voidedPaymentsPayment authorization was voided.
settlement.createdSettlementsSettlement request was created.
settlement.processingSettlementsSettlement is currently processing.
settlement.completedSettlementsSettlement completed successfully.
settlement.failedSettlementsSettlement failed during processing.
settlement.on_holdSettlementsSettlement temporarily placed on hold.
settlement.reversedSettlementsSettlement was reversed.
refund.createdRefundsRefund request was created.
refund.completedRefundsRefund was successfully completed.
refund.failedRefundsRefund failed during processing.
refund.canceledRefundsRefund request was canceled.

Key endpoints

EndpointPurpose
POST /v1/aggregators/{{aggregator_id}}/webhooksCreate a webhook.
GET /v1/aggregators/{{aggregator_id}}/webhooksList webhook endpoints.
GET /v1/aggregators/{{aggregator_id}}/webhooks/{{webhook_id}}Read a webhook endpoint.
PUT /v1/aggregators/{{aggregator_id}}/webhooks/{{webhook_id}}Update a webhook endpoint.
DELETE /v1/aggregators/{{aggregator_id}}/webhooks/{{webhook_id}}Delete a webhook endpoint.
PUT /v1/aggregators/{{aggregator_id}}/webhooks/{{webhook_id}}/rotateRotate the webhook secret.
GET /v1/aggregators/{{aggregator_id}}/webhooks/{{endpoint}}/deliveriesList webhook deliveries.
GET /v1/aggregators/{{aggregator_id}}/webhooks/{{endpoint}}/deliveries/{{delivery}}Read a webhook delivery.

See also