Phoenix Pay
API Reference

Withdrawals (Payouts)

API reference for creating and managing outbound payments.

The API routes use /payouts but the type field in responses is "withdrawal". This reflects the internal model where withdrawals are the counterpart to deposits.


Create Withdrawal

POST /api/payouts

Creates a new withdrawal intent to send funds to a recipient. The response tells your application what to do next — see Actions.

Request Body

FieldTypeRequiredDescription
reference_idstringYesYour unique identifier for this payout. Must be unique within your tenant.
amountinteger or stringYesAmount in cents. 500000 = 5000.00. Decimal strings like "5000.00" are also accepted and auto-converted.
currencystringYesCurrency code (e.g., "ETB").
channelstringYesWithdrawal channel. See Channels.
methodstringNoSub-method (e.g., "telebirr", "cbe").
fieldsobjectNoRecipient details required by the channel (e.g., account number, phone).

Channels

ChannelPSPFlowWhen to Use
direct_payoutChapaSingle-step bank or mobile money transferFiat payouts (ETB) to bank accounts or mobile wallets
crypto_payoutNOWPaymentsCrypto payout with optional verification stepCrypto payouts to wallet addresses

Examples

Payout to bank account
TIMESTAMP=$(date +%s)
BODY='{"reference_id":"payout-78901","amount":500000,"currency":"ETB","channel":"direct_payout","fields":{"account_number":"1000123456789","account_name":"Abebe Kebede"}}'

curl -X POST https://pay.phoenixverse.io/api/payouts \
  -H "Content-Type: application/json" \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: <signature-of-$TIMESTAMP.$BODY>" \
  -d "$BODY"
201 Created
{
  "intent_id": "01912e4c-9d5e-7def-ac12-3456789012cd",
  "action": "await",
  "message": "Transfer is being processed"
}
Payout to Telebirr
TIMESTAMP=$(date +%s)
BODY='{"reference_id":"payout-78902","amount":250000,"currency":"ETB","channel":"direct_payout","method":"telebirr","fields":{"account_number":"+251911234567","account_name":"Abebe Kebede"}}'

curl -X POST https://pay.phoenixverse.io/api/payouts \
  -H "Content-Type: application/json" \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: <signature-of-$TIMESTAMP.$BODY>" \
  -d "$BODY"
201 Created
{
  "intent_id": "01912e4d-ae6f-7def-bd23-4567890123de",
  "action": "await",
  "message": "Transfer is being processed"
}
Payout to crypto wallet
TIMESTAMP=$(date +%s)
BODY='{"reference_id":"payout-78903","amount":2500,"currency":"USDT","channel":"crypto_payout","fields":{"address":"TXrk4d5x7Bj3e6g7Y8Zw9AbCdEf1234567"}}'

curl -X POST https://pay.phoenixverse.io/api/payouts \
  -H "Content-Type: application/json" \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: <signature-of-$TIMESTAMP.$BODY>" \
  -d "$BODY"
201 Created
{
  "intent_id": "01912e4e-be7f-7def-ce34-5678901234ef",
  "action": "await",
  "message": "Payout is being processed"
}

The action: "await" response means the PSP is processing the transfer. Wait for a webhook to confirm completion.

For NOWPayments crypto payouts, the create response may also include:

FieldDescription
batch_withdrawal_idNOWPayments payout batch id
payout_statusRaw NOWPayments payout status
payout_currencyNetwork-specific payout currency code
fee_amountEstimated payout fee in cents
fee_currencyFee currency code
address_validationWhether NOWPayments accepted the destination address during validation

If NOWPayments rejects the wallet address during validation, Phoenix Pay returns 422 and does not create the payout.

If the NOWPayments account is still configured for manual merchant verification, Phoenix Pay returns 422 with a provider error instead of exposing that verification step to your app.

Errors

