---
title: "Send money to Ghana"
description: "Pay a mobile wallet or bank account in Ghana in cedis, send the sender's details up front, and resume a transfer that pauses for sender details."
---
Use this recipe to pay a mobile wallet or bank account in Ghana in cedis, send the sender's details up front, and resume a transfer that pauses for sender details.

**Who it is for.** Kwame Mensah, a nurse in London who uses your app's USD balance, sends money to his mother's mobile wallet in Kumasi and sometimes pays his landlord's Ghanaian bank account.

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

## Money example

1. Kwame sends 300.00 USD from his USD account. Say the fee on your plan is 2.00 USD.
2. Say the rate is 15.20 GHS per USD. The transfer response prices 4,560.00 GHS for his mother.
3. The route needs the sender's full name, date of birth, email and home address. Kwame's profile has no date of birth, so the transfer returns `paused`, and nothing is paid out yet.
4. You resume the transfer with his date of birth and address, and it goes ahead.

## Before you start

- Kwame's customer profile is as complete as you can make it: first and last name, email, `date_of_birth`, and an address with `address_line1`, `city`, `postal_code` and `country` ([`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id)). Any missing detail causes a pause.
- A business sender must still give a **person's** name and date of birth, such as a director's, because the route pays person to person.
- His USD account is `active` and funded.

## Find out what is required

