Skip to content

API Gateway

Overview

rail0 is a single immutable contract: buyer and merchant transact directly, and anyone can call it. The rail0 API Gateway is a convenience layer on top — a REST API at api.rail0.xyz that does the chain work a checkout would otherwise have to carry itself.

It does four things: it prepares everything a wallet has to sign, broadcasts what was signed and tracks it, confirms from the chain itself what actually happened, and notifies you. It holds no funds and no keys at any point: the gateway prepares, the wallet signs, the chain decides.

The API builds whatever needs a signature, so your integration never assembles chain data itself: the EIP-3009 payload a buyer signs to authorize or charge (and a merchant signs to refund), and the unsigned transaction for every operation, with gas sized for the chain it runs on. Nothing is signed on the gateway. Signing happens in the buyer’s or the merchant’s wallet, and the gateway checks that what comes back is what it prepared.

Hand back the signed transaction and the gateway sends it to the chain, retries transient failures, and follows it through pending → submitting → submitted → confirmed, or failed. A wallet that broadcasts for itself, like MetaMask, skips this step: report the hash to POST /payments/{id}/{operation}/submitted and tracking continues from there. Every payment carries in_flight while something is on its way, so a UI knows when to wait.

The gateway does not take its own word for it. It reads the rail0 contract’s events on every supported chain and applies an event only once its block is final by that chain’s rule (safe, finalized, or a confirmation depth), so a payment moves to captured when the chain says so, never before. A failed transaction records its decoded reason on the payment and leaves the payment’s status alone, so the operation can be prepared again. A missed event is caught up by transaction hash.

The clock runs here too: authorizations that lapse are expired, a submission that never mined times out, and the payments.authorization_expiring notice goes out ahead of authorization_expiry while there is still something to capture.

Every change arrives as a webhook: signed, delivered at least once, with retries and a per-subscription circuit breaker, and replayable from the API. Polling GET /payments/{id} works too; the webhook is just sooner. Merchant accounts also get sales analytics over their payments.

PartySigns in asTypical calls
Merchanta wallet registered to a gateway accountcreate webhooks, prepare and submit capture / void / refund, read analytics
Buyerany wallet — no account neededcreate and sign a payment, list its own payments, open a dispute
Anyonenothingread the catalog of chains and tokens, discover a merchant’s payment methods, health

Confirmation time is a property of the chain, not of the gateway. GET /blockchains reports each chain’s rule and, measured over the trailing week, the settlement percentiles from broadcast to confirmation, so a checkout can size its wait honestly.

The gateway serves these chains today. The waits are orders of magnitude, from broadcast to the gateway counting the payment as settled; read GET /blockchains for the live figures.

ChainTypical wait
Arc Testnetunder a second
Sonic Testnetunder a second
Polygon Amoyabout 2 seconds
Avalanche Fujiabout 7 seconds
Base Sepolia30 seconds to 2 minutes
Optimism Sepoliaabout 40 seconds
Arbitrum Sepolia7 to 20 minutes
Ethereum Sepolianot measured yet

The rail0 contract is deployed on more chains than the gateway serves; see Chains for the full list.

  • Hold funds or keys. The contract holds the escrow; the wallets hold the keys.
  • Act on its own. Every transaction that moves money is signed by the merchant or the buyer; the gateway only prepares, sends and watches.
  • Mirror what it did not prepare. Every rail0 operation can also be sent to the contract directly by the party it allows. That works on-chain, but the gateway does not apply it: the payment keeps its previous status here and no webhook fires. Operate through the gateway, or not at all, per payment.

Every endpoint lives at https://api.rail0.xyz; there is nothing to configure. The unversioned routes are v1 and are frozen: a breaking change ships as a separate /v2/… surface alongside them, so v1 clients keep working.