Session-Based Checkout
Sessions let you create a secure, tamper-proof checkout from your backend using a secret key (sk_), then redirect users to complete payment. This is ideal when you need server-side control over transaction parameters — locking the wallet address, amount, or currency so the end user can’t change them.
There are two variants:
Variant Description UI Work Hosted Checkout Redirect to checkout.nowramp.com — we handle the entire UI None Custom UI Build your own checkout page using session + onramp API Full control
How It Works
Step 1: Create a Session
Create a session from your backend using your secret key (sk_). This is the only step that requires a secret key — everything else uses public keys or no auth.
Request
curl -X POST https://api.nowramp.com/v1/sessions \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalCustomerId": "player_12345",
"sourceAmount": "100",
"sourceCurrency": "USD",
"destinationCurrency": "ETH",
"network": "ethereum",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21",
"redirectUrl": "https://yoursite.com/payment-complete",
"type": "onramp",
"metadata": {
"partnerOrderId": "game_purchase_789",
"itemId": "sword_of_destiny"
}
}'
Session metadata flows into order.metadata.partnerMetadata when an order is created. The same data is included in order.created and order.completed webhook payloads, so you can correlate orders back to your system without extra lookups.
Response (201 Created)
{
"type" : "session" ,
"id" : "sess_abc123" ,
"attributes" : {
"sessionId" : "sess_abc123" ,
"projectId" : "550e8400-e29b-41d4-a716-446655440000" ,
"clientSecret" : "sess_secret_xyz789" ,
"type" : "onramp" ,
"status" : "pending" ,
"customerId" : "cust_auto_created" ,
"externalCustomerId" : "player_12345" ,
"sourceAmount" : "100" ,
"sourceCurrency" : "USD" ,
"destinationCurrency" : "ETH" ,
"destinationAddress" : "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21" ,
"network" : "ethereum" ,
"redirectUrl" : "https://yoursite.com/payment-complete" ,
"fieldLocks" : {
"sourceAmount" : { "locked" : true },
"sourceCurrency" : { "locked" : true },
"destinationCurrency" : { "locked" : true },
"destinationAddress" : { "locked" : true },
"network" : { "locked" : true }
},
"expiresAt" : "2026-02-17T15:30:00Z" ,
"createdAt" : "2026-02-17T15:00:00Z"
}
}
Save the clientSecret immediately. It is only returned once on creation and cannot be retrieved later. You’ll need it to redirect users and authorize frontend operations.
Session Fields
Field Required Description externalCustomerIdYes* Your user/player ID. At least one of externalCustomerId or customerId is required. customerIdYes* NowRamp customer UUID. If you provide externalCustomerId instead, a customer is auto-created. typeNo "onramp" (default) or "offramp"sourceAmountNo Fiat amount (e.g., "100") sourceCurrencyNo Fiat currency code (e.g., "USD", "EUR") destinationCurrencyNo Crypto currency code (e.g., "ETH", "BTC", "USDC") networkNo Blockchain network (e.g., "ethereum", "polygon", "bitcoin") destinationAddressNo Wallet address to receive crypto redirectUrlNo URL to redirect the user after payment completes countryNo ISO 2-letter country code (auto-detected from IP if omitted) expiresInSecondsNo Session TTL. Default: 1800 (30 min). Max: 86400 (24 hours). metadataNo Partner data attached to the session. Propagates to order.metadata.partnerMetadata when an order is created, and is included in webhook payloads. Max 20 keys, 4KB, 2 levels of nesting. fieldLocksNo Explicit field lock overrides (see below)
Auto-Locking
When you provide a field value in the session, it is automatically locked — the user cannot change it in the checkout UI. For example, if you set destinationAddress, the wallet address field will be read-only.
You can override this behavior with explicit fieldLocks:
{
"destinationAddress" : "0x742d..." ,
"fieldLocks" : {
"destinationAddress" : { "locked" : false }
}
}
Step 2: Redirect to Checkout
After creating the session, you have two options.
Option A: Hosted Checkout (Recommended)
Redirect the user to our hosted checkout page. We handle the full checkout UI — currency selection, quote comparison, payment, and redirect.
https://checkout.nowramp.com?sessionId={sessionId}&clientSecret={clientSecret}&projectId={projectId}
Example redirect from your backend:
// Node.js / Express
app . post ( '/buy-crypto' , async ( req , res ) => {
const session = await fetch ( 'https://api.nowramp.com/v1/sessions' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . NOWRAMP_SECRET_KEY } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
externalCustomerId: req . user . id ,
sourceAmount: '100' ,
sourceCurrency: 'USD' ,
destinationCurrency: 'ETH' ,
network: 'ethereum' ,
destinationAddress: req . user . walletAddress ,
redirectUrl: ` ${ process . env . APP_URL } /payment-complete` ,
type: 'onramp' ,
}),
}). then ( r => r . json ());
const { sessionId , clientSecret , projectId } = session . attributes ;
const checkoutUrl = new URL ( 'https://checkout.nowramp.com' );
checkoutUrl . searchParams . set ( 'sessionId' , sessionId );
checkoutUrl . searchParams . set ( 'clientSecret' , clientSecret );
checkoutUrl . searchParams . set ( 'projectId' , projectId );
res . redirect ( 302 , checkoutUrl . toString ());
});
# Python / Flask
@app.route ( '/buy-crypto' , methods = [ 'POST' ])
def buy_crypto ():
session = requests.post( 'https://api.nowramp.com/v1/sessions' , json = {
'externalCustomerId' : current_user.id,
'sourceAmount' : '100' ,
'sourceCurrency' : 'USD' ,
'destinationCurrency' : 'ETH' ,
'network' : 'ethereum' ,
'destinationAddress' : current_user.wallet_address,
'redirectUrl' : f ' { APP_URL } /payment-complete' ,
'type' : 'onramp' ,
}, headers = {
'Authorization' : f 'Bearer { NOWRAMP_SECRET_KEY } ' ,
'Content-Type' : 'application/json' ,
}).json()
attrs = session[ 'attributes' ]
checkout_url = (
f "https://checkout.nowramp.com"
f "?sessionId= { attrs[ 'sessionId' ] } "
f "&clientSecret= { attrs[ 'clientSecret' ] } "
f "&projectId= { attrs[ 'projectId' ] } "
)
return redirect(checkout_url, code = 302 )
That’s it. The hosted checkout page handles everything:
Loads the session and applies field locks
Shows available currencies and payment methods (filtered by user’s country)
Fetches live quotes from all enabled gateways
Lets the user select a gateway and confirm
Redirects to the payment gateway for checkout
After payment, redirects back to your redirectUrl
What Is Hosted Checkout?
Hosted checkout is a pre-built, NowRamp-hosted checkout page that partners can redirect users to instead of building their own UI. Think of it like Stripe Checkout — you create a session on your server and send the user to our page to complete payment.
Benefits:
Zero frontend work — no React components or SDK needed
Always up to date — new gateways, currencies, and features are available immediately
Responsive — works on mobile and desktop
Partner branding — custom logo, colors, and background per project
Secure — session parameters are locked server-side, preventing client-side tampering
When to use hosted checkout:
You want the fastest possible integration
You don’t need a custom checkout UI
You’re building a backend-only integration (mobile apps, game servers, etc.)
You want to redirect users from any platform (web, mobile, email links)
Option B: Custom UI
If you want full control over the checkout experience, use the session endpoints combined with the public onramp API to build your own UI.
2a. Load the Session (frontend)
curl https://api.nowramp.com/v1/sessions/sess_abc123 \
-H "Authorization: Bearer pk_live_YOUR_PUBLIC_KEY"
This returns the session details (amount, currency, wallet, field locks) so you can populate your UI. Locked fields should be displayed as read-only.
2b. Fetch Quotes (no auth needed)
curl "https://api.nowramp.com/public/v1/onramp/quotes? \
projectId=YOUR_PROJECT_UUID& \
fiatCurrency=USD& \
cryptoCurrency=ETH& \
network=ethereum& \
fiatAmount=100"
Returns ranked quotes from all enabled gateways. Display these to the user so they can pick a provider.
2c. Create Order from Session
When the user selects a quote and confirms, create the order:
curl -X POST https://api.nowramp.com/v1/sessions/sess_abc123/order \
-H "Authorization: Bearer pk_live_YOUR_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "sess_secret_xyz789",
"fiatAmount": "100",
"fiatCurrency": "USD",
"cryptoCurrency": "ETH",
"network": "ethereum",
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21",
"gateway": "gateway_a",
"email": "player@example.com"
}'
Response (201 Created):
{
"type" : "order" ,
"id" : "ord_xyz" ,
"attributes" : {
"orderId" : "ord_xyz" ,
"status" : "created" ,
"sessionId" : "sess_abc123" ,
"checkout" : {
"method" : "redirect" ,
"url" : "https://checkout.gateway-a.com/..." ,
"expiresAt" : 1800
}
}
}
2d. Redirect to Payment Gateway
Open the checkout.url in the user’s browser:
window . location . href = order . attributes . checkout . url ;
Or embed it in an iframe if the gateway supports it (check checkout.method):
Method How to Use redirectNavigate the browser to checkout.url iframeLoad checkout.url in an <iframe> widgetInitialize the gateway’s JavaScript widget
After payment, the gateway redirects the user back to the redirectUrl from the session.
Step 3: Track the Transaction
Option A: Webhooks (Recommended)
Configure a webhook URL in the Partner Dashboard to receive real-time notifications:
{
"type" : "transaction.completed" ,
"data" : {
"transaction" : {
"id" : "txn_xyz789" ,
"status" : "completed" ,
"fiatAmount" : "100.00" ,
"cryptoAmount" : "0.0523" ,
"transactionHash" : "0xabc123..."
}
}
}
See the Callback URLs guide for full setup.
Option B: Poll the Transaction Status
curl "https://api.nowramp.com/public/v1/onramp/transactions/ord_xyz"
Authentication Summary
Step Key Type Endpoint Create session sk_ (secret key)POST /v1/sessionsLoad session pk_ (public key)GET /v1/sessions/:sessionIdConfirm session pk_ + clientSecretPOST /v1/sessions/:sessionId/confirmGet quotes None GET /public/v1/onramp/quotesGet supported None GET /public/v1/onramp/supportedCreate order pk_ + clientSecretPOST /v1/sessions/:sessionId/orderCancel session sk_ (secret key)POST /v1/sessions/:sessionId/cancel
Secret keys (sk_) must never be exposed in client-side code. Only use them from your backend server. Public keys (pk_) are safe for frontend use.
Session Lifecycle
Status Description pendingSession created, awaiting user action confirmedUser confirmed session parameters (optional step) processingOrder created, payment in progress completedPayment and crypto delivery successful expiredSession expired before use (default: 30 minutes) cancelledSession cancelled by partner backend failedPayment or delivery failed
Error Handling
Error HTTP Description SESSION_EXPIRED409 Session has expired. Create a new one. SESSION_ALREADY_USED409 An order was already created from this session. FORBIDDEN403 Invalid clientSecret. VALIDATION_ERROR400 Missing or invalid fields in the request body. NOT_FOUND404 Session ID does not exist.
{
"success" : false ,
"error" : {
"code" : "SESSION_EXPIRED" ,
"message" : "Session has expired"
}
}
Full Example: Hosted Checkout (curl)
# 1. Your backend creates a session
RESPONSE = $( curl -s -X POST https://api.nowramp.com/v1/sessions \
-H "Authorization: Bearer sk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"externalCustomerId": "player_999",
"sourceAmount": "50",
"sourceCurrency": "USD",
"destinationCurrency": "USDC",
"network": "polygon",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21",
"redirectUrl": "https://yoursite.com/success",
"type": "onramp"
}' )
# 2. Extract the values you need
SESSION_ID = $( echo $RESPONSE | jq -r '.attributes.sessionId' )
CLIENT_SECRET = $( echo $RESPONSE | jq -r '.attributes.clientSecret' )
PROJECT_ID = $( echo $RESPONSE | jq -r '.attributes.projectId' )
# 3. Build the redirect URL
echo "https://checkout.nowramp.com?sessionId= $SESSION_ID &clientSecret= $CLIENT_SECRET &projectId= $PROJECT_ID "
# 4. Redirect your user to that URL — done!
Environments
Environment API URL Checkout URL Sandbox https://api.sandbox.nowramp.comhttps://checkout.sandbox.nowramp.comProduction https://api.nowramp.comhttps://checkout.nowramp.com
Use sandbox for testing. All the same endpoints work with sk_test_ / pk_test_ keys.
A common need is to attach your own order ID, user ID, or other tracking data to a session so you can match it back when you receive webhooks. Use the metadata field on session creation — it flows through the entire pipeline automatically.
curl -X POST https://api.nowramp.com/v1/sessions \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalCustomerId": "player_12345",
"sourceAmount": "100",
"sourceCurrency": "USD",
"destinationCurrency": "ETH",
"network": "ethereum",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21",
"redirectUrl": "https://yoursite.com/payment-complete",
"type": "onramp",
"metadata": {
"orderId": "your-internal-order-123",
"userId": "player_12345"
}
}'
Step 2: User completes checkout
Your metadata is stored on the session. When an order is created (either via hosted checkout or your custom UI), the metadata is automatically propagated to the order as partnerMetadata.
Step 3: Receive it in webhooks
When the order completes (or fails, is refunded, etc.), your webhook receives the full metadata:
{
"type" : "order.completed" ,
"data" : {
"order" : {
"id" : "ord_xyz789" ,
"type" : "buy" ,
"status" : "completed" ,
"source" : { "currency" : "USD" , "amount" : "100.00" },
"destination" : {
"currency" : "ETH" ,
"amount" : "0.0523" ,
"address" : "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21"
},
"customerId" : "cust_uuid" ,
"externalCustomerId" : "player_12345" ,
"metadata" : {
"provider" : "banxa" ,
"providerOrderId" : "banxa-order-abc" ,
"email" : "player@example.com" ,
"partnerMetadata" : {
"orderId" : "your-internal-order-123" ,
"userId" : "player_12345"
}
},
"createdAt" : "2026-02-25T10:00:00.000Z" ,
"completedAt" : "2026-02-25T10:05:00.000Z"
}
}
}
Step 4: Match it back in your webhook handler
app . post ( '/webhooks/ramp' , ( req , res ) => {
res . status ( 200 ). json ({ received: true });
const order = req . body . data . order ;
const partnerData = order . metadata ?. partnerMetadata ;
if ( partnerData ?. orderId ) {
// Update your internal order with the ramp result
db . orders . update ({
where: { id: partnerData . orderId },
data: {
rampOrderId: order . id ,
rampStatus: order . status ,
cryptoAmount: order . destination . amount ,
}
});
}
});
Constraint Limit Max top-level keys 20 Max serialized size 4KB Max nesting depth 2 levels
// PASSES: { "orderId": "abc", "tags": { "env": "prod" } } → depth 1
// PASSES: { "orderId": "abc", "ctx": { "org": { "id": "x" } } } → depth 2
// FAILS: { "a": { "b": { "c": { "d": 1 } } } } → depth 3
Also works with direct checkout-intent
If you’re using the onramp API directly (without sessions), pass partnerMetadata on the checkout-intent request:
curl -X POST https://api.nowramp.com/public/v1/onramp/checkout-intent \
-H "Content-Type: application/json" \
-d '{
"projectId": "your-project-uuid",
"gateway": "banxa",
"fiatCurrency": "USD",
"fiatAmount": "100",
"cryptoCurrency": "ETH",
"network": "ethereum",
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21",
"partnerMetadata": {
"orderId": "your-internal-order-123"
}
}'
System keys (provider, providerOrderId, email, paymentMethodId) are reserved at the top level of order metadata. Your data is always safely namespaced under partnerMetadata — even if you pass keys with the same names, they won’t overwrite the system values.
Next Steps
Callback URLs Set up webhooks to track transaction status in real-time
Onramp API Full API reference for quotes, checkout, and transactions
Authentication API key types, rate limits, and security best practices
React Components Drop-in checkout form if you want an embedded UI instead