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. Store it in your secret manager and use it for signature verification.
curl --request POST "{{base_url}}/v1/aggregators/{{aggregator_id}}/webhooks" \
  --header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}" \
  --header "Content-Type: application/json" \
  --data '{
    "url": "https://api.example.com/webhooks/safepay",
    "description": "Raast webhook endpoint",
    "event_types": ["payment.created", "payment.completed", "refund.completed"],
    "enabled": true
  }'

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 signs the raw payload body. Use the signature header and timestamp header to verify authenticity.
1

Extract headers

Read X-SFPY-SIGNATURE and X-SFPY-TIMESTAMP from the incoming request.
2

Compute HMAC

Compute HMAC SHA-256 using the timestamp, a period separator, and the raw payload bytes: timestamp + '.' + payload.
3

Compare signatures

Compare the computed signature to the provided signature using a constant-time compare.
func Verify(secret, body []byte, providedSig, providedTS string, tolerance time.Duration) error {
	if tolerance > 0 {
		parsed, err := time.Parse(time.RFC3339Nano, providedTS)
		if err != nil {
			return err
		}
		if delta := time.Since(parsed); delta > tolerance || delta < -tolerance {
			return errTimestampDrift
		}
	}
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(providedTS))
	mac.Write([]byte{'.'})
	mac.Write(body)
	expected := fmt.Sprintf(headerFormat, hex.EncodeToString(mac.Sum(nil)))
	if !hmac.Equal([]byte(expected), []byte(providedSig)) {
		return errSignatureMismatch
	}
	return nil
}
Use the webhook payload object as the body argument when computing the signature.
headerFormat represents the signature format Safepay uses when building the X-SFPY-SIGNATURE header. Keep it consistent with the value you receive.

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

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.

See also