Skip to content
STONS

Production checklist

Go through it before you accept real payments. Each item closes a known way to lose money. The order of the integration work is in turnkey integration.

Keys and access

  • The API key is kept in a secrets manager and used only on the server. It is not in a browser, a mobile app, a repository, or logs.
  • The webhook secret is kept the same way, separately from the API key. The same secret signs webhooks and the redirects from the checkout page.
  • The product calls the API only over https://: over http:// the key would travel in plain text before the redirect happens — see authentication.
  • The webhook URL in the project settings starts with https://: the cabinet (Dashboard) also accepts http://, and then events travel in plain text.

Webhooks

  • The signature is verified against the raw request body, before JSON parsing — see signature.
  • Signatures are compared in constant time.
  • Webhooks whose X-Timestamp differs from the current time by more than 5 minutes are rejected.
  • The decision to fulfill an order is based on the API response — GET /v1/invoices/{id} — not on the webhook body.
  • A per_user top-up is credited to the user only after GET /v1/deposits/{tx_hash} shows its transfer with status = confirmed and credited = true — see verify a transfer.
  • Duplicates are dropped by the event id, and a redelivery of the same event does not fulfill the order a second time.
  • The handler does not depend on the order of events: invoice.confirmed can arrive before invoice.seen.
  • The handler responds 2xx within 10 seconds, including to event types it does not process, and does heavy work asynchronously — see delivery.

Payments

  • An order is fulfilled only when the invoice status is confirmed or settled, not on seen and not when the transaction first appears — see statuses.
  • The product has defined what it does on underpaid, on paid_late, and on an overpayment — confirmed with amount_received greater than amount_expected.
  • An order is not fulfilled on partially_paid. If the product shows the payment itself, the customer sees amount_remaining and the same address, see partial payment.
  • Amounts are handled as strings or as integers in minimum units, never as floating-point numbers.
  • The number of decimal places is taken for the specific asset and is not hardcoded: USDT on BNB Chain has 18, not 6. An amount in an API response has exactly as many digits after the point as its asset has decimals.
  • The product compares its price with amount_requested and asks the customer for amount_expected — see fees.
  • Each order creates an invoice with its own order_id, and a retry after a timeout uses the same order_id with the same fields.

Display

  • The address is shown to the user in full, without shortening, together with the network and the asset — see displaying the address.
  • The user is warned that a transfer on another network or of another token is not credited automatically.
  • The integration accepts more than one transfer in a transaction and a negative log_index other than -1: a payment in the native coin of an EVM network sent by a smart contract, such as ETH or BNB from a smart-contract wallet, a multisig or an exchange's withdrawal, comes with -2, -3 and so on — see what arrives at an address.
  • An invoice created without asset is paid only through checkout_url: until the customer chooses a token, it has no address.

Checkout page

  • If the checkout page is used, the return and cancel URLs are set in the project settings: without them the page does not send the customer back — see return URLs.
  • The redirect signature and the 5-minute window of ts are verified, and the order is never fulfilled on a redirect — see signature.
  • A visit to the cancel URL with status=created, status=seen, or status=partially_paid does not cancel the order: the invoice can still be paid.

Payouts

  • Payout addresses are on the whitelist in advance: the operator adds them, and a new address becomes usable only after a delay, 24 hours by default.
  • A payout address is taken from the whitelist and never from transaction history.
  • Each payout is created with its own reference, and a retry uses the same reference.
  • pending_review is handled as an accepted payout waiting for a person, not as an error, and the outcome is taken from GET /v1/payouts/{id}.

Testing

  • The whole flow — creating an invoice, paying, receiving the webhook, checking the status through the API, and fulfilling the order — has been run with a small amount before accepting real payments.