Offramps
Our off-ramp API allows you to programmatically convert stablecoins (USDC/USDT) on supported EVM chains into fiat currency, which can be paid out to a bank account or a mobile money wallet. The process involves creating an off-ramp transaction, sending the specified crypto amount to a unique address, and waiting for the fiat payout.
When To Use This
Use offramps when a user wants to convert supported stablecoins into fiat and receive the payout through mobile money, bank account, M-Pesa Paybill, or M-Pesa Till.
Happy Path
-
Retrieve any required payout code, such as a bank code or mobile money operator ID.
-
Create an offramp transaction with the crypto amount, source token, payout currency, destination, and recipient details.
-
Show the returned deposit address, expected crypto amount, and expiration time to the user.
-
The user sends the exact expected amount of USDC or USDT on the selected chain.
-
Honeycoin detects the on-chain payment, converts the funds, and initiates the fiat payout.
-
Reconcile the final result from webhooks or the transaction status endpoint.
Step 1: Get Supported Payout Methods
Before initiating a transaction, you need the correct code for the user's desired payout method.
For Bank Payouts: Use the GET /banks endpoint to fetch a list of supported banks and their corresponding codes.
For Mobile Money Payouts: Use the GET /mobile-money-operators endpoint to retrieve a list of supported mobile money providers and their codes.
You'll use the code from the response in the next step.
Step 2: Initiate the Off-Ramp Transaction
To start the process, make a POST request to the /minting/offramp endpoint. This generates a unique deposit address for the user.
Endpoint: POST /minting/offramp
Request Body:
You need to provide the following details in the request body:
| Field | Type | Required | Description |
|---|---|---|---|
| senderAmount | Number | ✅ | Exact amount of crypto the user must send (e.g. 50) |
| senderCurrency | String | ✅ | Crypto token the user sends (USDC or USDT) |
| receiverCurrency | String | ✅ | Fiat currency to pay out to the recipient (e.g. KES, NGN) |
| country | String | ✅ | ISO 3166-1 alpha-2 recipient country code (e.g. KE) |
| chain | String | ✅ | Blockchain the user sends funds on (e.g. BASE, MATIC) |
| destination | String | ✅ | Payout destination: MoMo, Bank Account, Paybill, or Till |
| payoutMethod | Object | ✅ | Recipient payout details. Shape depends on destination |
| externalReference | String | ✅ | Your unique ID for reconciliation |
| refundAddress | String | ❌ | Same-chain address used for an automatic refund attempt if crypto was received and the off-ramp later fails. If omitted, Honeycoin uses the detected on-chain sender address |
Destination Requirements
| Destination | Required payoutMethod Fields | Notes |
|---|---|---|
MoMo | accountName, accountNumber, sometimes code | code is the mobile money operator ID when operator selection is required |
Bank Account | accountName, accountNumber, code | code is the bank code from the Get Banks API |
Paybill | accountName, accountNumber, businessNumber | Kenya only |
Till | accountName, accountNumber | Kenya only |
Example Request:
{
"senderAmount": 50,
"senderCurrency": "USDC",
"receiverCurrency": "KES",
"country": "KE",
"chain": "BASE",
"destination": "MoMo",
"externalReference": "offramp_user_001",
"payoutMethod": {
"accountName": "John Doe",
"accountNumber": "254712345678",
"code": "mpesa"
}
}Example Response:
{
"success": true,
"data": {
"expectedAmount": 50,
"transactionId": "zQQvoWnhYED3yXYEo7y7",
"addressId": "wallet_123",
"address": "0x318aa0c46120bb214f7da0d53183d0b1c9be7047",
"expiresAt": 1755517238922
}
}The API response includes a unique address and the exact expectedAmount of crypto to be sent. The address is valid until the returned expiresAt timestamp.
How Expiry Works
Each off-ramp request creates a transaction-specific deposit address and an on-chain listener for that address. The listener expiry is configured by Honeycoin and returned as expiresAt, a Unix timestamp in milliseconds.
Your user must send the exact expectedAmount before expiresAt. When the expiry time is reached, Honeycoin's deleteSubscription job removes the on-chain listener for the generated address. If no on-chain transaction has been received and the off-ramp is still pending, the off-ramp is marked as failed with the message Transaction failed because no funds were received before expiration.
After expiresAt, do not reuse the same address or ask the user to send funds to it. Create a new off-ramp request to get a fresh transaction, deposit address, and expiry time.
Step 3: Send Crypto to the Generated Address
Next, your user must send the exact expectedAmount of USDC or USDT on the specified chain to the deposit address provided in the previous step's response.
Important Notes:
Send the Exact Amount: Sending an amount different from the expectedAmount can cause the transaction to fail. Honeycoin will attempt an automatic refund when the deposit was detected, but amount mismatches can still require support-assisted recovery.
Time Limit: The transfer must be received before the returned expiresAt timestamp. The current production window is typically 1 hour, but integrations should rely on expiresAt instead of hardcoding a duration.
Supported Tokens: Only USDC and USDT are supported.
Step 4: Receive Payout and Confirmation
Our system monitors the generated address for the incoming transaction.
Transaction Confirmation: Once the crypto deposit is confirmed on the blockchain, we automatically initiate the fiat payout to the payoutMethod details you provided.
Webhook Notification: When the payout is successfully processed, we will send a POST request with the final transaction status to your specified webhookUrl.
Your system should be configured to listen for this webhook to confirm that the off-ramp process is complete.
{
"event": "transaction_updated",
"data": {
"transactionId": "zQQvoWnhYED3yXYEo7y7",
"status": "successful",
"type": "offramp",
"externalReference": "cmb8fd4dfl4w004fd",
"method": "momo",
"depositAddress": "0x318aa0c46120bb214f7da0d53183d0b1c9be7047",
"txId": "0xc3acfde7f212fcb902045119ee41299249ca153fc5c2730b0e8adba749846b53"
},
"timestamp": "2025-08-18T10:40:38.922Z"
}Failed Off-Ramps and Automatic Refunds
An off-ramp can fail before or after crypto is received. Refund behavior depends on whether Honeycoin detected an on-chain deposit:
| Failure state | txId | Refund behavior |
|---|---|---|
No funds received before expiresAt | Absent | The off-ramp is marked failed. No refund is created because Honeycoin did not receive funds |
| Deposit amount does not match the expected amount | Present | Honeycoin attempts an automatic on-chain refund |
| Fiat payout fails after the crypto deposit is detected | Present | Honeycoin attempts an automatic on-chain refund |
For an eligible failed off-ramp, the refund runs asynchronously from the transaction-specific deposit wallet:
- Honeycoin sends the same source token on the same chain.
- The destination is the request's
refundAddresswhen supplied. Otherwise, Honeycoin uses thefromaddress detected on the original deposit. - The automatic attempt uses the original
senderAmountreturned asexpectedAmount. - The smart-account refund path uses Honeycoin-sponsored network gas, so gas is not deducted from the attempted refund amount.
- After a successful on-chain refund, Honeycoin adds
refundTransactionHashto the original off-ramp transaction. The off-ramp'schargeStatusremainsfailedbecause the fiat payout did not succeed.
Amount mismatches are not a safe recovery workflow. Because the automatic attempt uses the original
expectedAmount, an underpayment may not leave enough tokens in the deposit wallet and an overpayment may leave excess funds. Always instruct users to send the exact amount. Contact Honeycoin support if the received amount differed fromexpectedAmount.
Track Refund Completion
The transaction_updated webhook with status: "failed" confirms that the off-ramp payout failed. It does not confirm that the on-chain refund succeeded, and it may arrive before the refund is submitted.
Use the Get Transaction endpoint with the original transactionId or externalReference:
GET /api/b2b/transactions/zQQvoWnhYED3yXYEo7y7When the refund completes, the original off-ramp includes refundTransactionHash:
{
"success": true,
"data": {
"transactionId": "zQQvoWnhYED3yXYEo7y7",
"chargeStatus": "failed",
"status": "FAILED",
"type": "offramp",
"senderAmount": 50,
"senderCurrency": "USDC",
"chain": "BASE",
"txId": "0xc3acfde7f212fcb902045119ee41299249ca153fc5c2730b0e8adba749846b53",
"refundTransactionHash": "0xa41f748e7e0cbd3f1831439c300511bee7a6fa4067a4f48a927df1d892c7f8aa"
}
}Treat the refund as confirmed only after refundTransactionHash is present and, when needed, verify that hash on the source chain. If an eligible failed off-ramp continues to have no refundTransactionHash, contact Honeycoin support with the transactionId, deposit txId, chain, token, and expected amount.
Automatic off-ramp refunds remain part of the original type: "offramp" transaction. They are different from merchant-initiated mobile money deposit refunds and do not emit the separate type: "refund" transaction webhook described in the Collections refund guide.
Updated 25 days ago
