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

  1. Retrieve any required payout code, such as a bank code or mobile money operator ID.

  2. Create an offramp transaction with the crypto amount, source token, payout currency, destination, and recipient details.

  3. Show the returned deposit address, expected crypto amount, and expiration time to the user.

  4. The user sends the exact expected amount of USDC or USDT on the selected chain.

  5. Honeycoin detects the on-chain payment, converts the funds, and initiates the fiat payout.

  6. 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

Link to Crypto Off-Ramp API

Request Body:

You need to provide the following details in the request body:

FieldTypeRequiredDescription
senderAmountNumberExact amount of crypto the user must send (e.g. 50)
senderCurrencyStringCrypto token the user sends (USDC or USDT)
receiverCurrencyStringFiat currency to pay out to the recipient (e.g. KES, NGN)
countryStringISO 3166-1 alpha-2 recipient country code (e.g. KE)
chainStringBlockchain the user sends funds on (e.g. BASE, MATIC)
destinationStringPayout destination: MoMo, Bank Account, Paybill, or Till
payoutMethodObjectRecipient payout details. Shape depends on destination
externalReferenceStringYour unique ID for reconciliation
refundAddressStringAddress to refund if the payout fails. If omitted, funds are returned to the detected sender address when possible

Destination Requirements

DestinationRequired payoutMethod FieldsNotes
MoMoaccountName, accountNumber, sometimes codecode is the mobile money operator ID when operator selection is required
Bank AccountaccountName, accountNumber, codecode is the bank code from the Get Banks API
PaybillaccountName, accountNumber, businessNumberKenya only
TillaccountName, accountNumberKenya 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 will cause the transaction to fail and a refund to be initiated back to the address funds were received from.

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"
}