Skip to main content

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:
VariantDescriptionUI Work
Hosted CheckoutRedirect to checkout.nowramp.com — we handle the entire UINone
Custom UIBuild your own checkout page using session + onramp APIFull 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

FieldRequiredDescription
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"
sourceAmountNoFiat amount (e.g., "100")
sourceCurrencyNoFiat currency code (e.g., "USD", "EUR")
destinationCurrencyNoCrypto currency code (e.g., "ETH", "BTC", "USDC")
networkNoBlockchain network (e.g., "ethereum", "polygon", "bitcoin")
destinationAddressNoWallet address to receive crypto
redirectUrlNoURL to redirect the user after payment completes
countryNoISO 2-letter country code (auto-detected from IP if omitted)
expiresInSecondsNoSession TTL. Default: 1800 (30 min). Max: 86400 (24 hours).
metadataNoPartner 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.
fieldLocksNoExplicit 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. 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:
  1. Loads the session and applies field locks
  2. Shows available currencies and payment methods (filtered by user’s country)
  3. Fetches live quotes from all enabled gateways
  4. Lets the user select a gateway and confirm
  5. Redirects to the payment gateway for checkout
  6. 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):
MethodHow 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

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

StepKey TypeEndpoint
Create sessionsk_ (secret key)POST /v1/sessions
Load sessionpk_ (public key)GET /v1/sessions/:sessionId
Confirm sessionpk_ + clientSecretPOST /v1/sessions/:sessionId/confirm
Get quotesNoneGET /public/v1/onramp/quotes
Get supportedNoneGET /public/v1/onramp/supported
Create orderpk_ + clientSecretPOST /v1/sessions/:sessionId/order
Cancel sessionsk_ (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

StatusDescription
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

ErrorHTTPDescription
SESSION_EXPIRED409Session has expired. Create a new one.
SESSION_ALREADY_USED409An order was already created from this session.
FORBIDDEN403Invalid clientSecret.
VALIDATION_ERROR400Missing or invalid fields in the request body.
NOT_FOUND404Session 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

EnvironmentAPI URLCheckout URL
Sandboxhttps://api.sandbox.nowramp.comhttps://checkout.sandbox.nowramp.com
Productionhttps://api.nowramp.comhttps://checkout.nowramp.com
Use sandbox for testing. All the same endpoints work with sk_test_ / pk_test_ keys.

Passing Your Order ID (Partner Metadata)

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.

Step 1: Attach metadata when creating the session

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,
      }
    });
  }
});

Metadata limits

ConstraintLimit
Max top-level keys20
Max serialized size4KB
Max nesting depth2 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