Pay a mobile wallet in CFA francs

Pay a mobile wallet in Côte d'Ivoire or Benin in XOF, with the payee's country set and the phone number's leading zero kept.

Use this recipe to pay a mobile wallet in Côte d'Ivoire or Benin in XOF. You set the payee's country and keep the leading zero of the phone number.

Who it is for. This recipe is for a New York cocoa buyer whose app pays a cooperative treasurer's mobile wallet in Abidjan and a field agent in Cotonou.

Status. Available on the operator rows MTN MoMo Cote d'Ivoire and MTN MoMo Benin. Other wallet operators in Côte d'Ivoire, Senegal, Burkina Faso, Mali and Togo, and XOF or XAF bank accounts, are not yet available.

Money example

  1. The buyer sends 120.00 USD. Say the fee on your plan is 1.00 USD.
  2. Say the rate is 605.00 XOF per USD. The transfer response prices 72,600 XOF for the treasurer.
  3. The treasurer's wallet receives the CFA francs.

Before you start

  • The sender's name and country are on the profile, or in additional_information (remitter_full_name, remitter_country).
  • The USD account is active and funded.

Find out what is required

  1. The pair. GET /v1/transfer/supported-currencies lists pairs such as USD_XOF. A pair is not a country.
  2. The operator row. Call GET /v1/external-accounts/institutions?country_iso_code=CI&institution_type=mobile_money. Côte d'Ivoire's row is MTN MoMo Cote d'Ivoire (6c5cf000-ec15-4c95-98ee-b1a1077a31e0), and Benin's is MTN MoMo Benin (8adf9a8f-7fa9-4875-aff4-ee7a7624de3c). Other operators in the list are not confirmed, so ask support before you promise them.
  3. The rate. GET /v1/transfer/rates?currency=XOF returns one item per XOF country, such as currency_pair USD_US_XOF_CI or USD_US_XOF_BJ. Pick the one for the payee's country.
  4. The country. Eight countries share XOF, so the currency does not tell Gravv the country. Pick an operator row, which carries country_iso_code, and also send institution_country_iso_code.
  5. The phone number. A Côte d'Ivoire number is 225 plus 10 digits, and a Benin number is 229 plus 10 digits. Both keep their leading 0, as in +2250708123456 and +2290197123456.

Steps

1. Save the payee

curl --request POST \
 --url https://api.gravv.xyz/v1/external-accounts \
 --header 'Api-Key: <API_KEY>' \
 --header 'Content-Type: application/json' \
 --header 'Idempotency-Key: 9401411b-9038-4982-9804-504d4396dad9' \
 --data '
{
"customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
"payee_type": "mobile_money",
"currency": "XOF",
"account_name": "Awa Kone",
"phone_number": "+2250708123456",
"institution_id": "6c5cf000-ec15-4c95-98ee-b1a1077a31e0",
"institution_country_iso_code": "CI"
}
'
const response = await fetch("https://api.gravv.xyz/v1/external-accounts", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "9401411b-9038-4982-9804-504d4396dad9",
},
body: JSON.stringify({
"customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
"payee_type": "mobile_money",
"currency": "XOF",
"account_name": "Awa Kone",
"phone_number": "+2250708123456",
"institution_id": "6c5cf000-ec15-4c95-98ee-b1a1077a31e0",
"institution_country_iso_code": "CI"
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/external-accounts",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Content-Type": "application/json",
    "Idempotency-Key": "9401411b-9038-4982-9804-504d4396dad9",
},
json={
    "customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
    "payee_type": "mobile_money",
    "currency": "XOF",
    "account_name": "Awa Kone",
    "phone_number": "+2250708123456",
    "institution_id": "6c5cf000-ec15-4c95-98ee-b1a1077a31e0",
    "institution_country_iso_code": "CI",
},
)
print(response.status_code, response.json())
{
  "data": {
    "id": "c0407de2-db05-4385-8138-682b326c4b6e",
    "institution_id": "6c5cf000-ec15-4c95-98ee-b1a1077a31e0",
    "payee_type": "mobile_money",
    "institution_type": "mobile_money",
    "institution_name": "MTN MoMo Cote d'Ivoire",
    "account_name": "AWA KONE",
    "customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
    "currency": "XOF",
    "institution_country_iso_code": "CI",
    "status": "active",
    "phone_number": "+2250708123456",
    "date_created": "2026-10-13T12:20:00.000Z"
  },
  "error": null
}