StatusErrorCause
400"missing required parameter: reference_id"A required field is missing or empty
400"missing required parameter: channel"The channel field is required
409"duplicate_reference"The reference_id already exists for your tenant. Response includes intent_id.
422PSP-specific errorThe PSP rejected the request.

Get Withdrawal

GET /api/payouts/:id

Retrieve a withdrawal by its Phoenix Pay UUID.

Get withdrawal by ID
curl https://pay.phoenixverse.io/api/payouts/01912e4c-9d5e-7def-ac12-3456789012cd \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $(date +%s)" \
  -H "X-Signature: <signature>"
200 OK
{
  "id": "01912e4c-9d5e-7def-ac12-3456789012cd",
  "reference_id": "payout-78901",
  "display_ref": "WDR-20260311-D4E5F6",
  "type": "withdrawal",
  "status": "completed",
  "amount": 500000,
  "currency": "ETB",
  "channel": "direct_payout",
  "payment_method": null,
  "error_code": null,
  "error_detail": null,
  "psp": "chapa",
  "psp_external_id": "chapa_tr_12345",
  "inserted_at": "2026-03-11T12:30:00.000000Z",
  "updated_at": "2026-03-11T12:45:00.000000Z"
}

Response Fields

FieldTypeDescription
idstring (UUID v7)Unique intent identifier
reference_idstringYour reference ID from the create request
display_refstringAuto-generated human-readable reference
typestringAlways "withdrawal"
statusstringCurrent status: created, pending, completed, failed, expired
amountintegerAmount in cents
currencystringCurrency code
channelstringThe channel used (e.g., "direct_payout")
payment_methodstring or nullSub-method if specified
error_codestring or nullError code if failed
error_detailstring or nullHuman-readable failure reason
pspstring or nullPSP handling the latest attempt
psp_external_idstring or nullPSP's own transaction ID
inserted_atstring (ISO 8601)When the intent was created
updated_atstring (ISO 8601)When the intent was last updated

For NOWPayments payouts, psp_external_id is the payout batch identifier returned by NOWPayments.

Errors

StatusErrorCause
404"not_found"No withdrawal with this ID exists for your tenant

Get Withdrawal by Reference

GET /api/payouts/ref/:reference_id

Retrieve a withdrawal using your original reference_id.

Get withdrawal by reference ID
curl https://pay.phoenixverse.io/api/payouts/ref/payout-78901 \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $(date +%s)" \
  -H "X-Signature: <signature>"

The response format is identical to Get Withdrawal.


List Withdrawals

GET /api/payouts

Retrieve a paginated list of withdrawals for your tenant.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger25Results per page (max 100)
statusstringFilter by status: created, pending, completed, failed, expired
currencystringFilter by currency code
fromstring (date)Filter by creation date on or after (ISO 8601 date)
tostring (date)Filter by creation date on or before
searchstringSearch by reference ID (partial match)

Both from and to must be provided for date filtering to take effect.

List all withdrawals
curl "https://pay.phoenixverse.io/api/payouts?page=1&limit=10" \
  -H "X-Key-Id: your-key-id" \
  -H "X-Timestamp: $(date +%s)" \
  -H "X-Signature: <signature>"

Response

200 OK
{
  "data": [
    {
      "id": "01912e4c-9d5e-7def-ac12-3456789012cd",
      "reference_id": "payout-78901",
      "display_ref": "WDR-20260311-D4E5F6",
      "type": "withdrawal",
      "status": "completed",
      "amount": 500000,
      "currency": "ETB",
      "channel": "direct_payout",
      "payment_method": null,
      "error_code": null,
      "error_detail": null,
      "psp": "chapa",
      "psp_external_id": "chapa_tr_12345",
      "inserted_at": "2026-03-11T12:30:00.000000Z",
      "updated_at": "2026-03-11T12:45:00.000000Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 10,
    "total": 1,
    "total_pages": 1,
    "has_next": false,
    "has_prev": false
  }
}

Results are sorted by creation date, newest first.