Seev PlusDocs
Docs
On/Off-Ramp API

Off-Ramp (Sell USDC)

Create orders for USDC → GHS conversions. User sends USDC to a merchant address and receives GHS via Mobile Money payout.

The off-ramp flow lets users sell USDC for Ghana Cedis. Your app creates an order, the user sends USDC to the merchant's Stellar address, you confirm the crypto payment, and GHS is automatically disbursed to the user's Mobile Money wallet.

Prerequisites

  • User must have completed KYC verification
  • Merchant must have an active GHS wallet with sufficient balance for the payout
  • User must have USDC on the Stellar network ready to send

Create an off-ramp order

curl -X POST https://api.your-domain.com/api/v1/ramp/orders \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "offramp",
    "crypto_amount": 10,
    "payment_method": "mobile_money",
    "payout_method": "mobile_money",
    "idempotency_key": "offramp_xyz_456",
    "user": {
      "first_name": "Ama",
      "last_name": "Asante",
      "phone": "0542853417",
      "network": "MTN"
    }
  }'

Request body

FieldTypeRequiredDescription
typestringYesMust be "offramp"
crypto_amountnumberOne ofAmount in USDC the user sends (specify this OR fiat_amount)
fiat_amountnumberOne ofAmount in GHS the user wants to receive
payment_methodstringYes"mobile_money"
payout_methodstringNo"mobile_money" (default)
idempotency_keystringNoUnique key to prevent duplicate orders
user.first_namestringYesUser's first name (must match KYC)
user.last_namestringYesUser's last name
user.phonestringYesUser's phone to receive GHS payout
user.networkstringYesMoMo network: MTN, VODAFONE, TELECEL, or AIRTELTIGO

Response

{
  "success": true,
  "data": {
    "reference": "RAMP-20260810-6664a946",
    "order_id": "3fb1a533-f292-4764-a197-3df749f41bd0",
    "type": "offramp",
    "status": "crypto_pending",
    "you_pay": "10.00 USDC",
    "you_receive": "117.80 GHS",
    "rate": "1 USDC = 11.78 GHS",
    "fiat_amount": 117.8,
    "fiat_currency": "GHS",
    "crypto_amount": 10,
    "crypto_currency": "USDC",
    "rate_applied": 11.78,
    "fee_amount": 1.77,
    "fee_bearer": "customer",
    "net_fiat_amount": 117.8,
    "net_crypto_amount": 10,
    "merchant_stellar_address": "GC24GD7HFXFP4JBFNGKWSGDNNHRXLLY32NNNXSWDE24PBZUFZ5T5XNN5",
    "expires_at": "2026-08-10T06:40:04Z",
    "message": "Send 10.00 USDC to the merchant address below within 5 minutes."
  }
}

The response includes merchant_stellar_address — this is where the user must send their USDC. The order will expire if crypto is not confirmed before expires_at.

Step 2: User sends USDC

The user sends the exact USDC amount to the merchant_stellar_address returned in the order response. Your app should display this address clearly and instruct the user to send the correct amount.

Step 3: Confirm the crypto payment

Once the user has sent USDC, call the confirm endpoint. The API will search for a matching payment on-chain automatically:

curl -X POST https://api.your-domain.com/api/v1/ramp/orders/RAMP-20260810-6664a946/confirm-crypto \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source_wallet": "GCGES3SODXJX6RYF2ATRQIA5GLWDXTEXR6QWX3JERVCDY4EIFEMFB55O"}'

Request body

FieldTypeRequiredDescription
source_walletstringYesThe Stellar address the user sent USDC from
tx_hashstringNoSpecific transaction hash (if known). If omitted, the API searches for a matching payment automatically.

Response

{
  "success": true,
  "data": {
    "reference": "RAMP-20260810-6664a946",
    "status": "crypto_confirmed",
    "tx_hash": "a3f59723bc57101d16b322b4cd7b6948b4eefab735eca0654322d48f4c28007a",
    "message": "Crypto payment verified. Fiat payout initiated."
  }
}

What happens after confirmation

  1. Crypto validated — USDC payment is verified on the Stellar network
  2. Fiat payout initiated — GHS is sent to the user's Mobile Money
  3. Completed — Order status moves to completed

The fiat payout typically arrives within seconds for MTN Mobile Money. Bank transfers may take longer.

Check order status

curl https://api.your-domain.com/api/v1/ramp/orders/RAMP-20260810-6664a946 \
  -H "Authorization: Bearer $SECRET_KEY"

Completed response:

{
  "success": true,
  "data": {
    "reference": "RAMP-20260810-6664a946",
    "type": "offramp",
    "status": "completed",
    "crypto_tx_hash": "a3f59723bc57101d16b322b4cd7b6948b4eefab735eca0654322d48f4c28007a",
    "fiat_amount": 117.8,
    "crypto_amount": 10,
    "rate_applied": 11.78,
    "completed_at": "2026-08-10T06:52:30Z"
  }
}

Cancel an order

Cancel before crypto is sent:

curl -X POST https://api.your-domain.com/api/v1/ramp/orders/RAMP-20260810-6664a946/cancel \
  -H "Authorization: Bearer $SECRET_KEY"

Cancellation is only possible when the order is in crypto_pending status. Once crypto is confirmed, the order cannot be cancelled.

Automatic transaction matching

When you call /confirm-crypto without a tx_hash, the API searches the Stellar network for a matching USDC payment:

  • From the source_wallet you provide
  • To the merchant's Stellar address
  • For the exact expected USDC amount (with ±0.01 tolerance)
  • Created after the order was placed

If a match is found, the transaction hash is returned and the order proceeds. If no match is found, you'll receive an error with details about what was expected.

Error codes

ErrorMeaning
KYC_REQUIREDPhone number not verified
RAMP_NOT_ACTIVEMerchant ramp wallets not activated
RATE_UNAVAILABLEExchange rate temporarily unavailable
INSUFFICIENT_LIQUIDITYNot enough GHS for the payout
LIMIT_ERRORAmount exceeds limits
RATE_EXPIREDQuote expired — create a new order
INVALID_STATUSOrder not in the correct state for this operation
CRYPTO_VALIDATION_FAILEDNo matching USDC payment found on-chain
DUPLICATE_TXThis transaction hash was already used by another order

Best practices

  • Display the merchant_stellar_address and exact crypto_amount clearly so the user sends the correct amount
  • Call /confirm-crypto only after you're confident the user has sent the USDC (e.g., after they confirm in your UI or after a delay)
  • If the user provides the tx_hash from their wallet, include it in the confirm request for faster validation
  • Use idempotency_key to safely retry order creation
  • Poll order status to detect when the payout completes
  • Handle expired status by creating a new order if the user missed the window

On this page