> ## Documentation Index
> Fetch the complete documentation index at: https://docs.salesive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet sources

> Let a payment app show the merchant's balance held with it on the Wallet page, without the platform ever moving or summing that money.

## What a wallet source is

A payment app can register one **wallet source** for each installation. It is a display-only row on
the merchant's Wallet page: your app's name for the account, its currency, and a live balance the
platform reads from your endpoint.

The platform never moves this money. It is not a Salesive wallet, it cannot be withdrawn through
Salesive, and no order, payout or fee calculation reads it. The merchant's action on an app source
is **Manage**, which opens your page. **Withdraw** stays on platform wallets only.

## Authentication

```http theme={null}
Authorization: Bearer app_<your-app-access-token>
```

The installation, and therefore the store and the app, comes from the token. A merchant session is
refused with `403`. No OAuth scope is required, because this is your app's own display
configuration, not a write to store data.

## Register a source

```http theme={null}
PUT /api/v1/app/wallet-source
Content-Type: application/json
```

`PUT` creates the source, or replaces the one this installation already has. There is at most one
source per installation.

```bash theme={null}
curl -X PUT https://api.salesive.com/api/v1/app/wallet-source \
  -H "Authorization: Bearer app_<token>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Paystack NGN balance",
    "currency": {
      "code": "NGN",
      "name": "Nigerian Naira",
      "symbol": "NGN",
      "iconUrl": "https://cdn.example.com/ngn.png",
      "decimals": 2
    },
    "balanceUrl": "https://api.example.com/salesive/balance",
    "manageUrl": "https://dashboard.example.com/account",
    "enabled": true
  }'
```

| Field | Required | Rules |
| - | - | - |
| `label` | No | 1 to 40 characters. Your own name for the account, for example `Paystack NGN balance`. Omit it and the platform uses `<App name> <CODE>`. |
| `currency.code` | Yes | 2 to 10 letters or digits, uppercased. Any code is allowed, including one the platform also uses. |
| `currency.name` | Yes | 1 to 40 characters. |
| `currency.symbol` | Yes | 1 to 6 characters. |
| `currency.iconUrl` | Yes | HTTPS URL, at most 2,048 characters. |
| `currency.decimals` | Yes | Whole number from 0 to 8. |
| `balanceUrl` | Yes | HTTPS URL the platform POSTs to for a balance. |
| `manageUrl` | Yes | HTTPS URL the merchant is sent to. |
| `enabled` | No | Default `true`. The merchant can also turn it off. |

All three URLs pass the same SSRF guard as webhook URLs: HTTPS only, and localhost, private and link
local addresses are refused. The check runs at save time and again before every balance call,
because DNS can change.

Changing the currency code, the decimals or `balanceUrl` clears the stored last balance, because the
old number no longer describes the source.

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Wallet source saved",
  "data": {
    "source": {
      "id": "6910f3a2c4d5e6f7a8b9c0d1",
      "label": "Paystack NGN balance",
      "currency": {
        "code": "NGN",
        "name": "Nigerian Naira",
        "symbol": "NGN",
        "iconUrl": "https://cdn.example.com/ngn.png",
        "decimals": 2
      },
      "balanceUrl": "https://api.example.com/salesive/balance",
      "manageUrl": "https://dashboard.example.com/account",
      "enabled": true,
      "lastBalance": null,
      "lastBalanceAt": null,
      "lastError": null,
      "createdAt": "2026-11-08T09:00:00.000Z",
      "updatedAt": "2026-11-08T09:00:00.000Z"
    }
  }
}
```

`GET /api/v1/app/wallet-source` returns the same `source` object, or `404` when this installation
has not registered one. `DELETE /api/v1/app/wallet-source` removes it.

## The balance call

When the merchant opens the Wallet page, the platform POSTs to `balanceUrl` for each enabled source.
The result is cached for 60 seconds per store and source, so a page refresh does not hammer your
endpoint.

```http theme={null}
POST https://api.example.com/salesive/balance
Content-Type: application/json
X-Salesive-Signature: t=1762592400,v1=<base64 HMAC>

