On-Ramp (Buy USDC)
Create orders for GHS → USDC conversions. Collect fiat via Mobile Money and deliver USDC to the user's Stellar wallet.
The on-ramp flow lets users buy USDC with Ghana Cedis via Mobile Money. Your app creates an order, the user approves a MoMo prompt on their phone, and USDC is automatically sent to their Stellar wallet.
Prerequisites
- User must have completed KYC verification
- Merchant must have an active USDC wallet (set up during activation)
- Merchant must have sufficient USDC liquidity to fulfil the order
Get a rate preview
Before committing to an order, you can preview the current exchange rate without any side effects.
curl https://api.your-domain.com/api/v1/ramp/quote \
-H "Authorization: Bearer $SECRET_KEY" \
-G \
-d "type=onramp" \
-d "fiat_amount=100"Response:
{
"success": true,
"data": {
"type": "onramp",
"you_pay": "100.00 GHS",
"you_receive": "8.21 USDC",
"rate": "1 USDC = 12.18 GHS",
"fiat_amount": 100,
"fiat_currency": "GHS",
"crypto_amount": 8.21,
"crypto_currency": "USDC",
"rate_applied": 12.18,
"fee_amount": 1.5,
"fee_bearer": "customer",
"net_fiat_amount": 100,
"net_crypto_amount": 8.21,
"expires_in_seconds": 300
}
}You can specify either fiat_amount (GHS the user wants to pay) or crypto_amount (USDC the user wants to receive). The API calculates the other side.
Create an on-ramp order
This is the all-in-one endpoint that locks the rate, reserves liquidity, and initiates the Mobile Money collection in a single call.
curl -X POST https://api.your-domain.com/api/v1/ramp/orders \
-H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "onramp",
"fiat_amount": 100,
"destination_wallet": "GBXYZ...USER_STELLAR_ADDRESS",
"payment_method": "mobile_money",
"idempotency_key": "order_abc_123",
"user": {
"first_name": "Kofi",
"last_name": "Mensah",
"phone": "0241234567",
"network": "MTN"
}
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "onramp" |
fiat_amount | number | One of | Amount in GHS the user pays (specify this OR crypto_amount) |
crypto_amount | number | One of | Amount in USDC the user receives |
destination_wallet | string | Yes | User's Stellar address to receive USDC |
payment_method | string | Yes | "mobile_money" or "bank_transfer" |
idempotency_key | string | No | Unique key to prevent duplicate orders |
user.first_name | string | Yes | User's first name (must match KYC) |
user.last_name | string | Yes | User's last name |
user.phone | string | Yes | User's phone number (must be KYC verified) |
user.network | string | Yes | MoMo network: MTN, VODAFONE, TELECEL, or AIRTELTIGO |
Response
{
"success": true,
"data": {
"reference": "RAMP-20260810-a1b2c3d4",
"order_id": "f16890cc-8fd0-4af7-982e-2100bf3dda14",
"type": "onramp",
"status": "fiat_pending",
"you_pay": "100.00 GHS",
"you_receive": "8.21 USDC",
"rate": "1 USDC = 12.18 GHS",
"fiat_amount": 100,
"fiat_currency": "GHS",
"crypto_amount": 8.21,
"crypto_currency": "USDC",
"rate_applied": 12.18,
"fee_amount": 1.5,
"fee_bearer": "customer",
"net_fiat_amount": 100,
"net_crypto_amount": 8.21,
"expires_at": "2026-08-10T06:22:00Z",
"message": "Payment initiated. Approve the mobile money prompt on your phone."
}
}The order is immediately in fiat_pending status. A Mobile Money prompt is sent to the user's phone. The user must approve it before the rate expires.
What happens after order creation
- MoMo prompt sent — The user receives a payment prompt on their phone
- User approves — Status transitions to
fiat_confirmed - USDC sent — Status moves to
crypto_sending, thencompletedwith acrypto_tx_hash - Rate expires — If the user doesn't approve within the TTL, status becomes
expired
Check order status
Poll the order to track progress:
curl https://api.your-domain.com/api/v1/ramp/orders/RAMP-20260810-a1b2c3d4 \
-H "Authorization: Bearer $SECRET_KEY"Completed response includes the Stellar transaction hash:
{
"success": true,
"data": {
"reference": "RAMP-20260810-a1b2c3d4",
"type": "onramp",
"status": "completed",
"crypto_tx_hash": "ab517c09c4b67f8c3573621a40cb6ad00887f16fe6264f6388267bb33b76ad51",
"fiat_amount": 100,
"crypto_amount": 8.21,
"rate_applied": 12.18,
"completed_at": "2026-08-10T06:25:57Z"
}
}List orders
Retrieve paginated order history:
curl "https://api.your-domain.com/api/v1/ramp/orders?type=onramp&limit=20&offset=0" \
-H "Authorization: Bearer $SECRET_KEY"Cancel an order
Cancel an order that hasn't been paid yet:
curl -X POST https://api.your-domain.com/api/v1/ramp/orders/RAMP-20260810-a1b2c3d4/cancel \
-H "Authorization: Bearer $SECRET_KEY"Cancellation is only possible when the order is in quote_locked status (two-step flow). Orders created via the single-step POST /orders endpoint go directly to fiat_pending and cannot be cancelled once the MoMo prompt is sent.
Two-step flow (legacy)
For more control, you can separate quote creation from payment initiation:
Step 1: Create a quote
curl -X POST https://api.your-domain.com/api/v1/ramp/quote \
-H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "onramp",
"fiat_amount": 100,
"destination_wallet": "GBXYZ...USER_STELLAR_ADDRESS",
"user": {
"first_name": "Kofi",
"last_name": "Mensah",
"phone": "0241234567",
"provider_name": "MTN"
}
}'The order is created in quote_locked status with a locked rate.
Step 2: Initiate payment
curl -X POST https://api.your-domain.com/api/v1/ramp/RAMP-20260810-a1b2c3d4/pay \
-H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "mobile_money", "phone": "0241234567", "network": "MTN"}'This sends the MoMo prompt and transitions the order to fiat_pending.
Idempotency
Include an idempotency_key in your create order request to safely retry failed requests. If a request with the same key has already created an order, the existing order is returned without creating a duplicate.
Error codes
| Error | Meaning |
|---|---|
KYC_REQUIRED | Phone number not verified — complete KYC first |
RAMP_NOT_ACTIVE | Merchant ramp wallets not activated |
RATE_UNAVAILABLE | Exchange rate service temporarily unavailable |
INVALID_DESTINATION_WALLET | Stellar address is invalid or missing USDC trustline |
INSUFFICIENT_LIQUIDITY | Not enough USDC in merchant's liquidity pool |
LIMIT_ERROR | Amount exceeds transaction, daily, or monthly limits |
RATE_EXPIRED | Quote TTL elapsed — create a new order |
COLLECTION_FAILED | MoMo collection could not be initiated |
Best practices
- Always validate the destination Stellar wallet has a USDC trustline before creating an order
- Use
idempotency_keyfor all order creation to handle network retries safely - Poll order status or set up webhooks to detect completion — do not assume success after creation
- Show the user the rate and amount before they approve the MoMo prompt
- Handle
expiredstatus gracefully by prompting the user to retry