voided or reclaimed deal can still be refunded, if something was captured before the hold was released.
Every transition is one POST /dapi/v1/escrow/{escrowId}/actions that you sign with your developer key. You are the operator of every deal you create; escrow never takes X-Hevn-Account, because the caller is always you. It is one of two self-scoped families — the other is POST /clients and GET /clients — and both refuse the header the same way, with 400 account_scope_conflict and details.reason = "selfScopedRoute". See Sessions.
The states money is in
A deal carries two amounts, and they are what you branch on:capturableAmount— money out of the buyer’s wallet, held in the contract, not yet paid to the seller.capturemoves it to the seller,voidreturns it to the buyer,reclaimreturns it after the hold window.refundableAmount— money already paid to the seller that you can still send back. A refund leaves your own wallet, not the seller’s, so keep a working balance in the deal’s token.
status is a label for the last thing that happened, not a decision input. Read availableActions — the server computes it from the amounts, the windows and what already landed on chain — and pick from it. A draft nobody approved in time has an empty availableActions; there is no expired status and nothing sweeps a stale deal.
Amounts are decimal strings in the token’s major unit, rendered at the token’s precision: "100.000000" for USDC. token is a symbol — USDC or EURC — never an address. The full status vocabulary is in Escrow actions.
The three parties
All three must be different accounts, and both parties must be clients you created. HEVN binds sender and receiver through the sub-user relationship that
POST /clients writes, so only accounts you provisioned through this API qualify; an account you merely referred never does, and a stranger’s account id answers 422 receiver_not_a_client or 404 client_not_found. The addresses are each party’s Base smart wallet at creation time, and the deal is immutable: its terms are hashed into an on-chain payment record.
The three windows
You give three durations and HEVN resolves them once, at creation, each measured from the end of the one before it.
A duration is
^\d{1,4}[smhd]$ — 30m, 1h, 14d. You never compute a timestamp, which is what makes a retry safe: repeating POST /dapi/v1/escrow with the same Idempotency-Key returns the same deal with the same absolute windows, no matter how much later it arrives. Different durations under the same key answer 409 idempotency_key_reused with details.escrowId.
void is the exception with no clock on it: while anything is capturable, you can always hand it back.
What it costs
feeBps on the deal is your ceiling, an integer in basis points. capture and charge may take any fee up to it; the fee goes to your own Base smart wallet, which HEVN derives — there is no feeReceiver to send. fee = amount × feeBps / 10000, the receiver gets the rest, and a refund returns the gross amount from your wallet without clawing the fee back.
Set the ceiling at creation. It is part of the immutable deal, so a deal created with feeBps: 0 can never take a commission.
HEVN sponsors the gas for every action and charges nothing on top.
Before you start
- Both parties are clients of yours and are
ready— see Clients. - Your account may operate escrow. HEVN enables that, in the sandbox as in production; nothing in the API grants it. Without it every escrow call is refused — see Going live.
- The buyer’s wallet holds the token when you authorize, not when you create. Creating a deal moves nothing.
- Call escrow with your own access token and no
X-Hevn-Account— the header answers400 account_scope_conflicton every escrow route. - Your developer key registration is active and the call leaves an allowlisted source IP. Both are re-checked on every request, and again immediately before HEVN co-signs an action — see Developer key.
Idempotency-Key from an id your own system already owns, pass it in from the caller, and
keep it identical across every retry of the same payment.
A marketplace order, end to end
Every example below uses the client from HTTP client and thesign_payload helper from Signing.
Order A-1187: Northwind Trading Ltd (cl_7YQ2Kf3mN8) buys 100 USDC of parts from Ardenne Fabrication SARL (cl_2ABxR9dL4m), and you take 2.5% when it ships.
1
Open the deal and approve the spend permission
One call creates the deal and prepares the buyer’s approval — the on-chain permission that lets you pull the money later. Confirm it with the same key the create carried; the response echoes it as Nothing has moved.
action.idempotencyKey.Response — 201, Location: /dapi/v1/escrow/esc_5Qb4e17c9d
availableActions becomes ["authorize", "charge"] once the approval lands on chain — if the confirm answers 202, poll it to settlement first (Escrow actions).2
Lock the buyer's funds in the contract
authorize pulls the amount out of the buyer’s wallet into the escrow contract. It happens once per deal: authorize less than maxAmount and the remainder is unusable, so authorize the order total.Response — 200
3
Ship it, and capture with your commission
capture pays the seller and takes your fee in the same action. It is repeatable up to capturableAmount, so a partial shipment is a partial capture.captured with refundableAmount: "100.000000" — the gross is what you can still send back.When the order does not go to plan
void, refund and reclaim keep working when your relationship with a client is paused or your profile is suspended, so held money is never stranded.
Next: Escrow actions
Every action, the window it is legal in, the amount it takes, and the state it leaves behind.