Submit Sell Payout (Phase 2b)
Phase 2 of the two-phase off-ramp (sell) flow. Submit the customer's beneficiary details to trigger the fiat payout. The system obtains an FX quote (if cross-currency), maps the beneficiary fields to the payout provider, and initiates the bank transfer.
On success, the ramp intent automatically transitions to completed.
https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payoutRequest
Headers
| Header | Description |
|---|---|
Content-Type REQUIRED | application/json |
x-request-id OPTIONAL | Custom request ID for tracing |
This endpoint does not require authentication headers.
Body Parameters
| Parameter | Type | Description |
|---|---|---|
intentId REQUIRED | string | Ramp intent ID |
cryptoDepositId REQUIRED | string | Deposit ID from Phase 1 response |
fiatCurrency REQUIRED | string | Target fiat currency (e.g. USD) |
payoutType OPTIONAL | string | Payout method (default: bank_transfer) |
country OPTIONAL | string | ISO country code (default: US) |
selectedTsp OPTIONAL | string | Payout provider from Payout Preview response (e.g. zenpay_routing). When omitted, the system selects the best provider automatically. |
beneficiaryDetails REQUIRED | object | Dynamic fields from Payout Preview |
beneficiaryDetails Object
The fields in this object are dynamic — they come from the requiredFields and optionalFields returned by the Payout Preview endpoint. Always call payout-preview first to discover which fields are needed for the target currency and country.
Common fields include:
| Parameter | Type | Description |
|---|---|---|
receiverFirstName OPTIONAL | string | Beneficiary first name |
receiverLastName OPTIONAL | string | Beneficiary last name |
receiverAccountNumber OPTIONAL | string | Bank account number |
receiverBankName OPTIONAL | string | Bank name |
receiverBankCode OPTIONAL | string | SWIFT/BIC code |
receiverCountry OPTIONAL | string | ISO country code |
receiverAddressLine1 OPTIONAL | string | Street address |
receiverCity OPTIONAL | string | City |
receiverState OPTIONAL | string | State/province |
receiverPinCode OPTIONAL | string | Postal/ZIP code |
receiverEmail OPTIONAL | string | Email address |
receiverPhone OPTIONAL | string | Phone number |
remittancePurpose OPTIONAL | string | Purpose of remittance (e.g. PAYP001 - Family Support) |
sourceOfFund OPTIONAL | string | Source of funds (e.g. PAYF001 - Salary) |
relationship OPTIONAL | string | Relationship to sender (e.g. PAYR001 - Self) |
The required fields change based on the destination currency, country, and payout type. Always use the payout-preview response to determine which fields to collect. Do not hard-code field assumptions.
Response
Success (200 OK)
{
"success": true,
"data": {
"payoutId": "po_abc123def456",
"status": "processing",
"amount": 19.78,
"currency": "USD",
"intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8"
},
"message": "Sell payout submitted"
}
Response Fields
| Parameter | Type | Description |
|---|---|---|
payoutId OPTIONAL | string | Payout tracking ID |
status OPTIONAL | string | Payout status (processing) |
amount OPTIONAL | number | Fiat payout amount |
currency OPTIONAL | string | Fiat currency |
intentId OPTIONAL | string | The ramp intent ID |
Automatic Behaviors
| Condition | Action |
|---|---|
| Received crypto matches expected (±5%) | Uses locked fiat amount from quote |
| Received crypto differs >5% from expected | Recalculates fiat amount proportionally with fee scaling |
| Same-currency payout (e.g. USD → USD) | Skips FX quotation step |
| Cross-currency payout | Obtains live FX quote from provider |
| Payout succeeds | Intent auto-transitions to completed |
| Payout fails | Intent receives payout_failed metadata |
Error Responses
| Code | HTTP | Message |
|---|---|---|
| BAD_REQUEST | 400 | Deposit not confirmed, invalid amount, or FX quotation failed |
| NOT_FOUND | 404 | Intent or deposit not found |
| SELL_PAYOUT_FAILED | 500 | Payout submission failed |
Examples
- cURL
- JavaScript
- Python
curl -X POST https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout \
-H "Content-Type: application/json" \
-d '{
"intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
"cryptoDepositId": "d4e5f6a7-b8c9-1234-5678-abcdef012345",
"fiatCurrency": "USD",
"country": "US",
"selectedTsp": "zenpay_routing",
"beneficiaryDetails": {
"receiverFirstName": "John",
"receiverLastName": "Doe",
"receiverCountry": "US",
"receiverAccountNumber": "123456789",
"receiverBankName": "Bank of America",
"receiverBankCode": "BOFAUS3N",
"receiverAddressLine1": "123 Main St",
"receiverCity": "New York",
"receiverState": "NY",
"receiverPinCode": "10001",
"remittancePurpose": "PAYP001 - Family Support",
"sourceOfFund": "PAYF001 - Salary",
"relationship": "PAYR001 - Self"
}
}'
const response = await fetch(
'https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
intentId: 'ri_1710345678000_a1b2c3d4e5f6g7h8',
cryptoDepositId: 'd4e5f6a7-b8c9-1234-5678-abcdef012345',
fiatCurrency: 'USD',
country: 'US',
selectedTsp: 'zenpay_routing',
beneficiaryDetails: {
receiverFirstName: 'John',
receiverLastName: 'Doe',
receiverCountry: 'US',
receiverAccountNumber: '123456789',
receiverBankName: 'Bank of America',
receiverBankCode: 'BOFAUS3N',
receiverAddressLine1: '123 Main St',
receiverCity: 'New York',
receiverState: 'NY',
receiverPinCode: '10001',
remittancePurpose: 'PAYP001 - Family Support',
sourceOfFund: 'PAYF001 - Salary',
relationship: 'PAYR001 - Self',
},
}),
}
);
const { data } = await response.json();
console.log(`Payout ID: ${data.payoutId}`);
console.log(`Status: ${data.status}`);
console.log(`Amount: ${data.amount} ${data.currency}`);
// The intent is now completed — redirect user
window.location.href = '/success';
import requests
response = requests.post(
"https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout",
json={
"intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
"cryptoDepositId": "d4e5f6a7-b8c9-1234-5678-abcdef012345",
"fiatCurrency": "USD",
"country": "US",
"selectedTsp": "zenpay_routing",
"beneficiaryDetails": {
"receiverFirstName": "John",
"receiverLastName": "Doe",
"receiverCountry": "US",
"receiverAccountNumber": "123456789",
"receiverBankName": "Bank of America",
"receiverBankCode": "BOFAUS3N",
"receiverAddressLine1": "123 Main St",
"receiverCity": "New York",
"receiverState": "NY",
"receiverPinCode": "10001",
"remittancePurpose": "PAYP001 - Family Support",
"sourceOfFund": "PAYF001 - Salary",
"relationship": "PAYR001 - Self",
},
},
)
data = response.json()["data"]
print(f"Payout ID: {data['payoutId']}")
print(f"Status: {data['status']}")
print(f"Amount: {data['amount']} {data['currency']}")
Complete Two-Phase Flow
Phase 1: Deposit Phase 2: Payout
───────────────── ─────── ───────────────
1. POST sell/checkout 4. GET payout-preview
→ Get deposit address → Get required fields
2. Customer sends crypto 5. Render beneficiary form
3. Wait for confirmation 6. POST sell/payout
(deposit → confirmed) → Trigger fiat transfer
→ Intent → completed
Next Steps
- Get Payout Preview — Discover required fields (Phase 2a)
- Create Sell Deposit — Generate deposit address (Phase 1)
- Transaction Status — Poll transaction progress
- Off-Ramp Flow — Complete sell integration guide