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

> ## Agent Instructions
> Money is always a decimal string at the currency's precision (for example "29.00"), paired with an ISO 4217 currency field. Never use minor units.
> Build against the sandbox first: base URL https://sandbox-api.bachs.io with sk_sandbox_ keys. Production is https://api.bachs.io with sk_live_ keys; going live is a key swap.
> Treat webhooks (for example collection.succeeded) as the source of truth for fulfilment, never client-side events or redirects.
> Subscriptions are created by completing a checkout for a recurring product. There is no direct create-subscription endpoint.
> IDs carry resource prefixes (cust_, prod_, sub_, chk_, inv_, ref_) and timestamps are ISO 8601 UTC.

# Build an escrow platform

> Hold a buyer's payment until the deal is done, then release it to the seller or refund the buyer.

An escrow platform protects both sides of a deal. The buyer pays upfront, so the seller knows the money is real. The money waits with you until the buyer gets what they paid for. Then you release it to the seller, or refund the buyer if the deal falls through.

You can run this on Bachs today, with no special setup from us. This page explains how it works in plain words. If you have a developer, or an AI coding assistant, the last sections give them what they need to build it.

> [!NOTE]
>
> Bachs moves and holds the money. Your platform decides when to release it or refund it, and your own terms with buyers and sellers say what "deal done" means. Bachs does not make that decision for you.

## How it works

```mermaid theme={"dark"}
flowchart LR
    A[Buyer pays] --> B[Money held in your Bachs balance]
    B -->|Buyer confirms delivery| C[Released to the seller]
    B -->|Deal falls through| D[Refunded to the buyer]
```

**1. Add the seller**

Each seller gets their own sub account on your platform. They tell us who they are and where to send their money. It only takes a few details, because sellers on your platform receive money but don't take payments themselves.

**2. The buyer pays you**

The buyer pays through a Bachs checkout, by card, bank transfer, mobile money or crypto. The payment belongs to your platform, not the seller, so it lands in your Bachs balance.

**3. The money is held**

The money stays in your balance, out of the seller's reach. Let the seller know the deal is paid for, so they can deliver with confidence.

**4. Release or refund**

When the buyer confirms delivery, you send the seller their share and keep your commission. If the deal falls through, you refund the buyer in full instead.

**5. The seller withdraws**

Once released, the money is the seller's. They can withdraw it to their own bank account, mobile money wallet or crypto wallet.

## A worked example

Kemi buys a used laptop from Tunde on your platform for ₦250,000. Your commission is ₦10,000.

| What happens | Money |
| - | - |
| Kemi pays your platform | ₦250,000 |
| Bachs's processing fee, taken from the payment | ₦2,000 |
| Held in your balance until Kemi confirms delivery | ₦248,000 |
| Released to Tunde | ₦240,000 |
| Your platform keeps | ₦8,000 |

If Tunde never ships the laptop, you refund Kemi her full ₦250,000 instead. Bachs's ₦2,000 processing fee is not returned, so your platform covers it. Naira processing fees are capped at ₦2,000 per payment. See [Fees](https://docs.bachs.io/for-you/fees.md) for every rate.

## Releasing in stages

Some deals are paid in parts, such as a freelance project with three milestones. The buyer pays once for the whole job. You release each part as it's finished. If the job stops halfway, you refund the part you haven't released.

## Three things to get right

> [accordion] Held money sits next to your own earnings
>
> Held deals and your platform's commission share one Bachs balance. Keep your own record of how much you're holding for open deals, and never withdraw below it. Turn off automatic payouts on your platform's balance, or they could send held money to your bank along with your earnings. See [Automatic payouts](https://docs.bachs.io/guides/payouts/payout-schedules.md).

