Skip to content
STONS

API changelog

All changes to the public API: new routes, fields, webhook events, and error codes. New entries are added at the top; dates are in UTC. A change that breaks compatibility is marked with the word breaking and explains what the integrator must do.

2026-10-14, an amount in a stablecoin is not requoted, and the previous amount counts for 10 minutes

  • A token pegged to the US dollar (USDT_*, USDC_*): the amount of an invoice priced in dollars, fixed at the choice, holds until the invoice's deadline and is no longer requoted after the quote window, nor is the rest after a partial payment. quote.expires_at of such an invoice is the invoice's expires_at, also for an invoice chosen earlier. The amount used to move by cents every 30 minutes, and a customer who paid exactly what was shown got partially_paid.
  • Other tokens are requoted after the window as before, but a transfer in a block no later than 10 minutes after the end of the previous quote's window is judged against that quote's amount as well. If it covers it, the invoice becomes confirmed, and amount_requested, amount_expected and quote (with its number) are the previous quote's again. See quote window.
  • No field or error code changed.

2026-10-14, a rest below the minimum deposit pays the invoice

  • A transfer below the minimum deposit of its asset is no longer lost when it is the rest of a per_invoice invoice: once a payment to the invoice has arrived and amount_expected is not reached yet, a transfer in the invoice's asset to its address is a payment (kind: payment) of any amount above zero. It used to be dust, and an invoice a few cents short stayed underpaid. See partial payment.
  • The first transfer to an invoice, a transfer after the amount is reached, a zero transfer, and a transfer to a per_user address below the minimum deposit are still dust.
  • No field or error code changed.

2026-10-14, native coins sent by smart contracts are credited

  • A native coin of an EVM network sent by a smart contract is now credited — from a smart-contract wallet, a multisig, an exchange's withdrawal: it pays an invoice, is listed by GET /v1/deposits/{tx_hash} and sends deposit.* webhooks like a plain transfer from a wallet. The service finds it by tracing the block and credits it only when the trace accounts for the whole rise of the address's balance; one it cannot account for is still resolved by the operator — see what arrives at an address.
  • Such a transfer has log_index = -2, -3 and so on, as a transfer a contract made on TRON already has. One transaction can carry several of them and its own value with -1. Check your integration if it assumes that log_index of a native coin is always -1, or that a transaction pays an address once.
  • Routes, fields, webhook bodies and error codes have not changed. The decision — ADR-0055.

2026-10-14, the payment page estimates the wait for confirmations

  • New field confirmation_estimate in GET /checkout/v1/invoices/{id} and in the answer of POST /checkout/v1/invoices/{id}/select: while a transfer waits for confirmations, how many blocks it still needs, how many seconds that is by the block time of the network, and whether the network also waits for a final block (payment page). The page shows it as "About N minutes left" instead of promising about a minute on every network.

2026-10-14, a native coin has no token standard

  • token_standard is null for a native coin in GET /checkout/v1/invoices/{id} and in the answer of POST /checkout/v1/invoices/{id}/select, both for the invoice and for each of its choices: BNB is not a BEP-20 token, SOL is not an SPL token (payment page). Before, it named the standard of the network for every asset.

2026-10-06, a price in dollars in the asset you name

  • New field price_currency in POST /v1/invoices. The one value is USD, and only together with asset. With it, amount is a price in dollars rather than a number of coins: the service quotes it into the named asset at its own rate (ADR-0070, a price in dollars in the asset you name).
  • The answer to the creation is ready to be paid: address, amount_expected, price, quote.rate and expires_at come back from the POST itself, with no checkout page and no choice by the customer.
  • The field is optional and the old meaning stands: without price_currency, amount is still an amount in units of the asset. Existing integrations need no change.
  • price_currency without asset is refused (400 validation_failed): there the amount is a price in dollars already.
  • No rate, no invoice: 503 rate_unavailable, and nothing is left in the database.
  • New route GET /v1/rates: for every asset of the project, the rate an invoice would use (the market rate less the service markdown), the market rate itself, the markdown percent, when it was observed and whether it is fresh. So that «≈ 0.45 SOL» beside a price matches the amount of the invoice.

2026-10-05, an integration configures its project with its own key

  • New routes GET and PATCH /v1/project, GET and PUT /v1/project/assets. An integration — or an assistant given nothing but an API key — sets the webhook URL, the return addresses and the tokens it accepts itself. Nobody has to open the dashboard (ADR-0067, authentication).
  • These routes never return the webhook signing secret. It is shown once, when the project is created; an API key is not access to the secret.
  • The webhook URL can be set and replaced, never removed: null answers 400. Events already queued need somewhere to go.
  • The address mode and the custody mode are not changed here: an operator allows them.

2026-10-05, the address mode is allowed per project

  • A new error code 422 mode_not_allowed on POST /v1/addresses, POST /v1/invoices and POST /checkout/v1/invoices/{id}/select: the address mode of this project is not allowed to it. An operator allows a restricted mode per project rather than for the whole platform at once (ADR-0066). The older 422 address_mode_disabled stays and means something else: the platform paused the mode for everybody.
  • The address mode of a project can now be changed. It used to be chosen once, at creation. Now an operator allows the mode and the project's owner switches to it in the cabinet. The change decides where the next payment is collected, never where an earlier one went: addresses already issued keep their own mode, keep being credited and keep being swept.
  • A project cannot be created in per_user from the cabinet: 422 mode_not_allowed. Create it in per_invoice and switch once an operator has allowed the mode.

2026-10-05, Solana

Solana is the fourth family of networks the service works with. No route, field or webhook event changed shape; what changed is the set of networks and assets the existing ones can carry.

  • Payments work on ten networks: solana joins tron, ethereum, bsc, polygon, base, arbitrum, optimism, avalanche and ton. Its assets are USDT_SOLANA, USDC_SOLANA and the native coin SOL_SOLANA, each needing 1 confirmation; the mints and decimals are in assets on Solana.
  • There are no payouts on Solana, as there are none on the EVM networks or TON: 422 asset_not_payable.
  • A tx_hash of a Solana transfer is not a hexadecimal hash. It is the first signature of the transaction: 86 to 88 base58 characters, whose case carries meaning and must be preserved. A column sized for 64 hexadecimal digits will not hold it, and lower-casing it destroys the value. This affects GET /v1/deposits/{tx_hash}, the deposits of an invoice and the deposit.* webhooks — see addresses on Solana.
  • A Solana address is base58 of 32 to 44 characters and is case-sensitive too. Store, compare and display it exactly as returned; never normalise its case.
  • wallet_url on the checkout page stays null on Solana: the service builds a wallet deep link on TON only — see read an invoice for the page.
  • A token on Solana is held in an associated token account rather than at the address itself, which the service creates and watches. A customer's wallet pays to the address as usual; code that checks a payment by reading the chain must know that getSignaturesForAddress on the address shows no SPL transfer.

