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

> ## Agent Instructions
> HEVN is payments software: an API and app over a Base smart wallet. HEVN is not a bank and holds no key to the wallet. Virtual accounts, fiat payins and fiat payouts are provided by partner banks, which hold fiat while it is in transit. Self-serve (the General tab): the person who controls the email signs for the wallet. Developers integrate /dapi/v1 in one of two ways. HEVN Connect (recommended, the HEVN Connect tab): the integrator's customer creates or links a HEVN account in a HEVN-hosted sign-in window on the integrator's site, the integrator's backend reads the linked account and books its payouts, and the account holder approves each payout in that window. External wallets (the External wallets tab, enabled per integrator): the integrator creates a client through the API, registers a wallet address the customer holds as an external account, and requests virtual accounts that deliver to that address. The customer's own wallet sends each transfer that funds a payout. The developer key signs for the integrator's wallet and its sub accounts and cannot sign for a client's wallet.
> Use HEVN's terms as written. A product is the region a client requests. A rail is the bank route HEVN picks for the product. A virtual account (bnk_) is the record a partner bank opens for a client. Account details are what a payer needs to send a bank transfer, returned in a field named requisites. A client (cl_) is a customer's account: linked to the integrator through Sign in with HEVN (POST /dapi/v1/connect/sessions, then POST /dapi/v1/clients/link), or created with POST /dapi/v1/clients for an external-wallets integration. An external account (acc_, kind external) is a wallet address the client holds and signs for. A sub account (acc_, kind sub) is a further wallet of the integrator's own account on Base, spent with the developer key. Write 'sub account' as two words. A contact is a saved destination. A beneficiary is the party that receives a fiat payout. Money leaving an account is a payout, whether it goes to a bank account or to a wallet.
> Do not name a bank, payment institution, card issuer, identity-verification vendor or stablecoin issuer. Say 'the partner bank'. Do not describe HEVN as licensed, regulated or insured.
> HEVN publishes no fee rates, spreads or minimums. Prices come from a quote on a specific payment. Direct pricing questions to HEVN and do not invent numbers.
> The API is at /dapi/v1 and takes a developer-key session plus an X-Hevn-Account header. Developer keys have an IP allowlist and no scopes. Remote transfers are at /api/v1/remote_spend_limit and take a remote transfer key (rsl_) in X-Api-Key, bounded by an onchain spend permission the account holder grants. The two credentials are separate and neither works on the other API. The HEVN CLI is discontinued.

# Escrow

> Hold a buyer's funds in an on-chain contract, then authorize, capture, void or refund them with signed actions.

An escrow deal holds a buyer's funds in an on-chain contract until you capture them for the seller. Create a deal with `POST /dapi/v1/escrow`, then drive it with the operator actions in [The matrix](#the-matrix).

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft: POST /dapi/v1/escrow
    draft --> authorized: authorize
    draft --> charged: charge
    authorized --> partiallyCaptured: capture (part)
    partiallyCaptured --> captured: capture (rest)
    authorized --> captured: capture (all)
    authorized --> voided: void
    partiallyCaptured --> voided: void
    authorized --> reclaimed: reclaim (after holdUntil)
    partiallyCaptured --> reclaimed: reclaim
    captured --> partiallyRefunded: refund
    charged --> partiallyRefunded: refund
    partiallyRefunded --> refunded: refund
    captured --> refunded: refund
    charged --> refunded: refund
