Skip to main content
Same shape as checkout, with the amount usually left open — the payer decides how much to add.

Set up in the portal

For a server-created Deposit, the merchant needs an sk_ key stored on a server and a destination wallet for the person being funded. Add Payer choices in the portal only when the payer should choose among configured destination chain/token pairs; it does not replace the recipient on a simple server-authored Deposit. Payout addresses are not required for this merchant-managed recipient flow. For a partner that Hypermid has enabled, a publishable key can create an open Deposit directly in the browser with @hypermid/sdk/publishable-deposit, without a merchant backend. The browser must use one destination-binding artifact; it cannot name an arbitrary chain, token, or recipient. The production rollout is partner-gated: configure the key’s exact origin yourself, then ask Hypermid to enable the partner; otherwise use this server-created flow, a registered destination, or a payment link. Verify success from a server-side session read or a signed webhook, not from the payer’s browser. Omitting amount is what makes it open. minAmount / maxAmount bound it.

Fees

A Deposit uses the same two-fee policy as Checkout and Withdrawal: the Hypermid fee plus an optional developer fee on the same payer-input amount. The developer fee defaults to 0 and is limited to 0–100 basis points. A partner Hypermid override replaces the global rate outright. Same-token EVM transfers are the zero fee exception because a plain transfer cannot collect a fee. On EVM routes, developer earnings are held by Hypermid and settled periodically by manual transfer. On the Near deposit-address rail, the provider keeps half of each declared app fee and pays the developer leg directly to the configured recipient.

Let the payer choose what they receive

If your customer can receive funds on more than one chain, create the deposit with destinations instead of one top-level destination. Each candidate binds a chain, token, and recipient chosen by your server. The hosted payment page shows the candidates in the same order you send them.
destinations is mutually exclusive with the top-level chain, token, recipient, amount, minAmount, and maxAmount fields. Sending both shapes returns 400.
Each candidate needs its own recipient for that chain. A Solana destination needs a Solana address, a Sui destination needs a Sui address, and an EVM destination needs an address on that EVM chain. Never reuse one address across ecosystems merely because the same customer owns all of the wallets.

The same destinations power browser-created Deposits

The destination candidates configured for a server-created Deposit are also the choices a browser can select with a publishable key when Method 2 is enabled. The browser submits the candidate’s opaque candidateId; it does not submit a replacement chain, token, or recipient. This keeps the destination bound to the same merchant-controlled configuration instead of trusting a public key to name where funds should go. The other binding artifacts are a payer-signed SIWE challenge, which is EVM-only, or a single-use grant minted by a merchant server. A grant therefore does not provide a no-backend integration. See Payments for the three choices and the current 403 rollout errors, and the publishable-key SDK reference for exact types. The chain inside each candidate is a decimal string, not a JavaScript number. Keep it as a string end to end. Sui’s chain ID, "9270000000000000", exceeds Number.MAX_SAFE_INTEGER; JavaScript does not guarantee exact integer handling in that range, and other IDs can silently round. Use BigInt(chain) only if you genuinely need arithmetic.

Mixed Base, Solana, and Sui example

This example creates one open deposit that lets the payer choose USDC on Base, native SOL on Solana, or native SUI on Sui. The three recipient variables must be addresses from their respective ecosystems.
Only offer destinations enabled in your partner catalogue. Hypermid validates every candidate when the intent is created; one disallowed or currently unroutable candidate rejects the whole request instead of showing the payer an option that cannot complete.

Configure the destinations once, in the portal

destinations names the candidates on every request. If your accepted (chain, token) pairs are the same for every payer, configure them once in the partner portal and pass only a payer-owned recipient per chain with destinationRecipients instead:
The accepted token per chain comes from your portal configuration, so a candidate here is just a chain plus the recipient that should receive it.
destinationRecipients only works when your account is in configured Deposit mode. Without it the request is rejected with “destinationRecipients requires configured Deposit mode”. It is Deposit only — a checkout pays the merchant, so its recipient is not payer-owned.
The rules the backend enforces — each one is a real error to expect:
  • A recipient is required for every configured chain. Omit one and the request fails.
  • Each recipient is validated against its chain. An EVM address on the Bitcoin row is rejected.
  • destinationRecipients is mutually exclusive with chain / token / recipient / amount / minAmount / maxAmount and with destinations. Sending either alongside it is a 400.
  • One configured pair produces an ordinary session directly — no intent, no picker, no selection call.
Multiple configured pairs behave like destinations: the response is an intent with status: "awaiting_destination", and the payer selects one before funding (next section).

Selecting and freezing a destination

Both multi-destination shapes (destinations and multi-pair destinationRecipients) return an intent, not a session: status: "awaiting_destination". GET /v1/payments/:id/public returns its destinationCandidates, each with an opaque id. Two endpoints move the payer from an intent to a frozen session — mind which one you use: Use PUT while the payer is still deciding — a picker that highlights the current choice can call it as often as the payer changes their mind:
Then POST once, when the payer commits, to freeze the session:
The POST freeze is irreversible. Repeating it with the same candidateId is idempotent, but POSTing a different candidate after the first success returns 409 — as does any PUT once the session is frozen. The freeze happens before the flow issues a deposit address, because letting the destination change after an address is shown could strand funds at an address the session no longer tracks. After the freeze, quote, payment, settlement, webhook, and session-read behavior is the same as for any other deposit.
Existing integrations do not need to change. A request with top-level chain / token / recipient still creates the same single-destination session as before. A one-item destinations array also creates an ordinary session directly: no intent, no destination picker, and no selection call.

Funding from a non-EVM chain

If the payer holds BTC, SOL, TON or similar, there is no wallet that can sign an EVM transaction — the session takes the deposit flow instead. Mint an address with POST /v1/payments/{id}/deposit, supplying originAsset and refundTo.
refundTo is required up front. If settlement fails there is no connected wallet to return funds to, so it cannot be collected afterwards.
Want a quote without reserving an address? GET /v1/payments/{id}/deposit-quote prices it without minting.