---
title: "Pay a bank account in Nigeria"
description: "Check the account holder's name, save a Nigerian bank payee and send US dollars that arrive as naira."
---
Use this recipe to check the account holder's name, save a Nigerian bank payee, and send US dollars that arrive as naira.

**Who it is for.** A US remittance app whose user, Ada Bello in Houston, sends dollars to her brother Chinedu's bank account in Lagos.

**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. Ada's USD account holds 1,000.00 USD.
2. She sends 250.00 USD. Say the fee on your plan is 1.50 USD, so 251.50 USD leaves her account.
3. Say the rate is 1,530.00 NGN per USD. The transfer response shows 382,500.00 NGN for Chinedu in `local_amount`.
4. His bank credits the naira, and you receive `transfer.status.completed`.

## Before you start

- Ada is one of your customers ([`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id)).
- Her USD account is `active` and holds the amount plus the fee ([`GET /v2/accounts/{id}`](/api-reference/accounts-v2/get-v2-accounts-id)).
- You have Chinedu's 10-digit account number (NUBAN) and his bank's name.

## Find out what is required

### The currency

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/transfer/supported-countries?iso_code=NG' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer/supported-countries?iso_code=NG", {
  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/transfer/supported-countries?iso_code=NG",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "countries": [
      {
        "iso_code": "NG",
        "name": "Nigeria",
        "iso3_code": "NGA",
        "currency_code": "NGN"
      }
    ]
  },
  "error": null
}
```

The payee's currency is `NGN`. `iso_code` matches part of the code, so `N` alone matches many countries.

### The bank

The default page size is 10, so the example asks for 100 per page. Send the country code in upper case.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=NG&institution_type=bank&items_per_page=100' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts/institutions?country_iso_code=NG&institution_type=bank&items_per_page=100", {
  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=NG&institution_type=bank&items_per_page=100",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response (trimmed)"
{
  "data": {
    "items": [
      {
        "account_number_type": "bank_account_number",
        "country_iso_code": "NG",
        "currency": "NGN",
        "id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
        "max_amount": 10000,
        "min_amount": 1,
        "name": "GT Bank"
      }
    ],
    "items_per_page": 100,
    "page": 1,
    "total_items": 85,
    "total_pages": 1
  },
  "error": null
}
```

Store the bank's `id`. You send it as `institution_id`. `min_amount` and `max_amount` are reference values, not limits. Institution ids can differ between sandbox and live, so always take them from this list.

### The rate

[`GET /v1/transfer/rates?currency=NGN`](/api-reference/transfer/get-v1-transfer-rates) returns the indicative rate. Read the item whose `direction` is `USD_NGN`. This call does not lock the rate.

### The account number

The account number is exactly 10 digits. You do not need a routing number or an address.

## Steps

### 1. Check the account holder's name (optional)

Show Ada the name the bank holds before she pays. This call does not need an `Idempotency-Key`.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/external-accounts/verify \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --data '
{
  "account_number": "0123456789",
  "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts/verify", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "account_number": "0123456789",
    "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/external-accounts/verify",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "account_number": "0123456789",
        "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "verified": true,
    "account_holder_name": "CHINEDU OKECHUKWU EZE",
    "verification_source": "provider"
  },
  "error": null
}
```

Trust the name only when `verification_source` is `provider`. `local` means the format looks right, not that the bank confirmed it. The verify call covers bank accounts only, never phone numbers.

### 2. Look for an existing payee

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/external-accounts?customer_id=bc426080-4122-4cb1-8d46-ff4ffda26b44&search=0123456789' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts?customer_id=bc426080-4122-4cb1-8d46-ff4ffda26b44&search=0123456789", {
  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?customer_id=bc426080-4122-4cb1-8d46-ff4ffda26b44&search=0123456789",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

If an `active` payee with this account and bank exists, reuse its `id` and skip step 3. Gravv's duplicate check can miss a match when the bank's name check has replaced the name, so look first.

### 3. Save the payee

<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: 2367a2d6-f34c-43fb-bdb3-0e939145af9c' \
     --data '
{
  "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
  "payee_type": "bank_account",
  "currency": "NGN",
  "account_name": "Chinedu Eze",
  "account_number": "0123456789",
  "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
  "institution_country_iso_code": "NG"
}
'
```
```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": "2367a2d6-f34c-43fb-bdb3-0e939145af9c",
  },
  body: JSON.stringify({
    "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
    "payee_type": "bank_account",
    "currency": "NGN",
    "account_name": "Chinedu Eze",
    "account_number": "0123456789",
    "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
    "institution_country_iso_code": "NG"
  }),
});
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": "2367a2d6-f34c-43fb-bdb3-0e939145af9c",
    },
    json={
        "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
        "payee_type": "bank_account",
        "currency": "NGN",
        "account_name": "Chinedu Eze",
        "account_number": "0123456789",
        "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
        "institution_country_iso_code": "NG",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response (HTTP 200)"
{
  "data": {
    "account_name": "CHINEDU OKECHUKWU EZE",
    "account_number": "0123456789",
    "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
    "currency": "NGN",
    "date_created": "2026-10-13T22:34:50.223013Z",
    "id": "14418474-20a5-4553-86af-17ac34dde144",
    "institution_country_iso_code": "NG",
    "institution_id": "2ce313c8-de20-48dd-bfc6-949b8fd5ad84",
    "institution_name": "GT Bank",
    "institution_type": "bank",
    "payee_type": "bank_account",
    "status": "active"
  },
  "error": null
}
```

The API returns HTTP 200, not 202, and the payee is `active` at once. `account_name` is the holder's name as the bank records it, in upper case, not the name you sent.

### 4. Send the money

Leave `rail` out of the request.

<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: a70ed229-dbae-470a-84b4-f767c77f7a9b' \
     --data '
{
  "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
  "source": {
    "source_type": "internal_account",
    "id": "d070ac47-fd7a-4c5a-9a42-badd462c0420"
  },
  "destination": {
    "destination_type": "external_account",
    "id": "14418474-20a5-4553-86af-17ac34dde144"
  },
  "amount": "250.00",
  "description": "Family support October",
  "client_reference": "ada-ng-2026-10-001"
}
'
```
```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": "a70ed229-dbae-470a-84b4-f767c77f7a9b",
  },
  body: JSON.stringify({
    "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
    "source": {
      "source_type": "internal_account",
      "id": "d070ac47-fd7a-4c5a-9a42-badd462c0420"
    },
    "destination": {
      "destination_type": "external_account",
      "id": "14418474-20a5-4553-86af-17ac34dde144"
    },
    "amount": "250.00",
    "description": "Family support October",
    "client_reference": "ada-ng-2026-10-001"
  }),
});
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": "a70ed229-dbae-470a-84b4-f767c77f7a9b",
    },
    json={
        "customer_id": "bc426080-4122-4cb1-8d46-ff4ffda26b44",
        "source": {
            "source_type": "internal_account",
            "id": "d070ac47-fd7a-4c5a-9a42-badd462c0420",
        },
        "destination": {
            "destination_type": "external_account",
            "id": "14418474-20a5-4553-86af-17ac34dde144",
        },
        "amount": "250.00",
        "description": "Family support October",
        "client_reference": "ada-ng-2026-10-001",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "250",
    "amount_currency": "USD",
    "reference": "8b56ead0-bbe7-4bb2-aec8-a61ca66bd939",
    "date_created": "2026-10-13T09:15:22.481Z",
    "transfer_status": "pending",
    "transaction_id": "ded00040-b19c-46c9-8e43-b8729bc24a54",
    "local_amount": "382500",
    "local_currency": "NGN",
    "fee": "1.5",
    "fee_currency": "USD",
    "requires_approval": false
  },
  "error": null
}
```

Before you tell Ada the money is sent, check these points:

- `transfer_status` is `pending`. If it is `paused`, see [Get a stuck remittance moving](/recipes/remittance-and-crypto/get-a-stuck-remittance-moving).
- `local_amount` makes sense for the rate. Here, 250 times about 1,530 is about 382,500. If it looks like 250 times 1, or is far off, do not show it. Contact support with the `reference`.
- Store `reference` for a resume and for the webhooks, and `transaction_id` for polling.

### 5. Wait for the result

Listen for the webhooks, or poll [`GET /v1/transactions/ded00040-b19c-46c9-8e43-b8729bc24a54`](/api-reference/transactions/get-v1-transactions-transaction-id) every 30 to 60 seconds until `status` is `completed` or `failed`.

On the transaction, `transaction_type` is `global_remittance` and `channel` is `bank_transfer`. `metadata` names the recipient and the sender but not the local amount. Keep `local_amount` from the transfer response, and read the fee Gravv charged from the transaction.

Gravv promises no timing. A route that keeps reporting "processing" stays `pending`.

### Paying from an NGN account

If Ada holds an NGN account, use its id as `source.id`. No currency exchange takes place. When you pay from an NGN account:

- The payee must receive NGN.
- The amount has at most 2 decimal places.
- A zero balance blocks payouts.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `nigerian account numbers must be exactly 10 digits (NUBAN)` | The number is not 10 digits | Ask for the 10-digit number, then resend with a new `Idempotency-Key` |
| 400 `account number must not contain spaces` | The number has spaces or dashes | Send digits only |
| 400 `account verification failed: bank could not resolve account holder: invalid input` | The bank found no such account | Check the number and the bank |
| 400 `account verification temporarily unavailable; please retry: invalid input` | The name check is down | Retry later with a new key |
| 400 `a recipient with the same account name, account number and bank already exists`, with `data.payee_id` | The payee already exists. Deleted and failed payees count | Use `data.payee_id` |
| 400 `transfers to NG/NGN are not available at this time` | The route is not open for your business | Contact support |
| 400 `transfers to NG/NGN are not supported at this time` | No route can pay this country and currency now | Contact support |
| 400 `transfers from this account to NG/NGN are not supported at this time` | The source account cannot pay this route | Pay from a different account |
| 400 `this payee is not yet set up for transfers — please contact support to complete its setup` | The payee has no `institution_id` from the institutions list | Save the payee with an `institution_id` |
| 400 `the payee is missing required information for this transfer type — please update the payee details` | The route needs a bank code that Gravv's record of the bank lacks. Your fields are usually fine | Contact support with the payee `id`. Saving the payee again does not help |
| 400 `payee … is not active: deleted` | The payee was deleted | Save a new payee |
| 400 `insufficient balance` | The amount plus the fee is more than the balance | Fund the account, then retry with a new key |
| 400 `FX rate unavailable for currency conversion` | No rate is available right now | Retry shortly with a new key |
| 400 `the selected source account cannot be used for an international transfer` | This account cannot send abroad | Use the customer's USD account |
| 400 `the selected source account is not compatible with this transfer — a crypto wallet account is required` | The route pays only from a wallet-backed account | Use a wallet-backed USD account |
| 400 `payee type "bank_account" does not accept ACH/Wire transfers` | You sent `"rail": "wire"` | Leave `rail` out |
| `local_amount` far from the published rate | A wrong rate on Gravv's side | Do not show it. Contact support with the `reference` before the payout completes |
| `transfer.status.failed`, transaction `failed`, and a `transfer_refund` row | The bank refused the payout | Gravv refunds the amount and fee. Check the details and send again |
| `transfer.status.failed`, but the transaction is still `pending` | The transfer is in manual review. The outcome was unclear, so a person checks it | Do not send it again. Contact support with the `reference` |
| `transfer.status.returned` after `completed` | The bank sent the money back | Gravv refunds the customer. Fix the payee and send again |

## Webhooks you will receive

1. `external_account.created`, with `event_data.status` `active`.
2. `transfer.status.pending`, several times, one per internal step, with `workflow_type` `global_remittance`. Treat each one as a sign that the transfer is still in progress.
3. `transfer.status.completed` or `transfer.status.failed`. Rarely, `transfer.status.returned` later.

Each `event_data` carries `amount`, `associated_transactions`, `client_reference`, `destination_id` (the payee), `reference`, `status`, `transfer_status` and `workflow_type`.

## Testing in sandbox

- Every naira account check returns `account_holder_name` `SIM ACCOUNT HOLDER`, and the payee takes that name.
- A 5.00 USD payout from a USDC wallet (`source_type` `internal_crypto_wallet`) to a Nigerian bank payee completed in about 2 minutes.
- An NGN account funded with [`POST /v2/accounts/simulate/deposit`](/api-reference/accounts-v2/post-v2-accounts-simulate-deposit) can pay an NGN bank payee end to end.
- A new sandbox customer cannot open an NGN account until its KYC is `completed`.
- New sandbox USD accounts can stay in review, so a payout from a USD account may not run in sandbox.

## Reference

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