Phoenix Pay
API Reference

Webhook API

API reference for webhook verification and payload format.

Webhook API

Phoenix Pay delivers outbound webhooks to your configured callback URL whenever a payment status changes. This page covers the API endpoints and payload format. For a comprehensive guide on implementation, see Webhooks.


Public Signing Key

GET /api/.well-known/signing-key

Returns the Ed25519 public key used to sign all outbound webhooks. This is a public endpoint -- no authentication required.

Fetch signing key
curl https://pay.phoenixverse.io/api/.well-known/signing-key
200 OK
{
  "algorithm": "Ed25519",
  "public_key": "MCowBQYDK2VwAyEAx1Fz..."
}
FieldTypeDescription
algorithmstringAlways "Ed25519"
public_keystringBase64-encoded Ed25519 public key

Cache this key in your application. It changes only during key rotation, which is an infrequent, coordinated operation.


Webhook Delivery

Phoenix Pay sends an HTTP POST request to your tenant's configured callback_url whenever a payment transitions to a new status.

HTTP Headers

HeaderDescription
Content-Typeapplication/json
X-Phoenix-Pay-SignatureBase64-encoded Ed25519 signature
X-Phoenix-Pay-TimestampUnix timestamp (seconds) when the signature was created

Payload Format

Webhook payload
{
  "event": "payment.status_changed",
  "payment_id": "01912e4a-7b3c-7def-8a90-1234567890ab",
  "reference_id": "order-12345",
  "type": "deposit",
  "status": "settled",
  "amount": "50.00",
  "received_amount": "50.00",
  "currency": "USDT",
  "psp": "nowpayments",
  "timestamp": "2026-03-11T12:45:00Z"
}

Payload Fields

FieldTypeDescription
eventstringEvent type. Currently always "payment.status_changed".
payment_idstringPhoenix Pay payment UUID (UUID v7)
reference_idstringYour original reference ID from the create request
typestringPayment type: "deposit" or "payout"
statusstringThe new payment status. See Payment Lifecycle for all values.
amountstring or nullThe originally requested amount (decimal string)
received_amountstring or nullThe actual amount received or sent (may differ for partial payments)
currencystringCurrency code (e.g., "USDT", "ETB")
pspstringThe PSP that processed the payment ("nowpayments" or "chapa")
timestampstringISO 8601 timestamp of when Phoenix Pay generated this webhook

Events

Currently, Phoenix Pay emits a single event type:

EventDescriptionTrigger
payment.status_changedA payment's status has changedAny status transition on a deposit or payout

This event fires for every status transition, including:

  • awaiting_payment -- deposit created, waiting for customer
  • processing -- payment detected, being processed
  • partial -- partial amount received (crypto)
  • settled -- payment completed successfully
  • failed -- payment failed
  • expired -- payment window expired
  • cancelled -- payment cancelled

You will typically receive multiple webhooks for a single payment as it progresses through states (e.g., awaiting_payment then processing then settled).


Signature Verification

Every webhook is signed using the Ed25519 private key. Verify by:

Extract the signature and timestamp

Read X-Phoenix-Pay-Signature and X-Phoenix-Pay-Timestamp from the request headers.

Reconstruct the signing input

Concatenate the timestamp and raw body with a period separator:

{timestamp}.{raw_json_body}

Verify the Ed25519 signature

Use the public key from /api/.well-known/signing-key to verify the base64-decoded signature against the signing input.

Always verify using the raw request body as a string. Do not parse the JSON and re-serialize it -- JSON key ordering and whitespace are not guaranteed to be preserved, which will cause verification to fail.

For complete code examples in Node.js, Python, Elixir, and Go, see the Webhook Verification Guide.


Retry Behavior

AttemptDelayDescription
1stImmediateInitial delivery attempt
2nd5 secondsFirst retry after failure
3rd30 secondsSecond retry
4th (final)3 minutesFinal retry

A delivery is considered successful if your endpoint responds with any 2xx HTTP status code within 10 seconds.

A delivery is considered failed if:

  • Your endpoint returns a non-2xx status code
  • Your endpoint does not respond within 10 seconds
  • The connection cannot be established

After all retry attempts are exhausted, the webhook delivery is abandoned. The payment's callback_delivered field remains false. You can use the polling endpoints to check for missed updates.


Configuring Your Callback URL

Set your webhook callback URL via the tenant configuration endpoint:

Set callback URL
curl -X PUT https://pay.phoenixverse.io/api/config \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://myapp.example.com/webhooks/phoenix-pay"
  }'

Your callback URL must use HTTPS. Phoenix Pay will not deliver webhooks to HTTP endpoints in production.

Requirements for Your Endpoint

  • Must be publicly accessible over HTTPS
  • Must respond with a 2xx status code within 10 seconds
  • Must accept POST requests with Content-Type: application/json
  • Should be idempotent (may receive the same event more than once)

On this page