Docs
x402 payments
Price an HTTP endpoint in USDC, take payment from a Stellar wallet, and watch it settle.
WebSoroban Pay turns a normal HTTP endpoint into an x402 v2 resource settled in USDC on Stellar. You create and monitor the endpoint in Payment Studio. A buyer opens its public checkout, approves one authorization in their wallet, and gets the protected response with an on-chain receipt.
Testnet and Mainnet
Endpoints run on Stellar Testnet (test USDC) or Stellar Mainnet (real Circle USDC). New endpoints default to testnet. Every mainnet screen is marked in amber before a wallet asks for approval. Both networks settle through the OpenZeppelin x402 facilitator, which sponsors the network fee.
Create a paid endpoint#
- Sign in and open Payment Studio.
- Pick the network, then a name and URL slug.
- Set a USDC price, a payout address, and the JSON response message.
- Studio checks that the payout address exists and holds a USDC trustline. Settlement to an address without one always fails, so creation is blocked until the check passes.
- Create the endpoint and store its endpoint key. The key is shown once, and the
backend stores only its SHA-256 hash. Testnet keys start with
wsp_test_and mainnet keys withwsp_live_. - Use Inspect 402 to confirm the resource returns HTTP
402and aPAYMENT-REQUIREDheader.
Hosted resources use this shape:
GET https://YOUR_API/api/pay/resource/YOUR_SLUG # testnet
GET https://YOUR_API/api/pay/live/resource/YOUR_SLUG # mainnetOpening the resource in a browser shows a WebSoroban Pay handoff to the public checkout, so the buyer does not need a WebSoroban account.
Protect your own API#
npm install @web-soroban/pay @x402/core @x402/express @x402/stellar expressimport { createWebSorobanPay } from '@web-soroban/pay'
const pay = createWebSorobanPay({
apiUrl: process.env.WEBSOROBAN_API_URL!,
apiKey: process.env.WEBSOROBAN_PAY_KEY,
endpointId: process.env.WEBSOROBAN_ENDPOINT_ID!,
payTo: process.env.STELLAR_PAY_TO!,
})
app.use(pay.protect('GET /brief', '$0.01'))
const report = await pay.preflight() // key accepted? payout has a USDC trustline?The network comes from the key prefix, so a testnet key can never guard a
mainnet route. The fastest start is the Paid API endpoint template. It runs
the preflight on startup and includes pnpm pay, which makes a real paid
testnet request from your terminal.
Go live on mainnet#
- In Payment Studio, create a new endpoint with Mainnet selected. A testnet endpoint cannot be switched; its key stays testnet-only.
- Use a payout address that exists on mainnet and holds a trustline to Circle
USDC (issuer
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN). - Swap your server's
WEBSOROBAN_PAY_KEYandWEBSOROBAN_ENDPOINT_IDfor thewsp_live_key and new endpoint id.pay.preflight()confirms the key and the trustline on mainnet before you take traffic. - Make one small real payment (for example $0.01) from a mainnet wallet and check the receipt on Stellar Expert before announcing the endpoint.
Buyers need mainnet USDC and a USDC trustline. There is no faucet on mainnet.
Pay from a wallet#
x402 on Stellar asks the wallet to sign one Soroban authorization entry.
| Wallet | Hosted checkout |
|---|---|
| Freighter (browser extension) | Yes |
| Hana | Yes |
| HOT | Yes |
| Klever | Yes |
| Albedo, OneKey | Sign from your own x402 client |
| Freighter Mobile | Not supported (cannot sign auth entries) |
- Open the endpoint's Open checkout action, or continue from its raw
402 Payment Requiredpage. - Choose a wallet. Set it to the network shown on the checkout.
- The checkout checks the paying account's USDC trustline and balance before asking for anything.
- Review the exact asset, amount, recipient, and network, then approve.
- Wait for Payment settled, then open the receipt on Stellar Expert.
The facilitator sponsors the network fee. The authorization binds the exact token transfer: the facilitator can submit it, but cannot change the asset, amount, payer, or recipient. The checkout also verifies each signature against the exact authorization and your account before sending it.
Use an x402 client#
Programmatic clients read the PAYMENT-REQUIRED response, create an exact
Stellar payment payload, then retry with PAYMENT-SIGNATURE. Successful
responses expose PAYMENT-RESPONSE, which includes the settlement transaction.
npm install @x402/core@^2.25.0 @x402/fetch@^2.25.0 \
@x402/stellar@^2.25.0 @stellar/freighter-api@^6.0.1CORS requirements
A cross-origin resource must allow PAYMENT-SIGNATURE and expose
PAYMENT-REQUIRED, PAYMENT-RESPONSE, and PAYMENT-ERROR. WebSoroban's
hosted endpoints are already configured for these headers.
Reliability and failures#
Payment Studio shows the settlement success rate for the last 24 hours, 7 days, and 30 days. It also lists why payments failed, each reason with its fix. Failures that never reached the facilitator, such as spend caps and rate limits, are listed but do not count against the success rate.
- No USDC trustline: add USDC from the wallet's asset list, then retry.
- Insufficient USDC: fund the paying account. Testnet USDC is available at faucet.circle.com; on mainnet, buy or transfer Circle USDC.
- Fee below minimum: the network fee rose between quote and settlement. The checkout retries once automatically; otherwise approve again.
- Switch Freighter to Testnet/Mainnet: change the active network in the extension and approve again.
- Signature for a different authorization: reconnect the active account and approve again.
- Network mismatch: the key, wallet, or client is set to the other network.
- Failed to fetch after approval: the protected API's CORS policy is blocking the signed retry. Check the headers above.
- Spend cap: an agent signer over its daily USDC limit is rejected before settlement. Raise the cap or wait for the window to reset.
Webhooks#
Register payment.settled, payment.failed, or wallet.spend_limit_exceeded
in Payment Studio, then use Test to send a signed webhook.test delivery.
Deliveries are retried for about two hours. Each one carries
X-WebSoroban-Signature (t=<unix>,v1=<hmac>). Verify it against the raw body:
import { constructWebhookEvent } from '@web-soroban/pay'
app.post('/webhooks/pay', express.raw({ type: 'application/json' }), (req, res) => {
const event = constructWebhookEvent(secret, req.header('X-WebSoroban-Signature'), req.body)
res.sendStatus(204)
})Next#
- API reference: hosted resource and management routes.
- Wallets & faucet: wallet and network basics.
- Troubleshooting: errors across the IDE.
