StatusPrivate betaPublic testnets — test tokens, no real valueMainnet rolls out per jurisdiction

Payments

Hosted checkout

The fastest way to collect a payment: send the payer to a page that connects their own wallet and submits the on-chain payment for you. You write no wallet code and never handle a private key.

Where it lives

After you create an intent, redirect the payer to the hosted checkout, keyed by the intent_id:

checkout URL
https://demo.rsends.io/pay/pi_9f1c4a7e2b5d8036c1e94ab7d2508f63

The intent_id is the only credential the page needs — it is 128 bits of randomness, so it is not guessable, but it is the key to that intent's checkout. Treat the link as you would a payment link anywhere else.

What the page does

The checkout re-fetches the intent's onchain instructions and walks the payer through a normal wallet flow:

  • Connect a wallet. The payer connects their own wallet (MetaMask, Coinbase Wallet, WalletConnect, …). The funds and the keys are always theirs.
  • Approve, then pay. For an ERC-20 token, the wallet approves the router for onchain.total, then calls pay(...). Native ETH skips the approval and calls payNative(...) with msg.value equal to the total. Tokens that support EIP-2612 (onchain.permitType: "eip2612", which includes USDC) can sign a permit instead of sending a separate approval transaction.
  • Settle atomically. One transaction: the router moves your amount to your wallet and emits PaymentMade. Your principal is never touched — on the currently-deployed router the payer additionally covers the flat fee, which is why the approval is for total, not amount.
the call the browser makes — routerVersion 1 (deployed today)
// Read straight from intent.onchain — nothing is invented client-side.
router.pay(
  invoiceId,   // onchain.invoiceId  (bytes32, derived from your reference)
  merchant,    // onchain.merchant   (your destination wallet)
  token,       // onchain.token      (e.g. USDC)
  amount,      // onchain.amount     (base units — what YOU receive)
  maxFee       // onchain.maxFee     (payer's ceiling; reverts above it)
)

// routerVersion 2 (mainnet cutover) drops the fee argument entirely:
// router.pay(invoiceId, merchant, token, amount)

Don’t hardcode the ABI — read routerVersion

The two routers take different arguments. onchain.routerVersion tells you which one you are on, and onchain.calldata ships ready-to-send bytes for whichever it is. Building against the field rather than the shape means the mainnet cutover needs no change on your side.

No card fields, no processor

There are no PCI form fields here and no payment processor in the path. The page is just a wallet front-end for an on-chain call. RSends is never a party to the transfer — it only prepared the parameters and will later observe the result.

The golden rule

When the payment lands, the browser will redirect or show a success screen. That is a UI hint, not proof of payment. A redirect can be faked, refreshed, or lost.

Never mark an order paid from the browser redirect

Treat the redirect as “the customer says they paid.” The authoritative signal is the payment.completed webhook — verified by its signature — or the on-chain PaymentMade event read directly. Fulfil only after you have reconciled against one of those.

Recommended flow

WhereDo
BrowserShow a friendly “processing / thank you” state on redirect. Do not fulfil yet.
Server (webhook)Receive payment.completed, verify the signature, then mark the order paid and fulfil.
AnywhereOptionally cross-check the PaymentMade event on-chain for full independence.

Build your own checkout?

You don't have to use the hosted page. Because the intent response hands you the full onchain object (router, routerVersion, invoiceId, token, amount, total, calldata, permitType), you can drive pay(...) from your own front-end. The settlement and reconciliation model is identical — the webhook and the chain remain the source of truth.

Your front-end can also read the payer-facing view of an intent directly, without an API key: GET /api/v1/public/payment-intent/{intent_id} returns status, amount, currency, chain, expires_at, merchant_name, tx_hash and the same onchain object — and nothing else. It is what the hosted checkout itself polls. The intent_id is the credential, so keep the link private; rate limit is 40/min per IP, plus a 240/min ceiling per intent across all callers.