```

Escrow routes are self-scoped and refuse `X-Hevn-Account` ([Acting as a client](https://docs.gethevn.com/developers/authentication.md#acting-as-a-client)). A deal moves money between main wallets, and a sub account takes no part in it.

> [!WARNING]
>
> The buyer approves a deal with their key, and the [sign-in window](https://docs.gethevn.com/connect/sign-in-with-hevn.md) does not offer escrow approvals. `POST /dapi/v1/escrow` with a buyer linked through Sign in with HEVN returns `403 account_not_controlled`. Contact HEVN before you build on escrow.

## Parties

| Party | Role |
| - | - |
| Sender | A client, the buyer. Funds the deal and approves its spend permission. Can `reclaim` an uncaptured hold after `holdUntil`. |
| Receiver | Another client, the seller. Receives what you capture. |
| Operator | You. Signs `authorize`, `charge`, `capture`, `void` and `refund` with your developer key on your wallet. |

The parties are different accounts. Sender and receiver are `ready` [clients](https://docs.gethevn.com/developers/clients.md) linked to you through [Sign in with HEVN](https://docs.gethevn.com/connect/sign-in-with-hevn.md). A referred account does not qualify. HEVN enables escrow for your account in both environments ([What HEVN must enable](https://docs.gethevn.com/developers/sandbox.md#what-hevn-must-enable)).

The deal uses each party's Base smart wallet address at creation. Its terms are hashed into an on-chain payment record and cannot be changed.

## Amounts

`capturableAmount` is held in the contract. `refundableAmount` is paid to the seller and still refundable. Choose the next action from `availableActions` and these two amounts.

A draft not approved before `approveBy` stays `draft` with an empty `availableActions`. There is no `expired` status.

Amounts use the [money format](https://docs.gethevn.com/developers/conventions.md#money), for example `"100.000000"` for USDC. Field meanings are on [Read one escrow](https://docs.gethevn.com/api-reference/escrow/read-one-escrow.md).

## Windows

HEVN converts `approveWithin`, `holdFor` and `refundableFor` into the deadlines in `windows` when it creates the deal. Each window starts when the previous one ends. `void` has no window and is available while `capturableAmount` is positive.

Defaults, the duration format and the create refusals are on [Open an escrow](https://docs.gethevn.com/api-reference/escrow/open-an-escrow-and-return-the-payers-approval.md).

## Fees

* `feeBps` on the deal is your fee ceiling. `capture` and `charge` take a fee up to it, paid to your Base smart wallet. There is no `feeReceiver` field.
* `refund` returns the gross amount from your wallet. The fee is not taken back.
* HEVN sponsors the gas for each action.

## Run an order

**1. Create the deal**

Call `POST /dapi/v1/escrow`. It moves no funds and prepares the approval that the buyer signs.

```python Python theme={null}
deal = hevn.post("/escrow", {
    "senderClientId": "cl_7YQ2Kf3mN8",
    "receiverClientId": "cl_2ABxR9dL4m",
    "token": "USDC",
    "maxAmount": "100.00",
    "approveWithin": "1h", "holdFor": "14d", "refundableFor": "90d",
    "feeBps": 250,
    "reference": {"orderId": "A-1187"},
}, idempotency_key="order-A-1187")
```

```javascript Node theme={null}
const deal = await hevn.post("/escrow", {
  senderClientId: "cl_7YQ2Kf3mN8",
  receiverClientId: "cl_2ABxR9dL4m",
  token: "USDC",
  maxAmount: "100.00",
  approveWithin: "1h", holdFor: "14d", refundableFor: "90d",
  feeBps: 250,
  reference: { orderId: "A-1187" },
}, { idempotencyKey: "order-A-1187" });
```

```go Go theme={null}
deal, err := api.Post("/escrow", hevn.Body{
	"senderClientId":   "cl_7YQ2Kf3mN8",
	"receiverClientId": "cl_2ABxR9dL4m",
	"token":            "USDC",
	"maxAmount":        "100.00",
	"approveWithin":    "1h", "holdFor": "14d", "refundableFor": "90d",
	"feeBps":    250,
	"reference": hevn.Body{"orderId": "A-1187"},
}, hevn.IdempotencyKey("order-A-1187"))
```

```json Response 201, Location: /dapi/v1/escrow/esc_5Qb4e17c9d theme={null}
{
  "id": "esc_5Qb4e17c9d",
  "status": "draft",
  "availableActions": ["approve"],
  "maxAmount": "100.000000", "authorizedAmount": "0.000000",
  "capturableAmount": "0.000000", "refundableAmount": "0.000000",
  "windows": { "approveBy": "2026-09-17T11:04:11Z",
               "holdUntil": "2026-10-01T11:04:11Z",
               "refundableUntil": "2026-12-30T11:04:11Z" },
  "action": { "action": "approve", "idempotencyKey": "order-A-1187",
              "status": "awaitingSignature",
              "approval": { "id": "a7c1…", "payload": "eyJ…",
                            "expiresAt": "2026-09-17T10:05:11Z" } },
  "…": "…"
}
```

Read the deal until `availableActions` is `["authorize", "charge"]`. The buyer's approval is then on chain.

**2. Authorize the hold**

Prepare `authorize` with `POST /dapi/v1/escrow/{escrowId}/actions`, then sign and confirm it as described in [Signing](https://docs.gethevn.com/developers/signing.md). It moves the amount from the buyer's wallet into the contract. The wallet must hold the token when you confirm.

A deal accepts `authorize` once. An amount below `maxAmount` leaves the remainder unusable.

```python Python theme={null}
action = hevn.post(f"/escrow/{deal['id']}/actions",
                   {"action": "authorize", "amount": "100.00"},
                   idempotency_key="order-A-1187-authorize")
hevn.post(f"/escrow/{deal['id']}/actions/order-A-1187-authorize/confirm",
          {"signature": key.sign_payload(action["action"]["approval"]["payload"])})
