---
title: Wallet transfers
description: Send money from your wallet to another iSmartPay business.
---

A wallet transfer moves money from your wallet to another iSmartPay business's wallet. You find the business by its handle.

> **Warning**
>
> A transfer needs no PIN and no one approves it. Any key with `wallettransfer.write` can pay any business that has a handle, up to your limits. Give that permission only to keys that need it, and revoke a key at once if it leaks. The API has no way to reverse a transfer.

## Handles

A handle is a business's public name for receiving transfers. A business needs one to be paid. An owner claims it in the console, and only a verified business can claim one. A key cannot claim or change a handle.

The same handle works in the merchant console and the developer console.

## Check the recipient

Look up the handle first, so you can show who you are about to pay:

```http
GET /v1/recipients/acme-stores
```

```json
{
  "handle": "acme-stores",
  "businessName": "Acme Stores",
  "ownerName": "Ama Mensah",
  "handleSince": "2026-08-14T09:30:00Z"
}
```

- The handle must match exactly.
- `businessName` is the handle's display name, else the trading name, else the registered name. It can be missing when it is not known.
- Your own handle is `400 own_handle`. An unknown handle is `404`.

## Send

```http
POST /v1/transfers
Idempotency-Key: supplier-7731
```

```json
{
  "reference": "supplier-7731",
  "handle": "acme-stores",
  "amount": "25.00",
  "currency": "GHS"
}
```

- `reference` is yours, and unique for each transfer.
- Do not send a `pin`. Unknown fields are refused.
- Your own handle is `400 own_handle`. An unknown handle is `404`.

This one request holds the money and confirms it. A `200` with `status` `completed` means the money moved:

```json
{
  "reference": "supplier-7731",
  "amount": "25.00",
  "currency": "GHS",
  "status": "completed",
  "recipientHandle": "acme-stores",
  "recipientName": "Acme Stores",
  "createdAt": "2026-10-05T10:00:00Z"
}
```

`recipientName` can be missing when it is not known. Other statuses, such as `held`, can appear.

## Retrying

If a request fails, times out or returns `5xx`, send the same reference and body again. The transfer carries on from where it stopped and is never sent twice. Use the same `Idempotency-Key` too.

Do not retry with a new reference. A new reference is a new transfer.

A reference already used for a different transfer is `409 reference_reused`.

## Fees and limits

Wallet transfers carry no fee. They count toward your outbound limits. Check `GET /v1/limits` before a large transfer.

## Read a transfer

```http
GET /v1/transfers/supplier-7731
```

This returns the transfer by your reference.

## The receiving business

When a transfer completes, the recipient gets a `transfer.received` webhook:

```json
{
  "id": "evt_8d2c41f07a9e4b3c9d1e6f5a4b3c2d1e",
  "type": "transfer.received",
  "createdAt": "2026-10-05T12:00:00Z",
  "data": {
    "reference": "supplier-7731",
    "amount": "25.00",
    "currency": "GHS",
    "senderName": "Acme Stores",
    "completedAt": "2026-10-05T12:00:00Z"
  }
}
```

- `reference` is the one the sender chose.
- `senderName` is the sender's trading name, else registered name. It can be missing when it is not known.
- It is sent once per transfer, only when the money is in your wallet.

The transfer also shows in `GET /v1/transactions`, as a `wallet_transfer` with `direction` `in`. See [Transactions and events](/guides/transactions-and-events).

## Permissions

| To                                    | The key needs          |
| ------------------------------------- | ---------------------- |
| Look up a recipient or send           | `wallettransfer.write` |
| Read a transfer                       | `wallettransfer.read`  |

## Webhooks

Subscribe an endpoint to these events in the console:

| Type                      | Sent when                                                          |
| ------------------------- | ------------------------------------------------------------------ |
| `transfer.status_changed` | A transfer you sent moves to a new status, such as completed or failed. |
| `transfer.received`       | Another business's transfer to you completed. |

`transfer.status_changed` goes to the sending business only. `data` is the transfer as it stood when the event was sent. It does not carry the recipient's handle or name. Fetch the transfer by reference when you need them.

If your webhook endpoints were set up before wallet transfers arrived, edit or add an endpoint in the console once. Until then, transfer events are not sent to your business. See [Webhooks](/guides/webhooks).

## Errors

As well as the [common errors](/guides/errors):

| Status | Code               | What to do                                                            |
| ------ | ------------------ | --------------------------------------------------------------------- |
| 400    | `own_handle`       | You cannot pay yourself. Check the handle.                            |
| 400    | `transfer_refused` | The money could not be held. Nothing left your wallet. Read `message`. |
| 409    | `reference_reused` | The reference belongs to another transfer. Use a new one.             |
| 409    | `transfer_not_held`| The hold can no longer be confirmed. Read the transfer to see where it stands. |
| 403    | `transaction_limit_exceeded`, `daily_limit_exceeded`, `monthly_limit_exceeded` | The transfer is over your outbound limits. Check `GET /v1/limits`. |
