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

# Settlement

> Paying us: payment instructions, matching, confirmations, and what happens when the amount is wrong.

Every payout is funded by its own settlement. Once a payout reaches
`PENDING_PAYMENT` it carries a **payment instruction** — where to send money, how
much, and by when.

## The instruction

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/payouts/$PAYOUT_ID/payment-instruction" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
{
  "id": "c4d5e6f7-...",
  "payout_id": "a3f1c2d4-...",
  "rail": "CRYPTO",
  "account_unique": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "provider_name": "USDT (TRC-20)",
  "network": "TRC20",
  "expected_amount": 42.32,
  "expected_currency": "USDT",
  "payment_status": "UNPAID",
  "expires_at": "2026-08-16T10:11:00.000Z",
  "paid_at": null
}
```

| Field             | Meaning                                                      |
| ----------------- | ------------------------------------------------------------ |
| `account_unique`  | Where to send: a crypto address, or a virtual account number |
| `expected_amount` | Exactly what to send, in `expected_currency`                 |
| `expires_at`      | Pay before this or the payout ends `EXPIRED`                 |
| `payment_status`  | `UNPAID`, `PAID`, or `EXPIRED`                               |

The same instruction is also returned inline on the payout as
`payment_instruction`, so `GET /v1/payouts/{id}` is usually enough on its own — and
it rides along on the [webhook payload](/reference/events#payload), so the
`payout.pending_payment` event alone gives you everything the sender needs.

<Note>
  Read the address off the instruction every time. Do not cache it against a
  beneficiary or reuse one from an earlier payout — the amount is what lets us
  attribute a payment to the right payout, and paying a stale address puts money
  against the wrong instruction or none at all.
</Note>

## The two rails

<Tabs>
  <Tab title="Crypto">
    `account_unique` is a receiving address belonging to your account, on the
    chain you chose with `settlement_network` (`TRON` or `BSC`).

    Payments wait for network confirmations before the payout advances. The
    threshold is set by the chain, not by us — TRC-20 and BEP-20 clear in about a
    minute in normal conditions. `confirmations_received` on the inflow tracks
    progress.
  </Tab>

  <Tab title="Bank transfer">
    `account_unique` is a virtual account number issued for the payout. Send a
    naira transfer to it from any Nigerian bank.

    Bank transfers confirm on arrival — there is nothing to wait for beyond the
    inter-bank transfer itself. `network` is `null` and confirmations do not apply.
  </Tab>
</Tabs>

## How your payment is matched

We poll for incoming payments continuously and match each one to an open
instruction on **the receiving account and the amount together**.

Amount is part of the key, not a sanity check on it. Two payouts can legitimately
be open against the same receiving address at the same time, and the address alone
cannot say which one a payment is for. We pick the best fit — the largest
`expected_amount` that the payment covers.

<Warning>
  If two open instructions tie exactly on `expected_amount`, the payment is
  genuinely ambiguous and we refuse to guess. It is left unapplied and raised to
  our operations team. A delayed payout is always preferable to crediting the wrong
  one — so send distinguishable amounts, or wait for one instruction to clear
  before opening another on the same rail.
</Warning>

Each payment is recorded exactly once, keyed on the transaction hash or bank
session ID. A provider that notifies us twice cannot produce a double credit.

## Wrong amounts

<AccordionGroup>
  <Accordion title="You send less than expected" icon="arrow-down">
    The payout does **not** advance. An underpayment does not cover any open
    instruction, so it is flagged (`is_underpaid: true` on the inflow) and held for
    our operations team.

    The payout stays in `PENDING_PAYMENT` and is **never** expired while it holds
    your money, even past `expires_at`. Contact support to top up or return it.
  </Accordion>

  <Accordion title="You send more than expected" icon="arrow-up">
    The payout proceeds normally. The excess is recorded as `overpayment_amount`
    on the inflow.

    If the payout later fails, the overpayment comes back with the refund — you are
    refunded what actually arrived, not what was expected.
  </Accordion>

  <Accordion title="You pay after expiry" icon="clock">
    If the instruction already lapsed and the payout is `EXPIRED`, there is no open
    instruction to match against. The payment is held for our operations team and
    returned to you.

    This is the case to avoid: check `expires_at` before releasing funds, not
    after.
  </Accordion>
</AccordionGroup>

## Inspecting settlements

Your settlements are readable as inflows, with `inflows:read`.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/inflows?limit=20" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
{
  "data": [
    {
      "id": "b7e2d1a0-...",
      "rail": "CRYPTO",
      "provider_name": "USDT (TRC-20)",
      "network": "TRC20",
      "amount": 42.32,
      "currency": "USDT",
      "confirmations_received": 20,
      "is_matched": true,
      "matched_at": "2026-08-16T14:38:14.000Z",
      "payout_id": "a3f1c2d4-...",
      "is_underpaid": false,
      "overpayment_amount": 0,
      "created_at": "2026-08-16T14:37:55.000Z"
    }
  ],
  "meta": { "next_cursor": null, "has_more": false, "limit": 20 }
}
```

An inflow with `is_matched: false` has arrived but is not yet attributed to a
payout — either it is still confirming, or it needs manual attribution.

<Note>
  Payer identity is never returned. The sending address or account behind a
  settlement is the payer's PII and is not part of the merchant-facing contract;
  reconcile on `payout_id`, `amount`, and `created_at` instead.
</Note>

## Dispatch to the beneficiary

Once your settlement confirms, we dispatch to the local processor. Every attempt
is its own immutable record — a retry never overwrites a failed attempt.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/payouts/$PAYOUT_ID/outflows" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
[
  {
    "id": "c1d2e3f4-...",
    "payout_id": "a3f1c2d4-...",
    "processor": "neona",
    "status": "FAILED",
    "attempt_number": 1,
    "destination_amount": 500,
    "destination_currency": "GHS",
    "failure_code": "BENEFICIARY_UNREACHABLE",
    "dispatched_at": "2026-08-16T14:38:45.000Z"
  },
  {
    "id": "d2e3f4a5-...",
    "payout_id": "a3f1c2d4-...",
    "processor": "neona",
    "status": "SUCCESSFUL",
    "attempt_number": 2,
    "destination_amount": 500,
    "destination_currency": "GHS",
    "processor_reference": "NEONA-TX-00112",
    "dispatched_at": "2026-08-16T14:41:10.000Z",
    "settled_at": "2026-08-16T14:41:33.000Z"
  }
]
```

A failed attempt with retries remaining is not a failed payout. The payout only
reaches `FAILED` once attempts are exhausted — three by default. Read
`attempt_number` and `failure_code` here when you need to explain to a customer
*why* something took two tries.

Outflows need the `outflows:read` scope.