> [accordion] Payments need to settle before you can release them
>
> A payment can't be released the moment it's made. It first settles into your balance. Naira payments are available straight away. Card payments in US dollars take two days. See [When your funds become available](https://docs.bachs.io/for-you/fees.md#when-your-funds-become-available).

> [accordion] Write your release rules down
>
> Decide when money is released, and put it in your terms. For example: released when the buyer confirms delivery, or automatically three days after delivery if the buyer raises no problem. Clear rules prevent most arguments.

## When things go wrong

> [accordion] The deal falls through before release
>
> Refund the buyer from your balance. Nothing has gone to the seller yet, so there's nothing to recover.

> [accordion] The buyer wants a refund after you released the money
>
> The refund comes out of your platform's balance, not the seller's. You can take the seller's share back from their sub account, but only while it's still there. Once the seller has withdrawn it, Bachs can't recover it for you. This is why it pays to release only when you're sure.

> [accordion] The buyer disputes the payment with their bank
>
> The payment is your platform's, so the dispute is too, even after release. The disputed amount is set aside from your balance while it's open, and a dispute fee applies. Pause the deal and send your evidence before the deadline. See [Disputes](https://docs.bachs.io/guides/disputes/overview.md).

> [accordion] The seller hasn't finished signing up
>
> You can hold the money for as long as the deal needs. But you can only release it once the seller's sub account is verified, and they can only withdraw once their payout account is approved.

## Before you go live

* [ ] Your Bachs account is verified, and Connect is switched on. See [Become a platform](https://docs.bachs.io/connect/become-a-platform.md).
* [ ] Automatic payouts are off on your platform's balance.
* [ ] You keep a record of how much you're holding for open deals.
* [ ] Your release and refund rules are in your terms with buyers and sellers.
* [ ] Your team knows refunds and disputes come out of your platform's balance, even after release.

## Build it with an AI assistant

The quickest way to build this is to copy the prompt below into an AI coding assistant, such as Claude Code or Cursor, that can see your app's code. It reads these docs and builds the whole flow. For more ready-made prompts, see [Build with AI](https://docs.bachs.io/build/ai/prompts.md).

> [accordion] Show the prompt
>
> ```text Prompt theme={"dark"}
> Add a hold-then-release payment flow with Bachs Connect. Inspect our
> onboarding, deals or orders, authentication, database, and payment code.
> Confirm that our platform owns the charge and holds it until the deal is
> done. Ask for missing currency, commission, release-rule or
> seller-onboarding decisions.
>
> Read the SDK's current scope at https://github.com/bachsdev/bachs-node.
> Connect account context, seller onboarding and transfers are not supported
> by the current SDK. Use documented API calls for these operations; never
> invent SDK methods or force unsupported fields through type casts. A Node.js
> server can use the SDK's webhooks.constructEvent for signature verification.
>
> Read:
> https://docs.bachs.io/build/use-cases/escrow.md
> https://docs.bachs.io/connect/split-payments/separate-transfers.md
> https://docs.bachs.io/connect/transfers.md
> https://docs.bachs.io/connect/balances.md
> https://docs.bachs.io/connect/refunds.md
> https://docs.bachs.io/connect/disputes.md
> https://docs.bachs.io/connect/payouts.md
> https://docs.bachs.io/guides/webhooks/overview.md
> https://docs.bachs.io/guides/idempotency.md
>
> Use https://sandbox-api.bachs.io with BACHS_API_KEY on the server. Omit
> checkout return URLs for local testing; configured redirects must be public.
>
> Create and save a recipient sub account for each seller, and implement the
> documented onboarding path. Create the checkout as our platform with no
> transfer_data and no X-Account-Id, linked to our deal. Never use
> transfer_data.destination: it pays the seller automatically at settlement.
>
> Store a deal state: funded, held, released, refunded, disputed. Mark funded
> from a verified checkout.completed and save the charge id. Release only after
> the charge has settled into available_balance, with POST /v1/transfers to the
> seller, transfer_group set to our deal id, and an Idempotency-Key derived from
> the deal. Keep the commission by transferring less than the buyer paid. Refund
> unreleased deals with POST /v1/refunds. Support partial releases for
> milestones, tracking the released total per deal in our database.
>
> Verify X-Bachs-Signature-V2 on the raw body, with timestamp tolerance and all
> v1 signatures. Deduplicate events durably, keep state updates recoverable,
> and return 5xx on processing failure.
>
> Track the total held per currency and never withdraw our platform balance
> below it. Gate releases on the seller's transfers capability and withdrawals
> on payouts and a usable destination. A payout create response does not
> confirm delivery. Keep secrets server-side and amounts as decimal strings.
>
> Test a paid deal released in full, a deal refunded before release, a
> milestone deal released in parts, a release before settlement, duplicate
> events, a retried release, and a refund after release. Report which tests
> used mocks and which ran in sandbox, remaining setup, and how to reconcile
> held money with our balance.
> ```

## For developers

> [accordion] How each step maps to the Bachs API
>
> Every call runs in the sandbox at `https://sandbox-api.bachs.io` with an `sk_sandbox_` key. Going live is the same calls against `https://api.bachs.io`.
>
> | Step | Call | Key detail |
> | - | - | - |
> | Add the seller | `POST /v1/accounts` | `configuration: { "recipient": {} }`. See [Create an account](https://docs.bachs.io/connect/guides/create-an-account.md). |
> | The buyer pays you | `POST /v1/checkout-sessions` | Send no `transfer_data` and no `X-Account-Id`, so the charge is your platform's. A destination charge would pay the seller at settlement, leaving nothing to hold. |
> | Mark the deal funded | `checkout.completed` webhook | Save `data.charge.id` against the deal. |
> | Wait for settlement | `GET /v1/balances` | Release only once the amount is in `available_balance`, or the transfer fails with `INSUFFICIENT_BALANCE`. |
> | Release | `POST /v1/transfers` | `destination` is the seller's account id. Set `transfer_group` to your deal id and send an `Idempotency-Key` derived from the deal. Your commission is the part you don't transfer. |
> | Refund | `POST /v1/refunds` | `charge_id` from the deal. Pass `amount` for a partial refund of an unreleased milestone. |
> | Take back a release | `POST /v1/transfers` with `X-Account-Id` | Acting as the seller, `destination: "self"`. Works only while the money is still in the seller's balance. |
> | The seller withdraws | `POST /v1/payouts` with `X-Account-Id` | Transfer-funded balances aren't paid out by automatic schedules, so your platform starts the payout. |
>
> ```bash Release a deal theme={"dark"}
> curl -X POST https://sandbox-api.bachs.io/v1/transfers \
>   -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
>   -H "Content-Type: application/json" \
>   -H "Idempotency-Key: deal-5521-release" \
>   -d '{
>     "destination": "acct_Tq7RwB2mLx9VnKcE",
>     "amount": "240000.00",
>     "currency": "NGN",
>     "transfer_group": "deal-5521",
>     "description": "Deal #5521 released on delivery"
>   }'
> ```
>
> Webhooks to handle: `checkout.completed`, `transfer.created`, `refund.paid`, `refund.failed`, `dispute.created`, `account.updated`, `capability.updated`, `payout.paid` and `payout.failed`. Create the endpoint with `event_source` set to `connect` or `all`. See [Connect events](https://docs.bachs.io/guides/webhooks/overview.md#connect-events).
>
> The full mechanics are in [Paying an account without a charge](https://docs.bachs.io/connect/split-payments/separate-transfers.md), [Transfers](https://docs.bachs.io/connect/transfers.md), [Refunds on Connect](https://docs.bachs.io/connect/refunds.md) and [Payouts on Connect](https://docs.bachs.io/connect/payouts.md).

## Next steps

* [Build a marketplace](https://docs.bachs.io/build/use-cases/marketplace.md), where sellers are paid automatically when each payment settles
* [Connect overview](https://docs.bachs.io/connect/overview.md)
* [Fees](https://docs.bachs.io/for-you/fees.md)
* [Take Connect live](https://docs.bachs.io/connect/go-live.md)


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