2026-10-05, which networks and tokens actually work

This entry corrects the documentation, not the behaviour of the API: no route and no field changed. Earlier entries said the EVM networks were in development and TON was planned; that stopped being true before the documentation caught up.

  • Payments work on nine networks: tron, ethereum, bsc, polygon, base, arbitrum, optimism, avalanche, ton. Their assets are listed by GET /v1/assets; the contracts and decimals are in assets.
  • Native coins are accepted like tokens, at an exchange rate: TRX_TRON, GRAM_TON, ETH_ETHEREUM, BNB_BSC, POL_POLYGON, ETH_BASE, ETH_ARBITRUM, ETH_OPTIMISM, AVAX_AVALANCHE. Only a plain transfer from a wallet is credited.
  • USDT on Polygon, Arbitrum and OP Mainnet is USDT0, a separate asset with its own contract: USDT0_POLYGON, USDT0_ARBITRUM, USDT0_OPTIMISM.
  • USDC_TRON is switched off by the operator entirely, not only for new invoices: payouts in it are off as well and are refused with 422 asset_payouts_disabled. Until this entry the documentation promised that payouts in it worked, which was wrong.
  • Payouts are still made on TRON only, and only in USDT_TRON. There are none on the EVM networks or TON: 422 asset_not_payable.
  • The list of networks and tokens is no longer described with the statuses "works / in development / planned": networks and tokens says what the service does on each network, and what is available to a project at a given moment is the answer of GET /v1/assets. An asset switched off by the operator or by the project's settings does not appear in that answer.