{
  "shopId": "6680aabbccddeeff00112200",
  "installationId": "66b1f0a3c2d4e5f6a7b8c9d1",
  "currency": "NGN"
}
```

The signature is the same scheme as [webhook signature v2](https://docs.salesive.com/apps-api/webhooks.md#verify-the-signature).
`v1` is the base64 HMAC-SHA256 of `<t>.<raw body>` using your app's webhook secret for the
installation's environment. `currency` is the declared code, so an app that holds more than one
currency answers for the right account.

| Rule | Value |
| - | - |
| Timeout | 5 seconds. |
| Redirects | Never followed. A `3xx` answer is an error. |
| Response size | At most 16 KB. |
| Response body | `{ "amount": "1250.40" }`, with an optional `asOf` ISO 8601 timestamp. `amount` is a decimal string, at most 40 characters. |
| Success | A `2xx` status with a readable body. |
| Failure | A timeout, an unreachable host, a non `2xx` status, a redirect, an oversized or unreadable body, or an invalid amount. |

> [!NOTE]
>
> Store amounts follow [Money and units](https://docs.salesive.com/apps-api/introduction.md#money-and-units): a plain number in
> major units. The balance your app reports is the one exception, a decimal string, because it is
> your own value and the platform stores it as you send it.

The balance is stored as your decimal string, so a large value never loses precision. On a failed
refresh the merchant still sees the **last good balance and its timestamp**, with `error` set to a
short message. It is never replaced with zero. A source that has never reported returns `null`.

## The label rule

An app source may use any currency code, including one the store's platform wallet already uses.
Holding the merchant's naira float is normal for a provider, and refusing the code would be wrong.

Identity is by **source**, never by currency code:

* A platform wallet and an app source with the same code are two rows. They are never merged, and
  their balances are never summed into one total.
* When the code matches a platform wallet the store holds, the app row carries **your app's label**.
  If you sent no label, the server substitutes `<App name> <CODE>`, for example `Paystack NGN`.
* The dashboard shows an app label qualified by the app name, so the row can never read as the
  platform wallet.
* Platform entries keep the plain currency name.

## The merchant endpoint

The merchant's Wallet page reads:

```http theme={null}
GET /api/v1/wallet/sources
Authorization: Bearer <merchant-session-token>
x-shop-id: <shop-id>
```

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Wallet sources retrieved",
  "data": {
    "platform": [
      {
        "source": "platform",
        "currency": {
          "code": "NGN",
          "name": "Nigerian Naira",
          "symbol": "NGN",
          "iconUrl": null,
          "decimals": 2
        },
        "balance": 150000,
        "label": "Nigerian Naira"
      }
    ],
    "apps": [
      {
        "source": "app",
        "appName": "Paystack",
        "label": "Paystack NGN balance",
        "currency": {
          "code": "NGN",
          "name": "Nigerian Naira",
          "symbol": "NGN",
          "iconUrl": "https://cdn.example.com/ngn.png",
          "decimals": 2
        },
        "balance": "1250.40",
        "asOf": "2026-11-08T08:59:00.000Z",
        "error": null,
        "enabled": true,
        "sourceId": "6910f3a2c4d5e6f7a8b9c0d1",
        "manageUrl": "https://dashboard.example.com/account"
      }
    ]
  }
}
```

`platform` is the store's own wallets and `apps` is one entry per active installation that registered
a source. The two lists are separate on purpose.

A disabled source is still listed, so the merchant can turn it back on. It keeps its last known
balance, is not called, and carries no error. The merchant turns a source on or off with
`PATCH /api/v1/wallet/sources/:sourceId` and a body of `{ "enabled": true }` or
`{ "enabled": false }`. The source must belong to an active installation on the current store,
otherwise the call answers `404`.

## Errors

| Status | Meaning |
| - | - |
| `400` | A missing or invalid field. A shape error answers with a message; a check the service runs adds a `code` such as `invalid_currency`, `invalid_label`, `invalid_url` or `unsafe_url`. |
| `403` | The caller is not an installed app, or the installation is missing. |
| `404` | This installation has no source (`GET`, `DELETE`), or the merchant's source is not on this store (`PATCH`). |

## Rules to build by

* The balance is **display only**. No order, payout, platform fee or app charge reads it.
* The platform **never moves this money**. There is no withdraw, transfer or debit on an app source.
* The platform **never sums** an app balance with a platform wallet, even when the currency code
  matches.
* An uninstall removes the row from the merchant's view, because the source belongs to the
  installation.

## Next steps

> [card] [Platform billing](https://docs.salesive.com/apps-api/platform-billing.md)
>
> The fee on orders your provider captures, and the monthly invoice.

> [card] [Webhooks](https://docs.salesive.com/apps-api/webhooks.md)
>
> The signature scheme your balance endpoint shares.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.