Checkout V2

Create a checkout session with the end user's IP hash and platform declared. Requires a Signature V2 request.

📘

Checkout V2 requires a V2 signature

Every POST /checkout/v2/intent call must be signed with your Ed25519 key. There is no API-key-only mode on this endpoint: an unsigned or invalid request returns 401. See API: Sign requests (V2).

🚧

V1 is being deprecated

POST /checkout/intent still works, but it will be deprecated soon. Build new integrations on V2, and plan the migration if you are already live on V1.

Checkout V2 is the same checkout call with two extra fields:

  • clientIpHash: SHA256 of the end user's IP, hashed on your backend. We check the IP of the browser that opens the session against it, so a leaked session link is useless from another machine. Required.
  • platform: where your integration runs. Providers that only serve whitelisted platforms are dropped when you leave it out.

The flow

  1. The user starts a purchase in your app.
  2. Your backend takes the user's IP, hashes it with SHA256, and calls POST /checkout/v2/intent with a V2 signature.
  3. Onramper returns redirectUrl and sessionId.
  4. You send the user's browser to redirectUrl.
  5. Onramper checks the IP behind that browser against clientIpHash, then takes the user to the provider. A mismatch returns 403.

Step 1: add platform to your quotes call

GET /quotes/usd/btc?amount=100&paymentMethod=creditcard&platform=mobile-ios
ValueWhere your integration runsExample
webWeb browser, desktop or mobileReact app in Chrome or Safari
mobile-iosNative iOS appSwift or SwiftUI app
mobile-androidNative Android appKotlin or Java app
webview-iosWebView inside an iOS appReact Native WebView on iOS
webview-androidWebView inside an Android appReact Native WebView on Android
browser-extensionBrowser extensionChrome, Firefox, Edge
desktopNative desktop appElectron app

Pick the value that describes where the user actually is. A React Native WebView on iOS is webview-ios, not web.

Leaving platform out is not neutral: providers that require whitelisting disappear from quotes and cannot be used at checkout. Use the same value in the checkout body.

Step 2: call POST /checkout/v2/intent

Same body as POST /checkout/intent, plus:

FieldRequiredValue
clientIpHashYes64-character hex SHA256 of the end user's IP. Missing or malformed returns 400.
platformRecommendedThe same value you sent to quotes.

Both fields sit inside the signed body, so build them before you sign.

HeaderValue
AuthorizationYour API key
x-onramper-signaturev2:{base64_signature}
x-onramper-timestampISO 8601 timestamp
x-onramper-nonceUUID v4

Response:

{
  "redirectUrl": "https://buy.onramper.com?session=01ABC...&resumeToken=8f2c...",
  "sessionId": "01ABC...",
  "resumeToken": "8f2c..."
}

Step 3: send the user to redirectUrl

Redirect the browser to redirectUrl exactly as returned. It carries a one-time resume token, so treat it as a credential: do not log it, store it, or share it anywhere else. Each session is single-use and expires 10 minutes after intent.

Errors

At intent, on your backend:

StatusCause
400clientIpHash is missing or is not a 64-character hex hash
401Signature missing, expired, replayed or invalid (errorId 4011)

After the redirect, when the user's browser opens the session:

StatusCodeCause
403IP_MISMATCHThe IP opening the session does not match clientIpHash
404SESSION_NOT_FOUNDUnknown session
409SESSION_CONSUMEDSession already used
410SESSION_EXPIREDMore than 10 minutes since intent

Example

Signing uses the signApiRequestV2 helper from API: Sign requests (V2).

const API_BASE = 'https://api.onramper.com';
const PLATFORM = 'mobile-ios'; // match your integration type

function sha256(data: string): string {
  return createHash('sha256').update(data).digest('hex');
}

async function createCheckout(userIp: string, onramp: string) {
  const body = {
    onramp,
    source: 'eur',
    destination: 'btc',
    amount: 300,
    type: 'buy',
    country: 'de',
    paymentMethod: 'creditcard',
    network: 'bitcoin',
    wallet: { address: 'bc1q...' },
    clientIpHash: sha256(userIp),
    platform: PLATFORM,
  };

  const resp = await fetch(`${API_BASE}/checkout/v2/intent`, {
    method: 'POST',
    headers: {
      ...signApiRequestV2({
        method: 'POST',
        path: '/checkout/v2/intent',
        body,
        contentType: 'application/json',
      }),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  const { redirectUrl } = await resp.json();
  return redirectUrl; // send the user's browser here
}

Hash the end user's IP, not your server's. Read it from the connection your frontend makes to your backend.

Migrating from V1

StepChange
1Add platform to your quotes request
2Point checkout at /checkout/v2/intent
3Add clientIpHash to the checkout body
4Add platform to the checkout body, the same value as quotes
5Redirect to redirectUrl from the response instead of building a URL yourself

If you already sign with V2, the signing format does not change. Only the path, the two new fields and the response shape differ.


Did this page help you?