```

```javascript Node theme={null}
const action = await hevn.post(`/escrow/${deal.id}/actions`,
  { action: "authorize", amount: "100.00" },
  { idempotencyKey: "order-A-1187-authorize" });

await hevn.post(`/escrow/${deal.id}/actions/order-A-1187-authorize/confirm`,
  { signature: key.signPayload(action.action.approval.payload) });
```

```go Go theme={null}
action, err := api.Post("/escrow/"+deal.Str("id")+"/actions",
	hevn.Body{"action": "authorize", "amount": "100.00"},
	hevn.IdempotencyKey("order-A-1187-authorize"))

signature, err := key.SignPayload(action.Str("action.approval.payload"))
_, err = api.Post("/escrow/"+deal.Str("id")+"/actions/order-A-1187-authorize/confirm",
	hevn.Body{"signature": signature})
```

```json Response 200 theme={null}
{ "deal": { "status": "authorized", "capturableAmount": "100.000000",
            "availableActions": ["capture", "void"], "…": "…" },
  "action": { "action": "authorize", "status": "confirmed",
              "amount": "100.000000", "transactionHash": "0x3a77…", "…": "…" },
  "events": [ { "name": "PaymentAuthorized", "amount": "100.000000",
                "transactionHash": "0x3a77…", "blockNumber": 19884412 } ] }
