Send a customer to this page with URL parameters. Prefilled amount and wallet show as titles instead of inputs; anything missing is asked once, then Stripe's widget handles identity, payment and delivery of USDC on Base. We return the customer to you with signed transaction details. Without lock=1 the customer can change the wallet inside Stripe's widget, so check the returned wallet.
Prefill parameters
| Param | Meaning |
|---|---|
wallet | Receiving Base address (0x…). Add lock=1 to prevent editing. |
amount or usdc | USD worth of USDC to buy, or USDC to receive. One or the other. Stripe adds its fees on top, so the wallet receives exactly this much USDC and the card is charged amount + fees. |
email, first_name, last_name | Prefills Stripe's identity step. |
redirect | https URL to return to after delivery. redirect_fail for rejected sessions (defaults to redirect). |
notify | https URL that receives a signed POST when the session completes or is rejected. |
ref | Your opaque reference (≤128 chars), echoed back untouched. |
theme | dark (default) or light. |
https://HOST/?wallet=0xB00F…28a2&lock=1&amount=100&ref=order_42&redirect=https://shop.example.com/topup/done
Return parameters
The redirect receives session, status (fulfillment_complete or rejected), amount (USDC), wallet, tx (Base transaction hash), ref, ts and sig.
Verify without an API key
1. Ask us. GET https://HOST/api/sessions/{session} returns the canonical status, amount, wallet and tx hash. Session IDs are unguessable; treat this as the source of truth.
2. Check the signature. Sort the returned fields except sig by key, join as key=value with & (URL-encoded), and verify the Ed25519 signature sig (base64url) against the public JWK at /.well-known/onramp-keys.json.
3. Check the chain. tx is a USDC transfer on Base to wallet; confirm it on any Base RPC or BaseScan.
// Node 20+
const { session, sig, ...rest } = Object.fromEntries(new URL(req.url).searchParams);
const canonical = Object.keys({ session, ...rest }).sort().map(k => `${encodeURIComponent(k)}=${encodeURIComponent({ session, ...rest }[k])}`).join('&');
const { keys: [jwk] } = await (await fetch('https://HOST/.well-known/onramp-keys.json')).json();
const key = await crypto.subtle.importKey('jwk', { kty: 'OKP', crv: 'Ed25519', x: jwk.x }, { name: 'Ed25519' }, true, ['verify']);
const ok = await crypto.subtle.verify({ name: 'Ed25519' }, key, Buffer.from(sig, 'base64url'), new TextEncoder().encode(canonical));
Notify webhook
POST JSON with the same fields under signed, plus headers x-onramp-timestamp and x-onramp-signature (Ed25519 over timestamp.body). Delivered once per terminal status; poll the session endpoint if you miss it.
Stripe is the merchant of record and performs KYC. Available to US customers (not Hawaii). Amounts are subject to Stripe's per-customer limits.