Skip to main content
Create a dedicated payment page for a specific customer and let them pay in stablecoins by scanning a QR code. QR Pay is a hosted payment page that StablePay issues for a specific customer. A merchant creates a QR Pay session through the Open API or in the Merchant Portal, receives the hosted page URL, and turns that URL into a QR code for a counter, a printed poster, a chat message, or an email. The customer scans the code, enters an email address and an amount, picks a stablecoin and a network, and pays either by on-chain transfer or with a wallet. Address allocation, the payment QR code, on-chain monitoring, payment confirmation, and result notifications are all handled by StablePay. Unlike Payment Links, every QR Pay session is bound to one customer, so the same customer can reuse the same payment entry point and the merchant can report cumulative payment volume and successful payment count per customer. The product was previously called Deposit Pay; its internal resources still use the customer_deposit and deposit_session_id naming, which refers to the same object described here.

Use Cases

Use QR Pay when the payer is known and the same customer is expected to pay more than once:
  • A fixed payment QR code at a checkout counter, service desk, or store poster.
  • A dedicated payment link or QR code sent to a specific customer through support chat, a community group, or email.
  • Balance top-ups for trading platforms, wallets, and account-based products.
  • A payment entry point that must stay valid for long-term customers without expiring.
  • Customers spread across networks, where the hosted page should guide network and token selection.
If you want a reusable payment page that can be shared publicly with anyone, use Payment Links. If you need a formal bill with a due date, use Invoicing. If your backend should create a one-time checkout session, use Checkout. QR Pay does not provide a refund endpoint; to return funds to a customer, use the Payouts Open API.

Workflow

  1. The merchant creates a QR Pay session in the Merchant Portal, or calls the create endpoint with a 10-digit external_customer_id and allow_multiple_deposits, optionally passing customer details, an expiry time, a description, and redirect URLs.
  2. StablePay looks up the customer by the current merchant and external_customer_id, creating the customer if it does not exist and reusing it if it does.
  3. The response returns only deposit_session_id and the hosted page url. No on-chain address is returned.
  4. The merchant renders the url as a QR code, or shares it with the customer directly.
  5. The customer opens the page, enters an email address and an amount, and selects a payment method.
  6. For on-chain transfer, the page shows the address, Memo/Tag, and QR code for the selected token and network, and the customer sends the transfer. For wallet payment, the entered amount becomes the fixed payable amount and the standard wallet payment flow takes over.
  7. Once the payment is confirmed, StablePay creates the payment record, shows the success state on the page, and delivers the payment.completed webhook.
  8. Successful payments follow the standard settlement pipeline and settle in the same token on a T+1 calendar-day basis.

Core Capabilities

Customer Model

A customer is a merchant-scoped object shared with subscriptions. There is no separate deposit-customer subtype. An external_customer_id is unique within a merchant and cannot be changed after creation; the same ID under two different merchants refers to two completely independent customers, with no visibility into each other’s profile or on-chain addresses. Name, email, and phone passed when creating a QR Pay session are only used to create a new customer. An existing customer is never overwritten. One customer can own many QR Pay sessions, each with its own address, cumulative volume, and successful payment count. Merchants do not need a separate customer endpoint: the customer page in the Portal can create customers directly, and the Open API creates them on demand while creating a QR Pay session. The email field on the hosted page is prefilled with the customer email and can be edited; the submitted value is a snapshot for that payment only and is never written back to the customer.

Addresses and QR Codes

Addresses are allocated deterministically by merchant_id + deposit_session_id + address_family, so two QR Pay sessions never share an address even when they belong to the same customer. Within one session, EVM networks such as Ethereum, Base, and Arbitrum reuse a single EVM address, while non-EVM families such as TRON and Solana each get their own address. An address is allocated lazily the first time its family is requested and stays the same afterwards. Networks that require a Memo/Tag must display the address and the Memo/Tag together, since both are needed as the complete deposit credential. The page also tells the customer to send only the selected token on the selected network. On-chain addresses are shown only to the end customer on the hosted page after token and network selection. Neither the create nor the retrieve endpoint returns an address, and the Portal list, session detail, and customer detail pages never display addresses, address families, supported networks, or Memo/Tag values.

Expiry and Repeat Payments

allow_multiple_deposits is required on creation and has no default. When set to false, the session is single-use: after the first successful payment the status becomes closed, the page stops showing the address, and there is no way to pay again. When set to true, the customer can pay repeatedly while the session is valid. expires_at takes a Unix timestamp and must be later than the current time. Omitting it means the session never expires, and the response returns null. There is no default expiry and no maximum lifetime. The public status has three values: active (payable), closed (closed after a single-use payment completed), and expired. Note that losing validity does not invalidate the on-chain address. If a customer saved the address in their wallet and sends funds after the session was closed or expired, StablePay still recognises the transfer, books it, and creates a payment record marked outside_session. Such payments follow the same fee, settlement, and success webhook rules as normal payments.

