Checkout
Hosted on our origin, so no merchant code runs where a buyer is asked to sign.
The checkout is hosted by us, on our origin. That is a security boundary rather than a convenience: everything the buyer signs is built where no merchant code runs, so a compromised shop cannot alter what a wallet is asked to approve.
How the buyer gets there
Creating a payment returns a checkout_url. Redirect to it, or
render it as a QR for a point of sale. There is also a deeplink for opening
the UnifiWallet app directly.
The checkout token is a capability, not an identifier.
Whoever holds that URL can quote and submit that checkout. Do not log it, do not put it in an analytics event, and do not include it in a URL you hand to a third-party script. We keep it out of our own logs for the same reason.
What the buyer does
- Connects a wallet, or scans to open one.
- Picks an asset and chain from what you accept.
- Sees the exact amount, the fee and what the merchant receives.
- Signs one transaction.
There is no account to create and no password. The wallet is the identity.
Connect on the checkout, not on your site
A common request is to connect the wallet on the merchant's own page and hand us a signature. We do not support it, and the reason is worth stating plainly: the moment wallet-connection code runs on a merchant origin, the security of every buyer depends on the security of every merchant's frontend — their dependencies, their tag manager, their CMS plugins.
Hosting it ourselves means one origin to secure and one place to audit what a buyer is asked to sign.
Paying without gas
A buyer holding only a stablecoin can still pay. Where the token supports it, the checkout collects an EIP-3009 authorisation — the buyer signs, and the transaction is submitted for them. They never need the native token of the chain.
The signature names the recipient and the amount, so nobody submitting it can change either.
Deposit addresses
For wallets that cannot sign a contract call, the checkout can issue a one-time deposit address derived deterministically for that payment. Funds sent there are settled by anyone who calls the factory, and can be reclaimed by the buyer after expiry without our involvement.
We make both calls ourselves as a convenience. A sufficient deposit is
forwarded, and retried if that fails. After expiry, whatever was not forwarded
— a short payment, a late one, the wrong token, a second transfer — goes back
to the refund address, and payment.deposit_returned carries the
transaction.
The amount must arrive in full: the address enforces the exact required amount
on chain, so a deposit short by any amount is underpaid until the
buyer sends the rest. Deposit addresses are for ERC-20 tokens only — a
native-coin transfer leaves nothing on chain for us to observe — so a buyer
paying in ETH uses their wallet.
The address is derived in part from the buyer's refund address, so it is fixed once issued — changing it afterwards would strand anything already sent.
Rate locks
A payment priced in fiat locks a rate when it is created. The buyer sees what they must send and how long it holds. If the lock expires before they pay, the checkout re-quotes rather than settling at a stale price.
Statuses the buyer sees
| State | What the page shows |
|---|---|
awaiting_payment | Choose an asset and pay. |
settling | Transaction seen, waiting for confirmations. |
completed | Done, with a link to the transaction. |
underpaid | What is still outstanding, and how to send it. |
expired | This link is closed. Ask the merchant for a new one. |
Branding
The checkout carries your display name and logo. It does not carry your domain, and that is deliberate: buyers should be able to recognise the page where they are asked to sign a transaction, and one consistent origin is easier to verify than a thousand merchant-shaped ones.