```

**3. Capture with your fee**

Before `holdUntil`, prepare and confirm `capture` the same way, with the body `{"action": "capture", "amount": "100.00", "feeBps": 250}` and the key `order-A-1187-capture`. You can repeat it up to `capturableAmount`.

The seller receives 97.50 USDC and your wallet receives 2.50. The deal is `captured` with `refundableAmount: "100.000000"`.

## Cancel or refund

| Case | Action | Status |
| - | - | - |
| Cancelled before capture | `void` | `voided` |
| Partly delivered | `capture` that part, then `void` | `partiallyCaptured`, then `voided`. The captured amount stays refundable. |
| Returned after capture | `refund` | `partiallyRefunded`, then `refunded` |
| Not captured before `holdUntil` | The buyer's `reclaim`, which you cannot send | `reclaimed` |

Keep the deal's token in your wallet for refunds. If the wallet cannot cover the amount, `refund` returns `409 simulation_failed`.

`void`, `refund` and `reclaim` remain available while your relationship with a client is paused or your profile is suspended.

## The matrix

Prepare an operator action with `POST /dapi/v1/escrow/{escrowId}/actions` and sign the returned payload. Then confirm it at `POST /dapi/v1/escrow/{escrowId}/actions/{idempotencyKey}/confirm`. The `amount` and `feeBps` rules are on [Prepare an escrow action](https://docs.gethevn.com/api-reference/escrow/prepare-an-escrow-action-and-return-the-payload-to-sign.md).

| Action | Allowed when | Resulting status |
| - | - | - |
| `authorize` | Before `approveBy`, while no funds are collected | `authorized` |
| `charge` | Before `approveBy`, while no funds are collected | `charged` |
| `capture` | Before `holdUntil`, repeatable | `partiallyCaptured`, then `captured` |
| `void` | While `capturableAmount` is positive | `voided` |
| `refund` | Before `refundableUntil`, repeatable | `partiallyRefunded`, then `refunded` |

`charge` is `authorize` and `capture` in one on-chain action, with no hold.

## Prepare an action

```json Request: POST /dapi/v1/escrow/esc_5Qb4e17c9d/actions, Idempotency-Key: order-A-1187-capture theme={null}
{ "action": "capture", "amount": "40.00", "feeBps": 250 }
```

```json Response 201 theme={null}
{
  "deal": { "id": "esc_5Qb4e17c9d", "status": "authorized", "…": "…" },
  "action": { "action": "capture", "idempotencyKey": "order-A-1187-capture",
              "status": "awaitingSignature", "amount": "40.000000", "…": "…",
              "approval": { "id": "b1d0…", "payload": "eyJ…",
                            "expiresAt": "2026-09-17T10:31:02Z" } }
}
```

Confirm with `action.idempotencyKey` in the path. A replayed key returns the same record ([Idempotency-Key](https://docs.gethevn.com/developers/conventions.md#idempotency-key)).

## Confirm an action

Sign the decoded bytes of `approval.payload` as described in [Signing](https://docs.gethevn.com/developers/signing.md), then send the signature.

```json Request: POST /dapi/v1/escrow/esc_5Qb4e17c9d/actions/order-A-1187-capture/confirm theme={null}
{ "signature": "MEUCIQDk3f8Zc0xq2w1Yv7pB9nH4mJ6sR2tL5uD8aE0cQ1oXgAIg…" }
```

| Response | Meaning | Next |
| - | - | - |
| `200` | Mined. The body has the deal and its `events`. | Read `action.status`: `confirmed` or `reverted`. |
| `202` with `pollUrl` | The action is `submitted`. `pollUrl` is the deal read. | Call `POST /dapi/v1/escrow/{escrowId}/sync` while the action is `signing` or `submitted`. |
| `403 forbidden` | The developer key was deleted, or the IP is outside its [allowlist](https://docs.gethevn.com/developers/authentication.md#the-ip-allowlist). The prepared action is unchanged. | Confirm from an allowlisted host. |
| `403 account_not_controlled` | The action would sign on the buyer's wallet. | Confirm operator actions only. |
| `410 approval_expired` | The approval closed, or none is open. | Prepare again with the same key. |
| `409 approval_consumed` | The approval was already used. | Read the deal, then prepare again with the same key. |
| `409 escrow_state_changed` | No capturable amount is left, or the payment was already collected. | Read the deal. Pick from `availableActions`. |

`400 signature_invalid`, `503 bundler_rejected` and `503 bundler_unavailable` are handled as in [Retrying a confirm](https://docs.gethevn.com/developers/signing.md#retrying-a-confirm).

### Action in flight

A signing wallet runs one on-chain action at a time across all its deals. While one is `signing` or `submitted`, the next prepare or confirm returns `409 action_in_flight` with no `details`.

Queue actions per signing wallet and store the id of the deal in flight. Sync that deal before you retry.

## Statuses

`status` records the last transition. Choose the next action from `availableActions` ([Amounts](#amounts)).

| Deal status | Meaning | Next |
| - | - | - |
| `draft` | No funds held. The buyer's approval does not change the status. | `authorize` or `charge`, after the buyer's approval |
| `authorized` | The hold is in the contract. | `capture` or `void` |
| `charged` | Paid to the seller in one action. | `refund` |
| `partiallyCaptured` | Part of the hold was paid to the seller. | `capture` or `void` |
| `captured` | No capturable amount is left. | `refund` |
| `voided` | The hold returned to the buyer. | `refund` if part was captured |
| `reclaimed` | The buyer took the hold back after `holdUntil`. | `refund` if part was captured |
| `partiallyRefunded` | Part was refunded from your wallet. | `refund` |
| `refunded` | The paid amount was refunded from your wallet. | n/a |

| Action status | Meaning | Next |
| - | - | - |
| `awaitingSignature` | Prepared, with an open approval. | Sign and confirm within 60 seconds. |
| `signing` | Confirm in progress. | Wait. Do not prepare again. |
| `submitted` | Sent to the bundler, with no receipt yet. | Sync the deal as described in [Reads](#reads). |
| `confirmed` | Mined and applied to the deal. | n/a |
| `reverted` | Mined and reverted. No funds moved. | Read the deal. Prepare again with a new key. |
| `failed` | Not sent to the bundler. | Prepare again with the same key. |
| `dropped` | No receipt within the timeout. | Keep syncing. It can confirm for up to 24 hours. |
| `superseded` | Replaced by a later prepare. | Ignore. |

## Reads

`GET /dapi/v1/escrow/{escrowId}` returns one deal with its last action. There is no action list.

`GET /dapi/v1/escrow` lists the deals you operate ([List the escrows](https://docs.gethevn.com/api-reference/escrow/list-the-escrows-this-account-operates.md)).

```bash cURL theme={null}
curl "$HEVN_API/escrow?status=authorized&status=partiallyCaptured&limit=50" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN"
```

```python Python theme={null}
open_deals = hevn.get("/escrow?status=authorized&status=partiallyCaptured&limit=50")["items"]
```

```javascript Node theme={null}
const { items: openDeals } =
  await hevn.get("/escrow?status=authorized&status=partiallyCaptured&limit=50");
```

```go Go theme={null}
page, err := api.Get("/escrow?status=authorized&status=partiallyCaptured&limit=50")
openDeals := page.List("items")
```

Write a repeated `status` into the path, because the HTTP client's parameter map holds one value per name.

`POST /dapi/v1/escrow/{escrowId}/sync` reconciles the deal with the chain and returns it with `consistent` ([Reconcile one escrow](https://docs.gethevn.com/api-reference/escrow/reconcile-one-escrow-with-the-chain.md)).

## Errors

A closed window, a fee above the ceiling or an amount above the cap returns `400 invalid_request` with no `details`. There is no `window_closed` or `amount_out_of_range` code. Read the deal and pick from `availableActions`. Other codes are in [Errors](https://docs.gethevn.com/developers/reference/errors.md#escrow).

> [card] [Next: Sandbox](https://docs.gethevn.com/developers/sandbox.md)


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