Amounts, Tokens, and Fees

QR Pay supports only the USD stablecoins configured by StablePay. There is no fiat funding, fiat billing, or token conversion. All amounts travel as decimal fixed-point strings, never as floating point numbers. On-chain transfer does not compare the amount entered by the customer with the amount that arrives on chain, so there are no underpayment, overpayment, or partial-payment states: any positive amount that qualifies for booking and reaches the required confirmations is a successful payment. Wallet payment treats the entered amount as a fixed payable amount and reuses the standard payment validation and status flow. In both cases the final record is based on the amount actually paid. USDT is charged in USDT and USDC in USDC, with no conversion between them. The fee is calculated on the successfully paid amount, fee_amount = actual_amount × fee_rate, with a default rate of 1% that can be configured per merchant. The rate and fee are snapshotted when the payment succeeds, so later rate changes never apply retroactively. The settled amount is net_amount = actual_amount − fee_amount. Fee rate, fee amount, and net amount are internal settlement data and are never returned through the Payments API, webhooks, or the Merchant Portal.

Payment Results and Notifications

For on-chain transfers, the Trade Order is created only after the transfer reaches the required confirmations and passes risk checks, so the external status is paid from the start with no intermediate processing state. For wallet payments, a pending order is created when the customer selects that method and then follows the standard pending, processing, paid, failed, expired, and canceled flow. Successful payments are reported through the existing payment.completed event, with data.object.payment_type set to customer_deposit and with customer_id and deposit_session_id included. No webhook is sent while a transfer is detected or confirming, none is sent when a session is closed or expired, and settlement completion does not add a new event type. Signature verification, Event ID, retry behaviour, and idempotency follow the existing webhook contract, so merchants must handle duplicate deliveries.

Settlement

Successful payments follow the standard settlement pipeline and settle on T+1 calendar days, with weekends and holidays processed normally: payments that succeed on day T enter a settlement batch on day T+1. The settled amount is net_amount in the same token, with no fiat conversion.

Merchant Portal

The Portal lists QR Pay sessions as the primary record instead of flattening individual payments into the list. Filters cover customer ID, QR Pay ID, status, and a creation-time range. Each row shows the customer, cumulative payment volume, successful payment count, status, and creation time, with actions to open the detail view and copy the payment link. Cumulative volume is the sum of all payments with payment_status=paid under that session; USDT and USDC are displayed together as USD-equivalent value without any conversion between tokens. Creating a session requires selecting an existing customer from search results; free-text customer entry is not allowed. The form sets expiry, whether repeat payments are allowed, a description, and metadata. The Portal does not expose token or network configuration, because the hosted page always offers every supported stablecoin and network enabled for the merchant. After creation the Portal only shows the copyable payment link and never returns or displays an on-chain address. The detail page shows configuration, cumulative volume, and successful payment count first, then the payment records linked to that session, without fee rate, fee amount, or net amount. The customer detail page has a dedicated QR Pay section listing all sessions for that customer, again without on-chain addresses.

API Overview

Requests use a Bearer API key plus the X-StablePay-Timestamp, X-StablePay-Nonce, and X-StablePay-Signature headers, and creation requests must also carry an Idempotency-Key. The signature payload is {timestamp}.{nonce}.{requestBody}, signed with HMAC-SHA256 and encoded as lowercase hex; for GET requests requestBody is an empty string. See Getting Started for the full authentication and signing guide. The API reference pages linked below are currently published in Simplified Chinese.

Create QR Pay

Example response (201 on first creation, 200 on idempotent replay; values are placeholders)
Creation returns 201; a replay with the same Idempotency-Key returns 200 with the same session. A 400 is returned when external_customer_id is not 10 digits, allow_multiple_deposits is missing, or a redirect URL is not an absolute HTTPS URL. An idempotency-key conflict returns 409. A session that does not exist or belongs to another merchant returns 404 resource_not_found.

QR Pay vs. Other Payment Options

Important Notes

  • QR Pay does not provide a refund endpoint. To return funds to a customer, use the Payouts Open API and record the original payment ID in metadata for auditing. This does not create an automatic refund relationship.
  • Customer withdrawals, fiat funding, fiat billing, and token conversion are not supported.
  • Closing or expiring a session does not change how its address is booked. Transfers that arrive outside the session window are marked outside_session and keep the same fee and settlement rules.
  • On-chain addresses are shown only to the end customer on the hosted page. No API response or Portal page returns an address.
  • Available tokens, networks, and minimum amounts depend on the token-network configuration enabled for the merchant. See Supported Wallets and Supported Networks.
  • Customer-facing pages support merchant logo and brand colour. See Branding Settings.
Last modified on October 27, 2026