1. **The currency.** [`GET /v1/transfer/supported-countries?iso_code=GH`](/api-reference/transfer/get-v1-transfer-supported-countries) returns `GHS`.
2. **The wallet operator or bank.** List them with `institution_type=mobile_money` or `institution_type=bank`.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=GH&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=GH&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=GH&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": "GH",
        "currency": "GHS",
        "id": "c060a286-a785-4f76-b866-a40e8fe7570a",
        "max_amount": 10000,
        "min_amount": 1,
        "name": "Airtel Money"
      },
      {
        "account_number_type": "phone_number",
        "country_iso_code": "GH",
        "currency": "GHS",
        "id": "ad28c4e4-c303-4505-aa2f-7df94e0588cd",
        "max_amount": 10000,
        "min_amount": 1,
        "name": "MTN Mobile Money"
      }
    ],
    "items_per_page": 10,
    "page": 1,
    "total_items": 2,
    "total_pages": 1
  },
  "error": null
}
```

If `account_number_type` is `phone_number`, the payee carries `phone_number`. If it is `bank_account_number`, the payee carries `account_number`, which is 10 to 16 digits in Ghana.

3. **The rate.** Get it with [`GET /v1/transfer/rates?currency=GHS`](/api-reference/transfer/get-v1-transfer-rates).
4. **The sender's address form.** [`GET /v1/transfer/supported-countries-for-address?iso_code=GB`](/api-reference/transfer/get-v1-transfer-supported-countries-for-address) lists the country's states.
5. **The sender details the route needs.** Only the pause itself tells you these. No call lists them in advance, so send them up front when you have them.

## Steps

### 1. Save the wallet payee

Put the number in `phone_number`, in international format with `+`. Gravv does not check or reformat the number when you save the payee, so check it yourself.

<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: 5e674882-3611-413b-b7f6-d11ed371f64b' \
     --data '
{
  "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
  "payee_type": "mobile_money",
  "currency": "GHS",
  "account_name": "Akosua Mensah",
  "phone_number": "+233244123456",
  "institution_id": "ad28c4e4-c303-4505-aa2f-7df94e0588cd",
  "institution_country_iso_code": "GH"
}
'
```
```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": "5e674882-3611-413b-b7f6-d11ed371f64b",
  },
  body: JSON.stringify({
    "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
    "payee_type": "mobile_money",
    "currency": "GHS",
    "account_name": "Akosua Mensah",
    "phone_number": "+233244123456",
    "institution_id": "ad28c4e4-c303-4505-aa2f-7df94e0588cd",
    "institution_country_iso_code": "GH"
  }),
});
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": "5e674882-3611-413b-b7f6-d11ed371f64b",
    },
    json={
        "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
        "payee_type": "mobile_money",
        "currency": "GHS",
        "account_name": "Akosua Mensah",
        "phone_number": "+233244123456",
        "institution_id": "ad28c4e4-c303-4505-aa2f-7df94e0588cd",
        "institution_country_iso_code": "GH",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

The API returns HTTP 200 with `status` `active` and the payee `id`. In this example the `id` is `d1762ec1-9f61-4eed-a04b-21925a55dbe4`.

For the landlord's bank account instead, send `"payee_type": "bank_account"`, `"account_number": "1234567890123"` and a bank's `institution_id`. You can check a bank account's holder first with [verify](/api-reference/external-accounts/post-v1-external-accounts-verify). That call never checks a phone number.

### 2. Send, with the sender's details up front

Gravv uses the fields in `additional_information` before the profile, so the transfer does not pause. Gravv splits `remitter_full_name` at the first space into a first and a last name. The email always comes from the customer profile.

<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: 3233634a-8ff7-4561-9111-a0b28c463ebc' \
     --data '
{
  "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
  "source": {
    "source_type": "internal_account",
    "id": "245b9314-abed-4632-8fb1-ea2ca70baa3c"
  },
  "destination": {
    "destination_type": "external_account",
    "id": "d1762ec1-9f61-4eed-a04b-21925a55dbe4"
  },
  "amount": "300.00",
  "description": "For Mum, October",
  "client_reference": "kwame-gh-2026-10-007",
  "additional_information": {
    "remitter_full_name": "Kwame Mensah",
    "remitter_date_of_birth": "1988-04-17",
    "remitter_country": "GB",
    "remitter_address": {
      "address_line1": "27 Ashburnham Road",
      "city": "London",
      "postal_code": "SW10 0PL",
      "country": "GB"
    }
  }
}
'
```
```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": "3233634a-8ff7-4561-9111-a0b28c463ebc",
  },
  body: JSON.stringify({
    "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
    "source": {
      "source_type": "internal_account",
      "id": "245b9314-abed-4632-8fb1-ea2ca70baa3c"
    },
    "destination": {
      "destination_type": "external_account",
      "id": "d1762ec1-9f61-4eed-a04b-21925a55dbe4"
    },
    "amount": "300.00",
    "description": "For Mum, October",
    "client_reference": "kwame-gh-2026-10-007",
    "additional_information": {
      "remitter_full_name": "Kwame Mensah",
      "remitter_date_of_birth": "1988-04-17",
      "remitter_country": "GB",
      "remitter_address": {
        "address_line1": "27 Ashburnham Road",
        "city": "London",
        "postal_code": "SW10 0PL",
        "country": "GB"
      }
    }
  }),
});
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": "3233634a-8ff7-4561-9111-a0b28c463ebc",
    },
    json={
        "customer_id": "b4dab3d6-6a7f-4c66-9cc9-483404cd8463",
        "source": {
            "source_type": "internal_account",
            "id": "245b9314-abed-4632-8fb1-ea2ca70baa3c",
        },
        "destination": {
            "destination_type": "external_account",
            "id": "d1762ec1-9f61-4eed-a04b-21925a55dbe4",
        },
        "amount": "300.00",
        "description": "For Mum, October",
        "client_reference": "kwame-gh-2026-10-007",
        "additional_information": {
            "remitter_full_name": "Kwame Mensah",
            "remitter_date_of_birth": "1988-04-17",
            "remitter_country": "GB",
            "remitter_address": {
                "address_line1": "27 Ashburnham Road",
                "city": "London",
                "postal_code": "SW10 0PL",
                "country": "GB",
            },
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

### 3. If the transfer is paused

If the request and the profile lack sender details, the response looks like this:

```json title="Response (paused)"
{
  "data": {
    "amount": "300",
    "amount_currency": "USD",
    "reference": "3019b5b3-1ad0-4f00-809a-8e52f727ba0d",
    "date_created": "2026-10-13T09:20:11.204Z",
    "transfer_status": "paused",
    "transaction_id": "d12e8ee7-1965-4e9c-8fbf-c341a9c79f0f",
    "fee": "2",
    "fee_currency": "USD",
    "requires_approval": false,
    "status": "awaiting_remitter_information",
    "park_reason": "remitter_information",
    "pause_reason": "sender_details_required",
    "requirements": [
      {
        "type": "remitter_date_of_birth",
        "description": "resume with additional_information.remitter_date_of_birth: the sender's date of birth as YYYY-MM-DD"
      },
      {
        "type": "remitter_address",
        "description": "resume with additional_information.remitter_address: address_line1, city and postal_code of the sender"
      }
    ],
    "expires_at": "2026-10-16T09:20:11Z"
  },
  "error": null
}
```

Each `requirements[].type` names a field to send: `remitter_full_name`, `remitter_date_of_birth`, `remitter_address`, `remitter_country` or `customer_email`. For `customer_email`, add an email to the customer, then resume. The pause ends at `expires_at`, 72 hours later by default.

### 4. Resume it

The id in the path is the transfer `reference`. Use a **new** `Idempotency-Key` on every attempt. The resume call refuses unknown fields.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer/3019b5b3-1ad0-4f00-809a-8e52f727ba0d/resume \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 9e18276b-99b0-4345-b9c6-36c24be4487f' \
     --data '
{
  "additional_information": {
    "remitter_date_of_birth": "1988-04-17",
    "remitter_address": {
      "address_line1": "27 Ashburnham Road",
      "city": "London",
      "postal_code": "SW10 0PL",
      "country": "GB"
    }
  }
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer/3019b5b3-1ad0-4f00-809a-8e52f727ba0d/resume", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "9e18276b-99b0-4345-b9c6-36c24be4487f",
  },
  body: JSON.stringify({
    "additional_information": {
      "remitter_date_of_birth": "1988-04-17",
      "remitter_address": {
        "address_line1": "27 Ashburnham Road",
        "city": "London",
        "postal_code": "SW10 0PL",
        "country": "GB"
      }
    }
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer/3019b5b3-1ad0-4f00-809a-8e52f727ba0d/resume",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "9e18276b-99b0-4345-b9c6-36c24be4487f",
    },
    json={
        "additional_information": {
            "remitter_date_of_birth": "1988-04-17",
            "remitter_address": {
                "address_line1": "27 Ashburnham Road",
                "city": "London",
                "postal_code": "SW10 0PL",
                "country": "GB",
            },
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "transfer_id": "3019b5b3-1ad0-4f00-809a-8e52f727ba0d",
    "status": "approved_awaiting_execution",
    "transfer_status": "pending",
    "resumed": true
  },
  "error": null
}
```

