Skip to main content
Money arrives four ways: a quoted deposit you priced, a quoted on-chain transfer you priced, an unsolicited wire to account details that are already live, or crypto sent straight to the wallet. All four end as one income row on the client’s ledger. Quote a payin when you need the rate and the fee pinned before the payer pays, and when you want their transfer attributed to one order. Take an unquoted path when the client publishes its own details and reconciles afterwards. Send X-Hevn-Account: cl_… with your own access token to act for one client. On a resource route like GET /banks the header is optional: omit it and the call acts on your integrator account instead. The singular /client* routes require it — the header is the only thing that names the account — and /clients and /escrow* refuse it.
The whole table — where the header is optional, where it is required and where it is refused — is in Sessions.

Quote a deposit

The client needs an active rail in the currency the payer sends — see Virtual accounts.
1

Price the deposit

Response — 201, Location: /dapi/v1/payins/pi_2b7…
Four decisions:
  • amount or amountTo, exactly one. amount is what the payer sends in currency; amountTo is what should land on the wallet, and HEVN grosses the payer’s side up to reach it.
  • rail or originChainId, exactly one. rail prices money arriving over a payment method; originChainId prices money arriving on a chain. Sending both, or neither, is refused.
  • rail here is a payment methodsepa, ach, swift, fedwire, pix, … — not the rail id you opened with POST /dapi/v1/banks. The two are different vocabularies; Rails lists both.
  • destinationAccount is the stablecoin the payin is delivered in, USDC (the default) or EURC. It has to be a token one of the client’s matching rails settles into; when it is not, the call refuses with the pairs that work (see below).
The price holds until expiresAt. Read it; do not cache a quote.
2

Open it and read the instructions

Response — 200
Hand requisites to the payer verbatim, paymentReference included — on a pooled account it is the only thing that attributes the money to this payin.kind says where the payer acts: bank_transfer (use requisites), qr_code (qrPayload), hosted_page (url), manual, or onchain (address, chainId and memo). singleUse: true means these instructions belong to this payin alone.Opening the same payin again returns the same instructions — that is the safe retry, not a way to create a second payin. Once the price stops holding it answers 410 payin_expired; quote again.
3

Watch it settle

GET /dapi/v1/payins/{payinId} carries the payin through its whole life. Settlement is driven by the partner’s own reports, so poll it rather than waiting on a response.

Quote an on-chain transfer

originChainId in place of rail prices money arriving on a chain instead of through a bank. The body is otherwise the same call:
PayinView then carries originChainId — the network the payer funds from — and destinationChainId, the network the payin settles on. Open it with the same POST /dapi/v1/payins/{payinId}/instructions, and the instructions answer kind: "onchain" with the three fields a sender needs:
Response — 200
memo is the destination tag some networks route on. When it is present it is as load-bearing as a paymentReference — a transfer that omits it cannot be attributed. ChainCode is the same vocabulary a wallet contact uses; base, arb, sol and the rest are listed on the rail reference. This is the quoted arm of crypto-in. The unquoted arm — send straight to the wallet, no call at all — is further down.

What a rail converts at

GET /dapi/v1/banks/{rail}/rate answers {currency, rate, fixedFee, fixedFeeCurrency} — the indicative price of a deposit over that rail. On a rail the client has not opened, only currency comes back. Use it to show a rate in your own UI before the payer commits to an amount. It is indicative: POST /dapi/v1/payins returns the number you are held to.

When the payer has to be named

Some rails require the payer’s own account so the receiving bank can check the sender. POST /dapi/v1/payins/{payinId}/instructions takes it:
payer.requisites.method has to equal payer.method. When a rail needs the payer and you omit it, the call answers 400 invalid_request and names the fields it wants in message — that refusal carries no details. details.options[].payerRequired on a refused quote is the part you can branch on, and it tells you in advance which pairs demand a payer.

When a pair cannot be paid

A currency and method the client cannot be paid in answers 422 payin_not_available, and details.options carries every pair that can — each with rail, currency, available, minAmount, maxAmount, feeBps, fixedFee, payerRequired and destinationAccounts. Read the options rather than pre-flighting: the refusal is the capability catalogue. 410 payin_expired and 404 payin_not_found are the other two you will meet; both carry details.payinId. Errors has the rest.

Unsolicited wires to the client’s account details

An active rail’s account details stay live between payments. Anyone can wire to them with no quote and no API call from you.
  1. Read requisites for the active rail from GET /dapi/v1/banks.
  2. Give the payer the fields it carries, plus paymentReference when one is set.
  3. The deposit lands as an income row on GET /dapi/v1/transactions, carrying remitter where the partner reports the sender, paymentReference, and the receiving account details.
Some partners report no sender at all. When remitter is absent on a settled deposit, the sender cannot be recovered from the API — treat it as unknown rather than retrying.

Crypto straight to the wallet

The client’s smart wallet is an ordinary address on Base. Read it from GET /dapi/v1/client/balance — with the client id in X-Hevn-Account — as baseSmartWallet and publish it; anything sent there is the client’s. Nothing is quoted on this path, and there is no payin record: use the quoted on-chain arm above when you need a price or an attributed transfer.
Only USDC and EURC on Base are tracked. Other tokens, and the same tokens on other chains, sit at the address without ever becoming a balance or a transaction.

In the sandbox

The partner is emulated, so a quoted payin waits forever until you complete it yourself. One call does what the payer would have done:
POST /dapi/v1/sandbox/deposits covers the other two unquoted shapes: with bankId it emulates an unsolicited wire to that rail, without one it credits the smart wallet with tokens. Both are in Sandbox.
The sandbox is the same API against one emulated partner on Base Sepolia. Its money comes from a shared faucet account: at most 100 tokens per credit, at least 0.10, and 60 money calls per account per hour. GET /dapi/v1/sandbox/treasury reports the live ceiling. Full table in Limits.

Next: Payouts

Money out: price and book a bank payout in one call, sign it in the next.