---
title: "Pay a mobile wallet in Kenya"
description: "Save a Kenyan mobile wallet payee on the right operator row, check the wallet's name and send shillings."
---
Use this recipe to save a Kenyan mobile wallet payee on the right operator row, check the wallet's name, and send shillings.

**Who it is for.** A payroll app for a Dubai logistics firm that pays a driver's mobile wallet in Nairobi every week.

**Status.** Not yet available. Ask support to confirm that Gravv has switched it on for your business. The steps below show what the route needs.

## Money example

1. The employer's USD account sends 200.00 USD. Say the fee on your plan is 1.00 USD.
2. Say the rate is 129.00 KES per USD. The transfer response prices 25,800.00 KES for the driver.
3. The driver's wallet receives the shillings.

## Before you start

- The sender has a name: `first_name` and `last_name`, or `business_name` for a business ([`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id)). Kenyan routes refuse a payout with no sender name. If you are unsure, send `additional_information.remitter_full_name`.
- The USD account is `active` and funded.

## Find out what is required

### The operator row

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=KE&institution_type=mobile_money' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=KE&institution_type=mobile_money", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=KE&institution_type=mobile_money",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "items": [
      {
        "account_number_type": "phone_number",
        "country_iso_code": "KE",
        "currency": "KES",
        "id": "267a9823-de79-4bfc-99f2-d9edeb8e7b86",
        "max_amount": 0,
        "min_amount": 0,
        "name": "Airtel Money Kenya"
      },
      {
        "account_number_type": "phone_number",
        "country_iso_code": "KE",
        "currency": "KES",
        "id": "0c6324d5-b26d-4cb3-8f6c-db84c763338b",
        "max_amount": 0,
        "min_amount": 0,
        "name": "M-Pesa Kenya"
      },
      {
        "account_number_type": "phone_number",
        "country_iso_code": "KE",
        "currency": "KES",
        "id": "e8518e20-16f3-4390-86e6-55f14feee01d",
        "max_amount": 7500000,
        "min_amount": 1,
        "name": "Airtel"
      },
      {
        "account_number_type": "phone_number",
        "country_iso_code": "KE",
        "currency": "KES",
        "id": "9121e0a3-fb54-4760-b9cb-c303efd3af1c",
        "max_amount": 7500000,
        "min_amount": 1,
        "name": "M-Pesa"
      }
    ],
    "items_per_page": 10,
    "page": 1,
    "total_items": 4,
    "total_pages": 1
  },
  "error": null
}
```

Pick the row named `M-Pesa Kenya` or `Airtel Money Kenya`. The rows with shorter names come from another list, and this route is not set up for them.

### The phone number

A Kenyan mobile number is `254`, then `7` or `1`, then 8 digits. Gravv reads `+254 712 345 678`, `0712345678` and `712345678` all as `254712345678`.

Check the number yourself before you save the payee. If the wallet's name check cannot find the number, the API refuses the save. Gravv refuses a number with any other wrong format only at payout.

### The rate

Get the rate with [`GET /v1/transfer/rates?currency=KES`](/api-reference/transfer/get-v1-transfer-rates).

## Steps

### 1. Save the wallet payee

When you save the payee, Gravv looks up the wallet's registered name and replaces `account_name` with it.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/external-accounts \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: d5ad1237-53fc-429e-96ff-32d91280f5eb' \
     --data '
{
  "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
  "payee_type": "mobile_money",
  "currency": "KES",
  "account_name": "Brian Otieno",
  "phone_number": "+254712345678",
  "institution_id": "0c6324d5-b26d-4cb3-8f6c-db84c763338b"
}
'
```
```javascript title="Node"
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": "d5ad1237-53fc-429e-96ff-32d91280f5eb",
  },
  body: JSON.stringify({
    "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
    "payee_type": "mobile_money",
    "currency": "KES",
    "account_name": "Brian Otieno",
    "phone_number": "+254712345678",
    "institution_id": "0c6324d5-b26d-4cb3-8f6c-db84c763338b"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
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": "d5ad1237-53fc-429e-96ff-32d91280f5eb",
    },
    json={
        "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
        "payee_type": "mobile_money",
        "currency": "KES",
        "account_name": "Brian Otieno",
        "phone_number": "+254712345678",
        "institution_id": "0c6324d5-b26d-4cb3-8f6c-db84c763338b",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response (HTTP 200)"
{
  "data": {
    "id": "5f22bdc3-72df-46be-b8a8-176c2942294f",
    "institution_id": "0c6324d5-b26d-4cb3-8f6c-db84c763338b",
    "payee_type": "mobile_money",
    "institution_type": "mobile_money",
    "institution_name": "M-Pesa Kenya",
    "account_name": "BRIAN OTIENO",
    "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
    "currency": "KES",
    "institution_country_iso_code": "KE",
    "status": "active",
    "phone_number": "+254712345678",
    "date_created": "2026-10-13T10:02:31.551Z"
  },
  "error": null
}
```

Show the user `account_name`, which is the wallet's registered name, and ask them to confirm that it is the driver.

### 2. Send

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: f4b4216c-a048-45e2-a7fc-b0ef1d878340' \
     --data '
{
  "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
  "source": {
    "source_type": "internal_account",
    "id": "8925d474-9e46-4bc2-8d5b-9afd1b1a44e5"
  },
  "destination": {
    "destination_type": "external_account",
    "id": "5f22bdc3-72df-46be-b8a8-176c2942294f"
  },
  "amount": "200.00",
  "description": "Week 41 wages",
  "client_reference": "fleet-ke-wk41-0042",
  "additional_information": {
    "remitter_full_name": "Fatima Al Mansoori",
    "remitter_country": "AE"
  }
}
'
```
```javascript title="Node"
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": "f4b4216c-a048-45e2-a7fc-b0ef1d878340",
  },
  body: JSON.stringify({
    "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
    "source": {
      "source_type": "internal_account",
      "id": "8925d474-9e46-4bc2-8d5b-9afd1b1a44e5"
    },
    "destination": {
      "destination_type": "external_account",
      "id": "5f22bdc3-72df-46be-b8a8-176c2942294f"
    },
    "amount": "200.00",
    "description": "Week 41 wages",
    "client_reference": "fleet-ke-wk41-0042",
    "additional_information": {
      "remitter_full_name": "Fatima Al Mansoori",
      "remitter_country": "AE"
    }
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
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": "f4b4216c-a048-45e2-a7fc-b0ef1d878340",
    },
    json={
        "customer_id": "877231ff-f5e2-4ca2-9e1d-11ee4be57723",
        "source": {
            "source_type": "internal_account",
            "id": "8925d474-9e46-4bc2-8d5b-9afd1b1a44e5",
        },
        "destination": {
            "destination_type": "external_account",
            "id": "5f22bdc3-72df-46be-b8a8-176c2942294f",
        },
        "amount": "200.00",
        "description": "Week 41 wages",
        "client_reference": "fleet-ke-wk41-0042",
        "additional_information": {
            "remitter_full_name": "Fatima Al Mansoori",
            "remitter_country": "AE",
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "200",
    "amount_currency": "USD",
    "reference": "da01d483-2dd2-4965-8334-b2b416e2a23e",
    "date_created": "2026-10-13T10:05:10.118Z",
    "transfer_status": "pending",
    "transaction_id": "187c4388-caab-4310-a4a3-87ec5152e533",
    "local_amount": "25800",
    "local_currency": "KES",
    "fee": "1",
    "fee_currency": "USD",
    "requires_approval": false
  },
  "error": null
}
```

### 3. Follow it

Use the webhooks, or poll [`GET /v1/transactions/187c4388-caab-4310-a4a3-87ec5152e533`](/api-reference/transactions/get-v1-transactions-transaction-id). On the transaction, `channel` is `mobile_money`.

### Bank accounts in Kenya

To pay a Kenyan bank account, follow the same steps with `"payee_type": "bank_account"`, an `account_number` of 8 to 20 letters or digits, and a bank's `institution_id` from `institution_type=bank`. You can pay only the banks that the institutions list returns for KE.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `kenyan account numbers must be 8-20 alphanumeric characters` | The bank account number has the wrong format | Fix the number |
| 400 `account verification failed: bank could not resolve account holder: invalid input` when saving | The wallet number is not registered | Check the number with the recipient |
| 400 `a recipient with the same account name, account number and bank already exists`, with `data.payee_id` | A payee with the same name, phone number and operator exists | Use `data.payee_id` |
| 400 `transfers to KE/KES are not available at this time` | The route is not open for your business | Contact support |
| 400 `this payee is not yet set up for transfers — please contact support to complete its setup` | The payee has no operator `institution_id` | Save the payee on the right operator row |
| `transfer.status.failed` after acceptance, and a refund row | The number is not a Kenyan mobile number in the form above, the sender has no name, or the amount is outside the route's limits | You cannot change a phone number, so save a new payee with the right number. Add `remitter_full_name`, or change the amount |
| `transfer.status.failed`, but the transaction is still `pending` | The transfer is in manual review because the outcome was unclear | Do not send it again. Contact support with the `reference` |

## Webhooks you will receive

1. `external_account.created`.
2. `transfer.status.pending`, several times, with `workflow_type` `global_remittance`.
3. `transfer.status.completed` or `transfer.status.failed`.

## Testing in sandbox

You can save the wallet payee in sandbox. Its `account_name` becomes the phone number itself (`254712345678`), so do not show it as a person's name.

Gravv accepts a payout, and the payout goes through several `pending` steps. Then Gravv sends `transfer.status.failed` with `transfer_status` `manual_processing_required`, while the transaction stays `pending`. A Kenya payout cannot complete in sandbox, so use it to test how you handle manual review.

## Reference

- [List institutions](/api-reference/external-accounts/get-v1-external-accounts-institutions)
- [Add an external account](/api-reference/external-accounts/post-v1-external-accounts)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