2. Send

curl --request POST \
 --url https://api.gravv.xyz/v1/transfer \
 --header 'Api-Key: <API_KEY>' \
 --header 'Content-Type: application/json' \
 --header 'Idempotency-Key: 734ac97d-d945-415f-a75b-9384e21cb7a4' \
 --data '
{
"customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
"source": {
"source_type": "internal_account",
"id": "9a29e7f8-a3e8-4907-8dfd-1fee0169ee96"
},
"destination": {
"destination_type": "external_account",
"id": "c0407de2-db05-4385-8138-682b326c4b6e"
},
"amount": "120.00",
"description": "Lot 118 advance",
"client_reference": "cocoa-ci-lot-118",
"additional_information": {
"remitter_full_name": "Grace Whitfield",
"remitter_country": "US"
}
}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "734ac97d-d945-415f-a75b-9384e21cb7a4",
},
body: JSON.stringify({
"customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
"source": {
  "source_type": "internal_account",
  "id": "9a29e7f8-a3e8-4907-8dfd-1fee0169ee96"
},
"destination": {
  "destination_type": "external_account",
  "id": "c0407de2-db05-4385-8138-682b326c4b6e"
},
"amount": "120.00",
"description": "Lot 118 advance",
"client_reference": "cocoa-ci-lot-118",
"additional_information": {
  "remitter_full_name": "Grace Whitfield",
  "remitter_country": "US"
}
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/transfer",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Content-Type": "application/json",
    "Idempotency-Key": "734ac97d-d945-415f-a75b-9384e21cb7a4",
},
json={
    "customer_id": "adfc44c6-3bf4-4c4c-8e53-4bc255cdba4b",
    "source": {
        "source_type": "internal_account",
        "id": "9a29e7f8-a3e8-4907-8dfd-1fee0169ee96",
    },
    "destination": {
        "destination_type": "external_account",
        "id": "c0407de2-db05-4385-8138-682b326c4b6e",
    },
    "amount": "120.00",
    "description": "Lot 118 advance",
    "client_reference": "cocoa-ci-lot-118",
    "additional_information": {
        "remitter_full_name": "Grace Whitfield",
        "remitter_country": "US",
    },
},
)
print(response.status_code, response.json())
{
  "data": {
    "amount": "120",
    "amount_currency": "USD",
    "reference": "b3ee69b6-37e7-4e69-9236-b2dbef3f9790",
    "date_created": "2026-10-13T12:22:15.019Z",
    "transfer_status": "pending",
    "transaction_id": "6791bb33-7239-4a40-8749-69ed17e72701",
    "local_amount": "72600",
    "local_currency": "XOF",
    "fee": "1",
    "fee_currency": "USD",
    "requires_approval": false
  },
  "error": null
}

3. Follow it

Follow the transfer as described in the East and Central Africa recipe.

What can go wrong

Response Why Fix
400 destination country is required for international transfers The XOF payee has no country Save it again with an institution_id and institution_country_iso_code
400 WAEMU account numbers must be exactly 24 alphanumeric characters A bank payee in SN, CI, BJ, BF, GW, ML, NE or TG needs the 24-character account number (RIB) Ask for the full number. XOF bank payouts are not yet available
400 transfers to SN/XOF are not available at this time The route is not open Contact support
transfer.status.failed after acceptance The phone lost its leading 0, or the sender's name or country is missing Save a new payee with the full number, and add the sender details
transfer.status.failed, but the transaction is still pending The transfer is in manual review Do not send it again. Contact support with the reference

Webhooks you will receive

You receive the same webhooks as in the East and Central Africa recipe.

Testing in sandbox

You cannot test this route in sandbox today. In sandbox, saving a Côte d'Ivoire or Benin payee on these rows returns account verification temporarily unavailable; please retry: invalid input. Payouts to other XOF operators return the payee is missing required information for this transfer type — please update the payee details.

Reference

Was this page helpful?