If a detail is still missing, the API returns HTTP 200 with `resumed: false` and the remaining `requirements`. Send those with another new key. For `customer_email`, update the customer with [`PUT /v1/customers/{customer_id}`](/api-reference/customers/put-v1-customers-customer-id), then resume with `{}`.

### 5. Follow it

Use the webhooks, or poll [`GET /v1/transactions/d12e8ee7-1965-4e9c-8fbf-c341a9c79f0f`](/api-reference/transactions/get-v1-transactions-transaction-id) until its `status` is `completed` or `failed`.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `ghanaian account numbers must be 10-16 digits` | The bank account number is too short or too long | Ask for the full number |
| 400 `phone number is required for mobile money payees: invalid input` | The phone number is in `account_number` | Put it in `phone_number` |
| 400 `account number may contain only letters and digits; '+' is not allowed: invalid input` | You sent a `+233…` number in `account_number` | Put it in `phone_number` |
| 400 `the payee is missing required information for this transfer type — please update the payee details` | Gravv's record of the operator or bank lacks the route's code | Contact support with the payee `id` |
| 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 with an operator `institution_id` |
| 400 `transfers to GH/GHS are not available at this time` | The route is not open for your business | Contact support |
| 200 `transfer_status` `paused`, `pause_reason` `sender_details_required` | The request and the profile lacked a sender detail | Resume with the listed fields |
| 400 `remitter_date_of_birth must be a date as YYYY-MM-DD` | The date is in the wrong format, such as `17/04/1988` | Send `1988-04-17` |
| 400 `remitter_date_of_birth cannot be in the future` | The year has a typo | Fix the date |
| 400 `invalid request body: json: unknown field "remitter_email"` | The resume call does not accept this field | Add the email to the customer profile |
| 400 `this transfer is not paused` | The transfer was already resumed, or was never paused | Read the transaction |
| 410 `ERROR_CODE_TRANSFER_PAUSE_EXPIRED` | The pause is past `expires_at` | Send a new transfer with the details included |
| 410 `ERROR_CODE_TRANSFER_PAUSE_CANCELLED` | Gravv cancelled the pause | Send a new transfer |
| 400 `insufficient balance` on resume | The balance was spent while the transfer was paused | Fund the account and resume again |
| `transfer.status.failed` with `reason` `sender_details_expired` | Nobody resumed the transfer before the pause ended. Gravv returned the fee | Send again with the 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

1. `external_account.created`.
2. If the transfer pauses, `transfer.requires_information`, with `reason` `sender_details_required` and the requirements in `limit_details.requirements`.
3. After the resume, `transfer.resumed`.
4. `transfer.status.pending`, several times, then `transfer.status.completed` or `transfer.status.failed`. If a pause runs out, Gravv sends `transfer.status.failed` with `reason` `sender_details_expired` (or `sender_details_cancelled`).

## Testing in sandbox

You can save Ghana payees in sandbox. They are `active`, and wallets get no name check. But Ghana payouts in sandbox return `the payee is missing required information for this transfer type — please update the payee details` today, so you cannot produce a payout or the sender-details pause.

Sandbox does not let you create a customer without `date_of_birth`. Once the route works, use a business sender to try the pause.

## Reference

- [Paused transfers](/platform/transfers/paused-transfers)
- [Resume a paused transfer](/api-reference/transfer/post-v1-transfer-id-resume)
- [Add an external account](/api-reference/external-accounts/post-v1-external-accounts)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
