Off-Ramp: Crypto → Fiat
Step-by-step guide to integrating crypto-to-fiat off-ramp transactions.
The off-ramp flow lets your users send cryptocurrency and receive fiat payouts to their bank account or mobile money wallet. IvoryPay handles the crypto collection, conversion, and payout.
How it works
1. Your server calls POST /v1/offramp with user, account, and transaction details
2. IvoryPay returns a crypto wallet address and amount for the user to send to
3. The user sends crypto to the provided address
4. IvoryPay detects the deposit → webhook: offramp.cryptoPaymentReceived
5. IvoryPay converts crypto to fiat and initiates the payout
6. Payout completes → webhook: offramp.success (or offramp.failed)Mobile money support
For currencies other than NGN (e.g. UGX, KES), payouts are delivered via mobile money. Instead of a bank account, provide:
accountNumber— the recipient's mobile phone number (digits only, with country code)bankCode— not required for mobile money payouts
Bank account resolution (Step 1 below) is not required for mobile money — you can skip directly to creating the transaction.
Integration steps
Resolve the user's bank account (bank payouts only)
For NGN bank payouts, verify the user's bank details before initiating:
curl -X POST https://ramp-api.ivorypay.io/api/v1/banks/account-resolution \
-H "Authorization: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"accountNumber": "0123456789",
"bankCode": "058",
"currency": "NGN"
}'This returns the account holder's name, which you should confirm with your user before proceeding.
Create the off-ramp transaction
Bank payout (NGN)
curl -X POST https://ramp-api.ivorypay.io/api/v1/offramp \
-H "Authorization: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Chidi",
"lastName": "Nwankwo",
"email": "chidi@example.com",
"phoneNumber": "+2348098765432",
"accountNumber": "0123456789",
"bankCode": "058",
"blockchain": "BSC_MAINNET",
"reference": "660e8400-e29b-41d4-a716-446655440001",
"token": "USDT",
"fiatCurrency": "NGN",
"fiatAmount": 100000,
"businessFeeInFiat": 0
}'Mobile money payout (UGX)
curl -X POST https://ramp-api.ivorypay.io/api/v1/offramp \
-H "Authorization: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Chidi",
"lastName": "Nwankwo",
"email": "chidi@example.com",
"phoneNumber": "+256772882926",
"accountNumber": "256772882926",
"blockchain": "STARKNET_MAINNET",
"reference": "660e8410-f23c-41c4-a628-838625530891",
"token": "USDT",
"fiatCurrency": "UGX",
"cryptoAmount": 1.5,
"businessFeeInCrypto": 0.001
}'Response
The endpoint returns HTTP 201 with the standard envelope.
{
"success": true,
"message": "Query successful",
"data": {
"firstName": "Chidi",
"lastName": "Nwankwo",
"email": "chidi@example.com",
"refCode": "xyz789abc0",
"reference": "660e8400-e29b-41d4-a716-446655440001",
"transferDetails": {
"token": "USDT",
"blockchain": "BSC_MAINNET",
"amountPayable": 62.5,
"businessFee": 0,
"platformFee": 0.3,
"walletAddress": "0xIvoryPayDepositAddress...",
"createdAt": "2026-03-26T12:00:00.000Z",
"fiatAmount": 100000,
"expiresAt": "2026-03-26T13:00:00.000Z",
"sessionId": "session_abc123",
"duration": 3600
}
}
}User sends crypto
Display the walletAddress, amountPayable (in crypto), and blockchain to your user. They send the specified amount of crypto to the wallet address.
The session expires at expiresAt. If the crypto isn't received before expiry, use the Refresh Session endpoint to extend it.
Confirm crypto sent (optional)
If the user has sent crypto outside of your app (e.g., from an external wallet), you can manually confirm the payment:
curl -X POST https://ramp-api.ivorypay.io/api/v1/offramp/crypto-sent \
-H "Authorization: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"reference": "660e8400-e29b-41d4-a716-446655440001",
"settledCryptoAmount": 62.5,
"transactionHash": "0xdef456..."
}'Specifying amounts and fees
Business fees must be supplied in the same denomination as the amount. There are two valid patterns:
User wants to receive 100,000 NGN
"fiatAmount": 100000, "businessFeeInFiat": 250
User wants to send exactly 1.5 USDT
"cryptoAmount": 1.5, "businessFeeInCrypto": 0.001
Mixing denominations (e.g. fiatAmount + businessFeeInCrypto, or cryptoAmount + businessFeeInFiat) is rejected by validation (FiatOrCryptoAmountConstraint).
Transaction lifecycle
Frequently asked questions
Last updated