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

# How it works

> End to end escrow lifecycle: create customers or guest payers, fund a hold, release or refund, then pay out.

> [!TIP]
>
> Prefer [webhooks](/guides/webhooks) over polling. After you create checkout or move a hold, wait for the event. Do not hammer `GET /escrows/{id}`.

This is the product, end to end. You are the merchant. A **payer** is paying. A **payee** is receiving on release. Amounts are naira integers.

Money in a hold is **ours to move by API**, not the customer's bank. Release credits the payee's `available`. A bank payout is a later `POST /withdrawals`.

## 1. Create customers

`POST /customers` for the **payee** (and for a registered payer). You get identity, `walletId`, and zero balances. `account` is `null`. No webhook.

The **payer** may instead be a **guest** on create. Pass `guest: { email, name?, phone? }` on the payer party. No Demat customer or wallet is created for guests.

Look up NIN/BVN with `POST /identity/lookups` if you need the name. We do not store KYC on the customer.

## 2. Create the hold

`POST /escrows` with your unique `reference`, `amount`, `currency`, and `parties`: one `payer`, one `payee`. Payee must use `customerId`. Payer uses `customerId` **or** `guest`, never both.

Default status is `awaiting_funding`. Nothing is locked. Webhook: `escrow.created`.

`debitPayer: true` locks a **registered** payer's `available` now. Not allowed for guest payers. They must already have enough or the request fails **422** and **no hold is created**. Success: `escrow.created` then `escrow.funded`. Do not also pass `checkout: true`.

Pass `checkout: true` on the same create to also return a checkout URL (uses the payer's email, including guest email).

A transfer into a NUBAN never locks this hold. To lock that money later, create a new hold with `debitPayer: true`.

## 3. Fund it (if you did not debit)

If you did not pass `checkout: true` on create, `POST /checkout/sessions` with `escrowId`. Amount and currency must match. The payer opens `checkoutUrl` (card, bank, bank\_transfer, ussd).

A second create for the same unfunded hold returns the **same** pending session. Use this to retry a failed checkout or start one later. List sessions with `GET /checkout/sessions`.

Wait for webhooks, not the browser callback. Paid and hold still awaiting leads to `payment.succeeded` then `escrow.funded` (status `held`). Paid after you cancelled leads to `wallet.credited` on the payer; the hold stays cancelled.

## 4. After it is held

| You want | Call | Money | Webhook |
| - | - | - | - |
| Payee earned it | `POST /escrows/{id}/release` | Held to payee `available` | `escrow.released` or `escrow.partially_released` |
| Payer gets it back | `POST /escrows/{id}/refund` | Registered: held to payer `available`. Guest: provider refund, then on `refund.processed` liability to clearing | `escrow.refunded` (after processed for guests) |
| Deal never funded | `POST /escrows/{id}/cancel` | Nothing | `escrow.cancelled` |
| Freeze while you decide | `POST /escrows/{id}/disputes` | Stays held | `dispute.opened` |
| Unfreeze and move | `POST .../disputes/{id}/resolve` | Per `action` | `dispute.resolved` plus the money move events |

Omit `amount` on release or refund to move everything still held. Pass a smaller amount to leave the rest. You can release 250 then refund 150.

Release is not a bank transfer. Withdraw from `available` later.

## 5. Pay out to a bank

`POST /withdrawals` with `customerId`, `accountNumber`, `bankCode`, `amount`, and `reference`. We resolve the account and debit that customer's `available`. Listen for `withdrawal.completed` or `withdrawal.failed`. Optional: preview the name first with `POST /identity/bank-accounts/verify`.

## Status cheat sheet

Escrow, dispute, checkout, and withdrawal statuses are listed on the **escrows**, **checkout**, and **payouts** tags in the [API Reference](/api-reference). Rule of thumb: if the hold is `disputed`, you cannot release or refund until you resolve. If it is `awaiting_funding`, you cannot release or refund. Cancel or wait for funding.


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