2026-10-05, the settlement wallet is readable

  • Cabinet API: the project object gained custody_mode — custodial (the service keeps the money) or pass_through (the money goes to the project's own wallet). Only the operator changes it.
  • Cabinet API: GET /cabinet/v1/merchants/{id}/settlement-wallets returns the wallets the project's accepted money is forwarded to, one per asset: the address in full, from which moment it is in force, and whether it is in use already. The list is empty for a custodial project. See where the money goes.
  • Admin API: GET /admin/v1/merchants/{id}/settlement-wallets returns the same plus who entered the row and the fingerprint of the key that signed it.
  • The merchant API and the bodies of webhooks did not change. A wallet is still entered by a command an operator signs, never through the API.

2026-10-05, pass-through custody in the admin API

  • Admin API: the merchant object gained custody_mode — custodial (our treasury) or pass_through (the merchant's own settlement wallet). PATCH /admin/v1/merchants/{id} takes it; switching to pass_through without a settlement wallet in force answers 422 settlement_wallet_missing.
  • Admin API: GET /admin/v1/custody-modes and PATCH /admin/v1/custody-modes/{mode} — the platform switch of the pass-through mode. The custodial mode cannot be switched off: 422 custody_mode_last_enabled.
  • The merchant API, the cabinet API and webhook bodies did not change. The wallet a merchant's money is forwarded to is entered by an operator with a signature made outside the service, not through an API.
  • Documented for integrations: what settled means for a project settled directly, why its balance stays at zero, and that small payments accumulate on the deposit address until a transfer is worth its cost — see where the money goes.

2026-10-04, balance of the project by API

  • Merchant API: GET /v1/balance returns the ledger balance of every asset the project holds something in, what is still unconfirmed beside it, and the same money in dollars at the rates the service has accepted. An asset without a fresh rate is listed and named in total.unpriced_assets rather than valued at a stale one. The answer carries an ETag. See balance.
  • No existing route or field changed.

2026-10-04, QR code of an invoice and conditional reads

  • Merchant API: POST /v1/invoices and GET /v1/invoices/{id} take the query parameter include=qr and then carry qr.address — the QR code of the payment address as an SVG image in a data: URL, about 11 KB. It is the image the checkout page draws, so a product that draws its own payment screen needs no QR library. Without the parameter the field is absent, and the change feed does not take it. See QR code.
  • Merchant API: GET /v1/assets, GET /v1/invoices, GET /v1/invoices/{id} and GET /v1/deposits/{tx_hash} answer with an ETag. A request that returns it in If-None-Match gets 304 Not Modified with no body while the answer has not changed; * always matches. Errors are not tagged. See conditional reads.
  • Breaking for a client that sent query parameters of its own: POST /v1/invoices and GET /v1/invoices/{id} now accept include only, and refuse any other query parameter with 400 validation_failed. Nothing documented used one; a client that appended its own tracking parameters to these two URLs must stop.

2026-10-04, invoice change feed

  • Merchant API: GET /v1/invoices returns the invoices of a project in the order their state changed — the catch-up path after a missed webhook. Parameters updated_since, status, limit (1–100, 50 by default) and cursor; the response carries invoices without the deposits field, and next_cursor. Pages are keyset: an invoice that changes during the walk does not shift a page. See change feed.
  • No existing route or field changed.

2026-10-04, the step of an amount in a token pegged to the dollar

  • Merchant API: the invoice amount in a token pegged to the dollar is rounded up to the cent of that token, not to its last decimal. A price of $229.00 at a rate of 0.9999 asks for 229.03, not 229.024488. Rounding stays up: the customer pays at least the price, and the difference is under a cent and in the merchant's favour. A token pegged to nothing keeps its old step of 0.00000001 of a unit. The rest of an invoice repriced after a quote window steps the same way. See price and exchange rate and ADR-0053.
  • No field or error code changed.

2026-10-04, payment details for your own page

  • Merchant API: the invoice response gained payment_uri, a link that opens a wallet with the transfer filled in. It is there while the exact amount is asked for: the invoice waits for money, its deadline has not passed and its quote still holds. Today only TON gives one; other networks return null, because they have no link scheme this service trusts.
  • Merchant API: GET /v1/assets gained confirmations, how many confirmations a transfer of the asset needs, and icon, the mark of the asset in the catalog. A merchant draws «2 of 20» and its own list of coins from them instead of inventing the values.
  • Compatible: no existing field changed or disappeared.
  • The first-run setup routes /setup/* are no longer in GET /v1/openapi.json: the document describes the merchant's own routes only.

2026-09-20, marking a withdrawal line paid by hand

  • Admin API: POST /admin/v1/withdrawals/{id}/lines/{line_id}/force-paid marks a line of a request paid without waiting for the chain, with a reason that is required. It is the only decision of staff on a request that moves money: it posts what the check would have posted, with the same keys. Lines of a request gained forced_by, forced_at and force_reason. See merchant withdrawals and ADR-0058.
  • The merchant API, the cabinet API and webhook bodies did not change.

2026-09-20, the fee of withdrawals in dollars

  • Admin API: POST /admin/v1/withdrawal-fees and POST /admin/v1/merchants/{id}/withdrawal-fees take amount_usd in place of amount — the fixed part in dollars. The service turns it into the coin at a fresh rate on every request and freezes it in the line; the amount of the coin is kept beside it and stands when no rate is fresh. The fee in effect and every row of the tariff gained an amount_usd field. Writing such a row without a fresh rate answers 503 rate_unavailable. See the fee of withdrawals and ADR-0059.
  • The merchant API, the cabinet API and webhook bodies did not change. The admin API stays compatible: amount is still accepted.

2026-09-20, a fee of its own for withdrawals

  • Admin API: the fee a merchant pays to withdraw its own money has its own tariff — GET /admin/v1/withdrawal-fees, POST /admin/v1/withdrawal-fees, GET /admin/v1/merchants/{id}/withdrawal-fees, POST /admin/v1/merchants/{id}/withdrawal-fees — and can be overridden for one project. New error codes withdrawal_fee_confirmation_required and withdrawal_fee_valid_from_in_past — see the fee of withdrawals. The decision — ADR-0059.
  • Breaking for clients of the admin API that set the fee of withdrawals through POST /admin/v1/payout-fees: that is now the tariff of payouts to third parties alone, and the fee of withdrawals is set by the routes above. The history of the old tariff was carried into the new one, so the numbers in effect did not change.
  • The merchant API, the cabinet API and webhook bodies did not change.

2026-09-19, merchant withdrawals

  • Cabinet API: requests to withdraw a project's money to the owner's wallets — GET /cabinet/v1/withdrawals/assets, GET /cabinet/v1/withdrawals, POST /cabinet/v1/withdrawals/preview, POST /cabinet/v1/withdrawals, GET /cabinet/v1/withdrawals/{id}, POST /cabinet/v1/withdrawals/{id}/cancel. A request holds several tokens and wallets, reserves the amounts with their fees at once, and needs a second-factor code the cabinet has checked — see withdrawals. The decision — ADR-0058.
  • Admin API: GET /admin/v1/withdrawals/summary, GET /admin/v1/withdrawals, GET /admin/v1/withdrawals/{id}, entering the hash of a manual transfer with POST /admin/v1/withdrawals/{id}/lines/{line_id}/tx, and refusals with POST /admin/v1/withdrawals/{id}/lines/{line_id}/reject and POST /admin/v1/withdrawals/{id}/reject — see merchant withdrawals. The payout tariff POST /admin/v1/payout-fees takes any asset of the catalog, not only one with payouts: it is also the fee of a withdrawal; GET /admin/v1/payout-fees lists every active asset.
  • New error codes of these routes: withdrawal_not_found, withdrawal_insufficient_balance, withdrawal_not_cancelable, withdrawal_line_not_found, withdrawal_line_not_open, withdrawal_tx_in_use, withdrawal_code_required.
  • The merchant API and webhook bodies did not change.

2026-09-19, funds in dollars in the cabinet

  • Cabinet API: GET /cabinet/v1/balances returns the new fields assets, projects and total next to balances: the balances per asset across all projects, per project and in total, valued in US dollars at the market rate. An asset without a fresh rate is left out of the sums and named in unpriced_assets — see cabinet API.
  • The merchant API, webhook bodies and error codes did not change.

2026-09-18, TON

  • A new network, ton — the TON mainnet, with the assets USDT_TON (6 decimals) and GRAM_TON, Gram, the native coin of TON (9 decimals). USDT_TON pays invoices; GRAM_TON pays them once the platform enables payments in it, like TRX_TRON. There are no payouts on TON. GET /v1/assets lists the TON assets once the operator enables the network — see networks and tokens and assets.
  • An address on TON is returned in the user-friendly form, 48 base64url characters, non-bounceable: UQ…, 0Q… on a test network. This is the form of address in POST /v1/addresses, in invoices, in GET /v1/deposits/{tx_hash} and in deposit.* webhooks, and the form to show the customer as it is — see addresses on TON. The contract of a TON token in GET /v1/assets is the jetton master in the raw form, 0: and 64 hexadecimal digits. Check your integration if it validates an address against the forms of TRON and EVM networks: accept the TON form.
  • A TON transaction hash is 64 hexadecimal characters without 0x, as on TRON; GET /v1/deposits/{tx_hash} accepts it. On TON, log_index is 0 for a token transfer and -1 for GRAM — see verify a transfer.
  • The checkout page routes return a new field, wallet_url: on TON, a ton://transfer link that opens a wallet application with the address, the amount still to pay and the token; null elsewhere. The page shows it as an "Open in wallet" button — see read an invoice for the page. Merchants do not need these routes.
  • Admin API: the family ton, whose chain_id is the global id of the network; chain_symbol of a token preview can be null, when the jetton keeps its symbol off chain; a TON jetton master is accepted in the raw form or a user-friendly one; the gas-wallet threshold and gas_wallet apply to TON too — see admin API.
  • Error codes and the bodies of invoice.* webhooks have not changed.

2026-09-16, payments in native coins of EVM networks

  • The platform can accept the native coins of EVM networks for payments — ETH_ETHEREUM, BNB_BSC, POL_POLYGON, ETH_BASE, ETH_ARBITRUM, ETH_OPTIMISM, AVAX_AVALANCHE — when it enables their network and payments in them. They behave like TRX_TRON: priced at an exchange rate, listed by GET /v1/assets while an invoice can be created in them, 18 decimals, no payouts — see networks and tokens and assets. The EVM networks are still in development.
  • Only a plain transfer from a wallet is credited. A coin sent by a smart contract — a smart-contract wallet, some exchanges — is not credited automatically, is not listed by GET /v1/deposits/{tx_hash}, and sends no webhook — see what arrives at an address.
  • A transfer of such a coin has log_index = -1 in GET /v1/deposits/{tx_hash} and in deposit.* webhooks, as a TRX transfer already does. Check your integration if it assumes log_index is never negative.
  • Routes, fields, webhook bodies and error codes have not changed. Admin API: PATCH /admin/v1/assets/{id} can switch on accepts_payments of the native coin of an EVM network together with its rate_symbol, and payments_supported is true for it — see admin API. The decision — ADR-0055.

2026-09-16, payments in TRX

  • The platform can accept TRX_TRON, the native coin of TRON, for payments, when the platform enables payments in it. GET /v1/assets lists TRX_TRON while an invoice can be created in it: take the list from there — see list the assets and assets.
  • Such invoices and choices behave like those of any asset priced at an exchange rate. An invoice created without asset and paid in TRX_TRON is converted at the rate lowered by the platform's markdown, and the rest still owed is quoted again after the quote window — see price and exchange rate. An invoice created with asset = TRX_TRON has its amount in TRX, set by the merchant.
  • Payouts in TRX_TRON are not possible: POST /v1/payouts answers 422 asset_not_payable.
  • Check your integration if it checks asset of an invoice, a transfer or a webhook against a fixed list: accept TRX_TRON. Take its decimals from GET /v1/assets, not from your code.
  • Routes, fields, webhook bodies and error codes have not changed. Admin API: PATCH /admin/v1/assets/{id} can switch on accepts_payments of TRX_TRON, and answers 409 asset_flag_locked for the native coin of an EVM network and for removing rate_symbol from an asset accepted without a peg to the US dollar — see admin API. The decision — ADR-0054.

2026-09-16, prices in US dollars at exchange rates

  • An invoice created without asset is converted into the chosen token at an exchange rate that the service collects, stablecoins included: 1 USDT is no longer taken as 1 USD. The amount is rounded up to the last decimal of the token, and to 8 decimals for a token with more, so the customer pays at least the price. A token not pegged to the US dollar is converted at its rate lowered by the platform's markdown. See price and exchange rate; the decision — ADR-0053.
  • Breaking for code that compares its price with amount_requested of an invoice created without asset: the price converted at a rate is not equal to price.amount. Compare with price.amount.
  • Breaking for code that reads amount_expected or amount_remaining of such an invoice once. After the quote window, while the invoice is created or partially_paid and before its deadline, the rest still owed is quoted again at the current rate, and these amounts and amount_requested change. No webhook is sent for that: read the amounts from GET /v1/invoices/{id}. See quote window.
  • A price that cannot be written exactly in a token is no longer refused with 400 validation_failed: it is rounded up.
  • A new field quote in the responses of POST /v1/invoices and GET /v1/invoices/{id}: rate, market_rate, markdown_percent, currency, number and expires_at. It is null for an invoice created with asset, before the choice, and for an invoice whose token was chosen before this change. See the quote.
  • Check your integration for a new error code: POST /v1/invoices without asset answers 503 rate_unavailable when none of the allowed tokens has a fresh rate, and no invoice is created. Retry later with the same order_id. 422 asset_not_offered now means that a token from currencies has no exchange rate, and 422 no_payable_assets that the merchant accepts no token with one. See error codes.
  • The checkout page routes: GET /checkout/v1/invoices/{id} returns rate, rate_id and rate_expires_at in each element of choices, and rates_unavailable and quote with rate, currency and expires_at on the invoice. POST /checkout/v1/invoices/{id}/select takes an optional rate_id and can answer 503 rate_unavailable. See save the token choice. Merchants do not need these routes.
  • Cabinet API: quote on invoices; POST /cabinet/v1/invoices can answer 503 rate_unavailable. Admin API: quote on invoices, new routes GET /admin/v1/rates and PATCH /admin/v1/rates/settings, and the asset field rate_symbol — see admin API.
  • Webhook bodies have not changed. Invoices created with asset behave as before.

2026-09-14, the gas wallet in the ledger

  • Admin API: a new ledger account kind gas_wallet in GET /admin/v1/balances and in the postings of GET /admin/v1/ledger/transactions — the gas wallet of an EVM network, in its native coin. The gas of a move to the treasury is posted with reason = fee and the key sweep_gas:{network}:{tx_hash}: debit network_fees, credit gas_wallet. See admin API.
  • The public API, webhooks and error codes have not changed.

2026-09-14, managing networks and tokens

  • The admin API manages networks and tokens — admin API. New routes: POST /admin/v1/networks, PATCH /admin/v1/networks/{id}, GET /admin/v1/networks/{id}/tokens/{contract}, POST /admin/v1/assets and DELETE /admin/v1/assets/{id}. PATCH /admin/v1/assets/{id} also changes minimums, confirmations, the sweep threshold, the flags of payments and payouts, archiving and the mark, and GET /admin/v1/assets accepts the network parameter.
  • New fields in admin API responses: icon_slug and rpc_configured on a network; pegged_to, sweep_min_amount, icon_slug and payouts_supported on an asset. New error codes: asset_exists, asset_in_use, asset_referenced, asset_confirmations_below_finality, asset_symbol_required, contract_invalid, contract_not_token, network_not_found, network_exists, network_without_assets, network_family_not_supported, network_rpc_not_configured, network_chain_id_mismatch, network_rpc_unavailable.
  • The checkout page routes GET /checkout/v1/invoices/{id} and POST /checkout/v1/invoices/{id}/select return network_icon_slug, the slug of the network's mark, on the invoice and in each element of choices — see read an invoice for the page. The merchant API has not changed. The new EVM networks appear in the registry switched off, and their assets are not offered to merchants.
  • New fields with the name of the network and links to its explorer. Invoices (POST /v1/invoices, GET /v1/invoices/{id}): network_name, address_explorer_url, and explorer_url in each element of deposits. GET /v1/deposits/{tx_hash}: explorer_url. Payouts: network_name and explorer_url. A link is built from the explorer template of the network and is null if the network has none — see create an invoice.

  • The checkout page route GET /checkout/v1/invoices/{id} has address_explorer_url and explorer_url in deposits, and gives address in the form shown to the customer: on EVM networks, with the EIP-55 checksum — see read an invoice for the page. The merchant API keeps EVM addresses in lower case.

  • New route GET /v1/assets: the assets the merchant can create an invoice in now, with network, network_name, token_standard, contract, decimals and min_amount. Use it to decide which tokens to offer instead of a list in your code — see list the assets.

  • GET /v1/deposits/{tx_hash} also accepts a hash in the form of EVM networks: 0x and 64 hexadecimal characters. The hash is looked up exactly in the form given, in any case; a TRON hash is still written without 0x — see verify a transfer.

  • EVM networks are still in development: until the operator enables one, GET /v1/assets lists only TRON assets, and nothing changes for a TRON integration. Error codes have not changed.

2026-09-14, the network in webhook events

  • A new field network in data of the invoice.* and deposit.* events: the id of the network of the asset, such as tron, the same value as network of the invoice and of the transfer in the API. In invoice.expired of an invoice whose customer did not choose a token, it is null. See webhooks.
  • The field is added; nothing is removed or renamed. Check your integration if it rejects unknown fields in a webhook body: accept network.
  • The service is getting ready to accept payments in EVM networks, where one transaction hash can exist in several networks. A transfer is identified by network, tx_hash and log_index together.
  • Routes and error codes have not changed.

2026-09-14, USDC on TRON for payouts only

  • Check your integration if it creates invoices in USDC_TRON or passes it in currencies. Circle has stopped minting USDC on TRON, and the operator has disabled payments in USDC_TRON: POST /v1/invoices in this asset, or with it in currencies, answers 422 asset_payments_disabled. Remove the asset from your requests or handle this code. See assets.
  • The checkout page does not offer USDC_TRON to the customer, and POST /v1/addresses does not include it in assets.
  • Payouts in USDC_TRON work. Invoices in USDC_TRON that are already open are still paid, and a USDC transfer to a per_user permanent address is credited as before.
  • Routes, fields and error codes have not changed. The rationale is in ADR-0026, amendment of 2026-09-14.

2026-09-14, partial payment

  • A new invoice status partially_paid: before the deadline, confirmed transfers cover part of amount_expected, and no transfer is waiting for confirmations. The customer can send the rest to the same address before the deadline. There is no tolerance for an underpayment: confirmed still means the whole amount, and underpaid arrives after a deadline without the rest. See partial payment.
  • A new webhook event invoice.partially_paid, on every transition to this status. See events.
  • A new field amount_remaining in the responses of POST /v1/invoices and GET /v1/invoices/{id}: amount_expected minus amount_received, not below zero; null until the customer has chosen the token. The checkout page route GET /checkout/v1/invoices/{id} has the same field.
  • Breaking for code that parses status or the event type against a strict list. A partially paid invoice used to stay seen before the deadline; now it is partially_paid, and seen means a transfer is waiting for confirmations. Add partially_paid to the list and do not fulfill the order on it. If your handler does not process invoice.partially_paid, respond 2xx to it.
  • Check your integration. The "Return to the shop" button leads to cancel_url with status=partially_paid too: the invoice is open and can still be paid — see return before an outcome.
  • On partially_paid the checkout page asks the customer to send amount_remaining to the same address, and on seen it no longer says that nothing more needs to be sent. See checkout page.
  • Cabinet API and admin API: partially_paid among the invoice statuses and in the status filter. Error codes have not changed.

2026-09-14, verifying a transfer and pausing an address mode

  • New route GET /v1/deposits/{tx_hash}: the transfers of a transaction to the merchant's addresses, with kind, status, confirmations and credited. A per_user top-up without an invoice can now be checked through the API, as the main rule of webhooks requires — see verify a transfer. New code 404 deposit_not_found.
  • Check your integration. The platform can pause an address mode. While a mode is paused, its projects get 422 address_mode_disabled from POST /v1/addresses, POST /v1/invoices and POST /checkout/v1/invoices/{id}/select, and a new project cannot choose the mode — see a paused mode. Transfers to addresses already issued are credited as usual. Handle the code like any other 422: tell the customer that payment is not available now.
  • Cabinet API: GET /cabinet/v1/address-modes; POST /cabinet/v1/merchants and POST /cabinet/v1/invoices can answer 422 address_mode_disabled. Admin API: GET /admin/v1/address-modes and PATCH /admin/v1/address-modes/{mode}, the code 422 address_mode_last_enabled — admin API.

2026-09-13, the failed payout event

  • The payout.failed webhook arrives on every transition of a payout to failed. Before, it arrived only when the watcher found the payout transaction unsuccessful. Now it also arrives when the signer refused to sign the payout, when attempts ran out, and when the signer itself found the transaction of an earlier attempt unsuccessful. There is one event per payout. See events.
  • Breaking for code that parses payout.failed strictly: its tx_hash can be null — for a payout nothing was ever signed for. The reason field is text for a human.
  • A payout no longer becomes failed when the node answered the broadcast of its transaction with an error or did not answer at all: the money may have left. It stays in queued with the same tx_hash until the transaction is found in the chain or expires without reaching a block. See payout statuses.
  • Routes and error codes have not changed.

2026-09-13, documentation in English

  • The documentation is published in two languages. English is the primary one, at the existing addresses: /docs, /docs/<section>.md, /llms.txt, and /llms-full.txt. Russian is under /ru: /ru/docs, /ru/docs/<section>.md, /ru/llms.txt, and /ru/llms-full.txt. Integrations that read the Russian Markdown at the old addresses should switch to the /ru addresses.
  • The checkout page opens in the customer's language: the lang parameter of checkout_url, then the choice the customer made on the page, then the browser language, otherwise English. See checkout page.
  • The API, error codes, and webhook events have not changed.

2026-09-13, the customer chooses the token

  • asset in POST /v1/invoices is now optional. Without it, the customer chooses the token and the network on the checkout page, and amount is the price in US dollars. A new optional field currencies lists the assets to choose from. See the customer chooses the token; the decision — ADR-0042.

  • An invoice has new fields price, currencies, and asset_selected_at.

  • Breaking for code that parses invoices strictly. For an invoice without a chosen token, these are null:

    • asset, network, address;
    • amount_requested, amount_expected, amount_received, amount_credited;
    • fees.service.fixed and fees.service.amount.

    Integrations that always pass asset do not create such invoices and need no changes. The same null values appear in the invoice.expired event of an invoice whose deadline passed without a choice, and in the responses of the cabinet API and the console.

  • A retry of POST /v1/invoices without asset compares the price and the set of currencies: a different set gets 409 order_id_conflict.

  • New codes: 422 asset_required, 422 no_payable_assets, 422 asset_not_offered; the checkout page route also has 409 asset_already_selected and 409 invoice_expired.

  • The checkout page route GET /checkout/v1/invoices/{id}: the fields price and choices; the asset and amount fields can be null. A new route POST /checkout/v1/invoices/{id}/select. Merchants do not need them.

  • POST /cabinet/v1/invoices: asset is optional; new field currencies.

  • Admin API: the invoice has the fields price, currencies, and asset_selected_at; the asset, the address, and the amounts can be null.

2026-09-13, invoice lifetime and return URLs

  • expires_in in POST /v1/invoices is now optional: without it, the invoice waits for the payment for 3600 seconds (1 hour).
  • Breaking for requests with a lifetime shorter than 5 minutes or longer than 72 hours. The bounds of expires_in are now 300 to 259200 seconds, in POST /v1/invoices and in POST /cabinet/v1/invoices. They used to be 60 to 604800 (7 days). A request outside the bounds gets 400 validation_failed, including a retry of a request accepted earlier: the request shape is validated before the invoice is looked up. If your integration sets a lifetime longer than 72 hours, reduce it. Invoices already created keep their lifetime.
  • Overrides of return_url and cancel_url in POST /v1/invoices are now also allowed within the project site from its application (site_url): the origin must match the project's URL of the same kind or the project site. On t.me and telegram.me the bot or channel name — the first path segment, case-insensitive — must match too; this rule now also applies to the project's URL of the same kind. Previously, without a URL of the same kind in the project settings, an override was rejected with 422 redirect_url_not_allowed. The format requirements for the URL have not changed — see return URLs.
  • Check your integration. While an invoice can be paid, the checkout page shows a "Return to the shop" button if the invoice or the project has a cancel URL. The button leads to cancel_url with a signature and status=created or status=seen: the invoice is still open and can still be paid. If your cancel URL handler cancels the order, have it check the status with GET /v1/invoices/{id} first — see return before an outcome. While the invoice is open, the page itself does not redirect anywhere.
  • POST /cabinet/v1/invoices: optional return_url and cancel_url under the same rules.
  • The checkout page route GET /checkout/v1/invoices/{id}: a new field back_url, the signed URL for the "Return to the shop" button.
  • The mapping of Heleket parameters is in checkout page.

2026-09-13, fee percentage and fixed amount

  • The service fee of an invoice now has a fixed amount per asset. The new field fees.service.fixed is the amount recorded in the invoice at creation; fees.service.amount is still everything withheld, which is now the percentage and the fixed amount together. The fields are only added. The same applies to the data of invoice.* events and to the responses of the cabinet API and the checkout page. See fees.
  • Check your integration. If the customer pays the service fee, amount_expected = price + percentage + fixed amount. Compare your price with amount_requested, as before. Until the operator sets a fixed amount, it is zero, and nothing changes.
  • The fixed amount is withheld once per invoice and never more than what was received: a payment smaller than the fixed amount is withheld in full. A per_user top-up without an invoice is a separate operation with its own fixed amount.
  • The payout fee now has a percentage of the amount: new fields fees.network.percent and fees.network.fixed, and fees.network.amount is the whole fee, the fixed amount plus the percentage of amount rounded down. The same applies to the data of the payout.confirmed and payout.failed events. See payout fee.
  • Admin API: the routes GET /admin/v1/invoice-fees and POST /admin/v1/invoice-fees, the codes invoice_fee_confirmation_required and invoice_fee_valid_from_in_past — admin API. Breaking for admin API clients: POST /admin/v1/payout-fees requires the percent field and accepts confirm_high_rate, and in responses the current fee and the tariff rows have a new percent field. The console was updated in the same change.

2026-09-13, network fee

  • Breaking for code that checks the invoice's fees.network.status against the list quoted, pending, settled. An invoice has no separate network fee: the network costs of accepting payments are part of the service fee, and fees.network.status is now always included. The former values no longer arrive. The keys are kept: quote, quote_rate, and actual are always null, payer is always merchant. If your integration checks the list strictly, add included. See fees. The same applies to invoice.* events and to the responses of the cabinet API and the checkout page.
  • The codes 422 network_fee_payer_not_supported and 503 network_fee_quote_unavailable remain: the customer does not bear the network fee. Only their messages have changed.
  • A payout has a new field fees.network: payer is always merchant, amount is the fixed network fee recorded in the payout at creation, charged is how much was charged to the project balance. The same is in the data of the payout.confirmed and payout.failed events. See payout fee.
  • The network fee is charged to the project balance when the payout is confirmed, as a separate ledger posting; a payout that failed on-chain does not carry it. The recipient still receives exactly amount.
  • Admin API: the tariff of the payout network fee — GET /admin/v1/payout-fees and POST /admin/v1/payout-fees, the codes payout_fee_confirmation_required and payout_fee_valid_from_in_past; a new ledger account kind network_cost_recovery in GET /admin/v1/balances and in postings — admin API.

2026-09-13, service fee

  • An invoice has new fields: amount_requested — the merchant's price, amount_credited — the amount received minus the fees withheld, fees — the service fee and the network fee. The fields are only added, and the existing ones keep their meaning: amount_expected is still the amount the customer must pay. See fees.
  • Check your integration. If the customer pays the service fee in a project, amount_expected is greater than the price by that fee. Code that compares amount_expected with its order price must compare amount_requested instead. In projects where the merchant pays the fee (the default), amount_expected equals the price, as before.
  • Idempotency of POST /v1/invoices by order_id compares the amount of the request with the amount_requested price: a retry sends the price, not the amount with the fee.
  • The service fee is withheld from each confirmed payment as a separate ledger posting and reduces the merchant's balance. The rate is recorded in the invoice at creation: a tariff change does not change invoices already issued.
  • The same fields are in the data of invoice.* events (webhooks) and in the response of the checkout page route; the checkout page shows the breakdown "Price · Service fee · Total" when the customer pays the fee (checkout page).
  • New code 503 network_fee_quote_unavailable: the project is configured so that the customer pays the network fee, but it cannot be calculated right now. The cabinet API no longer accepts such a setting — 422 network_fee_payer_not_supported.
  • Admin API: service fee tariffs — GET /admin/v1/service-fees, POST /admin/v1/service-fees/platform, GET /admin/v1/merchants/{id}/service-fees, POST /admin/v1/merchants/{id}/service-fees, the codes service_fee_confirmation_required and service_fee_valid_from_in_past — admin API. GET /admin/v1/fees remains the network fee report.

2026-09-13, project review and project settings

  • A project that a business creates by itself accepts payments only after review. Until it is approved, or if it is rejected, POST /v1/invoices and POST /v1/addresses respond with the new code 403 merchant_not_approved. API keys still work, and so does reading invoices. All merchants created earlier are approved: nothing changes for existing integrations.
  • The owner can disable accepting payments in an asset for their project. Then POST /v1/invoices in that asset responds with the new code 422 asset_disabled_by_merchant, and POST /v1/addresses does not include the asset in the assets list. Invoices that are already open are paid and closed, and a retry of an accepted request returns the invoice.
  • 403 merchant_disabled now means that the merchant was disabled either by the operator or by the project owner.
  • A project without a webhook URL receives no events: they are not queued and are not sent after a URL appears (webhooks).
  • Admin API: a merchant has new fields — review_status, review_reason, reviewed_at, reviewed_by, site_url, description, network_fee_payer, service_fee_payer, disabled_by. There is a new filter GET /admin/v1/merchants?review_status= and new routes POST /admin/v1/merchants/{id}/approve and POST /admin/v1/merchants/{id}/reject — admin API. Breaking for admin API clients: a merchant's webhook_url can be null. The console was updated in the same change.
  • The /cabinet/v1 routes for the cabinet application: internal, not reachable from outside, and not included in GET /v1/openapi.json.

2026-09-13, payment and payout switches

  • The operator can temporarily disable accepting payments or making payouts in an asset. While payments are disabled, POST /v1/invoices in that asset responds with the new code 422 asset_payments_disabled, and POST /v1/addresses does not include the asset in the assets list. While payouts are disabled, POST /v1/payouts responds with the new code 422 asset_payouts_disabled.
  • Retries of requests accepted earlier respond as before: the invoice for the same order_id and the payout for the same reference are returned.
  • Invoices that are already open are paid and closed, and payouts that are already created are executed: a switch affects only new ones.
  • Admin API: PATCH /admin/v1/assets/{id}, the payments_enabled and payouts_enabled fields in the asset object, new codes asset_not_found and asset_flag_locked — admin API.

2026-09-13, console admin API

  • The /admin/v1 routes for the operator console: merchants and their keys, invoices, balances of ledger accounts, fund movements, network fees, the asset catalog, webhook deliveries, and the audit log — admin API. Access requires the console token ADMIN_API_TOKEN and the operator header X-Operator; without the token the routes do not exist (ADR-0033).
  • The merchant's return URLs return_url and cancel_url are visible in the merchant object and are changed with PATCH /admin/v1/merchants/{id} under the rules of the checkout page.
  • New error codes, only for these routes: merchant_not_found, api_key_not_found, delivery_not_found, delivery_not_failed.
  • The /v1 routes and the GET /v1/openapi.json description have not changed: the admin routes are not included in it.

2026-09-13, checkout page and return URLs

  • An invoice has a new field checkout_url: a link to the checkout page if the operator has set it up, otherwise null.
  • Optional return_url and cancel_url fields in POST /v1/invoices: where the checkout page sends the customer back. They are allowed only within the origin of the URLs that the operator set for the merchant; otherwise the new code 422 redirect_url_not_allowed. The same fields appeared in the invoice response.
  • A retry of POST /v1/invoices with a different return_url or cancel_url returns 409 order_id_conflict, like a retry with a different amount. Requests without these fields behave as before.
  • The customer's return is signed: the parameters invoice_id, order_id, status, ts, signature, with an HMAC-SHA256 signature made with the webhook secret. The format and the verification code are described in checkout page. The decision — ADR-0032.
  • GET /checkout/v1/invoices/{id} is an internal route of the checkout page server, without an API key, on the private network. Merchants do not need it.

2026-09-12, USDC on TRON

  • New asset USDC_TRON: invoices and payouts in USDC are accepted on a par with USDT, 6 decimal places, 20 confirmations. The contract TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8 was read from the network, not taken from an article: symbol() = USDC, name() = USD Coin, decimals() = 6. The rationale and the limits of the decision are in ADR-0026.
  • USDC on a deposit address no longer counts as an unexpected asset by itself. unexpected_asset is an asset from the catalog that the address does not accept: at an invoice address, an asset other than the invoice's; at a permanent address, an asset not accepted for payments (TRX). A token that is not in the catalog is not recorded at all. The first version of this item called unexpected_asset a token outside the catalog; that was wrong and was corrected on 2026-09-14.
  • TRX has not become a payment asset yet: in energy burn mode (SWEEP_ENERGY_MODE=burn) the deposit address itself pays the sweep fee, so the operator puts TRX on it — and with the flag enabled, this operational money would be credited as someone's payment. First a rule that tells transfers from our own addresses apart, and a test; then the flag. TRX payouts will not appear even after that: daily limits add up amounts in a single unit, which is valid only for dollar-pegged assets.
  • The assets of the test networks have not changed: there is no verifiable USDC contract on Nile or Shasta.

2026-09-12, domain in the installer

  • POST /setup/domain: the panel finds the zone at the DNS provider that the given name belongs to, creates or fixes an A record pointing to the address of this server, and returns what public DNS shows right now. What is checked is not our own record but the network's answer: what was written and what is visible are different statements, and only the second one works.
  • If the provider has no such zone, the panel creates it by itself and shows the NS servers that must be set at the domain registrar: without delegation nobody sees the zone, however correct its records are. The zone is created under the registrable name — the last two labels.
  • The route's response says whether the provider sees the delegation (delegated), whether the zone was created (zone_created), and which NS servers are needed (nameservers).
  • The "Domain" step comes first and is optional: without a domain the service is reachable at the server's address. The panel itself writes the SETUP_DOMAIN and BUNNY_API_KEY variables; the provider key is used only to write and check records and is never shown back.
  • The first-run panel now opens in a browser at the server's address — the API port is published for the duration of setup by a single variable and goes back to loopback afterwards (ADR-0025). The /v1 routes have not changed.

2026-09-12, the installer writes the configuration itself

  • The first-run page no longer prints a block of variables to paste by hand: it writes the environment file on the server — POST /setup/env. The values are checked by the same parsers that the service uses to read its configuration at startup, so the written file is the file the service starts with. The /v1 routes have not changed.
  • POST /setup/signer-values and GET /setup/signer-env: the owner's choices for the signer and a ready environment file for it, together with the commands in order. The password of the database role and the two seed values are not in it: the first is already on this server, and the other two are created on that one.
  • The "Network and nodes" and "Environment file" steps are new and required. The second one is closed only when the values are not just written but also applied: variables are read once, at startup.
  • A secret that the page accepted is never returned: in the GET /setup/state response it has only an "is set" flag. An empty field means "do not change".
  • A field into which 12 or 24 short words were pasted is rejected: a mnemonic is not entered on this page in any field. The rationale and the limits are in ADR-0024.

2026-09-12, first-run page

  • While the service setup is not complete, the API serves the GET /setup page, which guides you step by step and checks each step. When all the required steps are closed, its routes respond 404. This does not affect merchants: the /v1 routes have not changed.
  • The internal routes of the page: GET /setup/state, POST /setup/verify, POST /setup/steps/{key}, POST /setup/merchant. They are available only from the private network and only with a one-time code from the service log. See first run.
  • The page does not accept or create the mnemonic, the passphrase, or the private half of the operator key — the rationale is in ADR-0024.

2026-09-12, sending payouts

  • The payout.confirmed and payout.failed events: the network confirmed the payout, or its transaction landed in a block as unsuccessful. The fields are in webhooks. As before, the webhook body is a "go and check" signal: get the payout state from GET /v1/payouts/{id}.
  • A payout in failed is not retried automatically: a new attempt requires a new reference.
  • The rest of this update is internal: signing payouts from the hot wallet, a second independent whitelist check together with the operator signature, and a circuit breaker that halts payouts by itself. The public routes have not changed.

2026-09-12, payouts

  • POST /v1/payouts: creating a payout, idempotent by reference. The response is 202 for a created payout and 200 for a retry. See payouts.
  • GET /v1/payouts/{id}: the current state of a payout with its status and review reasons.
  • A payout is possible only to an address on the merchant's whitelist, and only after the delay before its first use. Exceeding a limit does not reject a payout: it is accepted with the status pending_review and waits for human approval, and review_reasons names the limits exceeded. See limits.
  • New error codes: payout_not_found, reference_conflict, asset_not_payable, address_not_whitelisted, payouts_halted. See error codes.
  • Sending payouts to the network and the payout.confirmed and payout.failed webhook events will come in the next update: for now a payout reaches the queued status and waits for execution.

2026-09-12, sweeps

  • An invoice reaches the settled status: its funds have been moved to the cold treasury. For the merchant this is the same as confirmed, and the transition has no webhook — the order is fulfilled on confirmed, as before. See statuses.
  • The rest of this update is internal: signing and sending sweeps, confirming them on-chain, and ledger postings. The public routes and webhook events have not changed.

2026-09-11, operational routes

  • GET /admin/health: the state of the service for the operator and monitoring — the watcher lag in blocks and the depth of the webhook queue. See operational state.
  • GET /metrics: the same indicators in the Prometheus text format.
  • Both routes are internal: they require no authentication, contain no merchant data, and are reachable only on the private network.

2026-09-11, webhook delivery

  • Events from the queue go to the merchant's URL with the X-Signature signature and the X-Timestamp header, with retries on the schedule from the webhook documentation. Before this, events were only recorded.
  • The User-Agent header of outgoing webhooks contains the product name.
  • A 3xx response does not count as a successful delivery and is not followed.

2026-09-11, payment watcher

  • Invoices change status based on transfers on the network: seen, confirmed, underpaid, expired, paid_late. See statuses.
  • In the GET /v1/invoices/{id} response, the deposits field is filled with the payments for the invoice.
  • The service produces the webhook events invoice.seen, invoice.confirmed, invoice.overpaid, invoice.underpaid, invoice.expired, invoice.paid_late, deposit.confirmed, and deposit.orphaned. See events. Sending events to the merchant's URL will be enabled in the next update.

2026-09-11, addresses and invoices

  • Authentication with the X-Api-Key header, and request limits per key and per IP address — see authentication.
  • POST /v1/addresses: a permanent user address for merchants in per_user mode. See addresses.
  • POST /v1/invoices: creating an invoice, idempotent by order_id. See invoices.
  • GET /v1/invoices/{id}: the current state of an invoice with its payments.
  • GET /v1/openapi.json: a machine-readable description of the API in OpenAPI 3.1 format.
  • New error codes: validation_failed, unauthorized, merchant_disabled, invoice_not_found, address_mode_mismatch, order_id_conflict, asset_not_supported, amount_below_minimum, rate_limited, addresses_exhausted. See error codes.

2026-09-11, internal routes

  • Added the internal routes GET /healthz and GET /readyz — see monitoring.
  • Introduced a single error format { "error": { "code", "message" } } and the codes bad_request, route_not_found, internal_error, service_unavailable — see error codes.
  • Every response includes the X-Request-Id header for finding the request in the logs.