Klarna — Gr4vy vs. Radial

Migrating Klarna from Radial to Gr4vy

This guide is for merchants who have already implemented Klarna through Radial’s JavaScript integration and are moving to Gr4vy. It focuses on what changes, what stays the same, and where the two approaches differ in ways that require real code changes.


Overview of the Two Approaches

The most important thing to understand upfront: Radial and Gr4vy have fundamentally different integration architectures for Klarna, not just different API endpoints.

DimensionRadialGr4vy
Integration layerCustom Radial JS SDK wrapping KlarnaDirect Klarna SDK or Gr4vy redirect
AuthenticationJWT via Radial OAuth endpointBearer token via Gr4vy SDK
Session managementradial.initKlarnaPayment()Gr4vy transactions.create() + /session endpoint
Authorizationradial.processKlarnaPayment() → Radial auth APIKlarna SDK authorization_token → Gr4vy transactions.create()
Multi-page checkoutDedicated klarnaMultiPageSetup() / updateKlarnaOrder() methodsStateless — re-create the transaction or use the session token
Server whitelistingRequired (CORS)Not required
Recurring/MITNot described in Radial integrationFirst-class support via store: true

Authentication

In Radial, you authenticate server-side against a Radial OAuth endpoint and pass the resulting JWT to the browser. That token is then passed explicitly into every Radial SDK call.

# Radial: get a JWT
POST https://hps-auth.uat.radial.com/oauth2/token
?grant_type=client_credentials&client_id=xyz&scope=hostedpayments-dev/web-ui-js
Authorization: Basic {Base64String}

The JWT then flows into the frontend:

// Radial: JWT threaded into every SDK call
radial.initKlarnaPayment(orderJsonObject, divId, jwt);

In Gr4vy, authentication happens entirely on your server using a private key. The frontend never handles auth credentials. Your server creates transactions and sessions via the Gr4vy SDK; the browser only receives what Gr4vy explicitly returns (like a client_token or approval_url).

// Gr4vy: server-side only, private key never touches the browser
const gr4vy = new Gr4vy({
  id: "example",
  server: "sandbox",
  bearerAuth: withToken({ privateKey }),
});

What this means for migration: Remove any logic that passes auth tokens to the browser. If your current frontend code receives or stores the Radial JWT, that pattern goes away entirely.


The Order Data Object

This is one area where the underlying structure is largely the same, because both systems use Klarna’s own order object schema. Fields like purchase_country, purchase_currency, order_amount, order_lines, and billing_address carry over directly.

However, there are key differences in how and where you pass this data:

In Radial, the order object is constructed in the browser and passed directly to the Radial JS SDK on the client side.

In Gr4vy, the order data is sent from your server as part of transactions.create(), or for Express Checkout, embedded in the frontend SDK’s on_click payload. Gr4vy’s API uses cartItems at the transaction level, which maps to Klarna’s order_lines — you may need to transform your existing order line construction logic.

Radial requires amounts in minor units (e.g., $10.00 = 1000). Gr4vy uses the same convention. This does not change.


Checkout Flows

Single-Page Checkout

Radial flow:

  1. Call radial.initKlarnaPayment(orderData, divId, jwt) on page load / when Klarna is selected
  2. Customer interacts with the Klarna widget (rendered into #klarna-payments-container)
  3. On submit, call radial.processKlarnaPayment(paymentMethodCategories, true)
  4. On SUCCESS, capture authorization_token and call Radial’s InstantFinanceAuthRequest API server-side

Gr4vy equivalent (Direct / Standard Checkout):

  1. Server calls transactions.create() with integration_client: "web", receives a session_token
  2. Server fetches /transactions/:id/session?token=:session_token, receives client_token
  3. Browser initializes Klarna’s own JS SDK directly with the client_token
  4. Customer interacts with the Klarna widget
  5. Klarna SDK returns authorization_token to the browser
  6. Browser sends authorization_token to your server
  7. Server calls Gr4vy’s default_completion_url with the token appended as a query parameter — transaction moves to authorization_succeeded

Key difference: In Radial, step 4 (the final auth) is a Radial-specific API call (InstantFinanceAuthRequest). In Gr4vy, you complete the transaction by hitting the default_completion_url returned in the session response — no separate authorization API to learn.

Step 1 — Create the transaction

Request

POST https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions
Authorization: Bearer {token}
Content-Type: application/json
 
{
  "amount": 6350,
  "currency": "USD",
  "country": "US",
  "payment_method": {
    "method": "klarna",
    "country": "US",
    "currency": "USD"
  },
  "cart_items": [
    {
      "name": "Cool Thing 1",
      "quantity": 1,
      "unit_amount": 5000,
      "sku": "coolthing1",
      "type": "physical"
    },
    {
      "name": "Cool Thing 2",
      "quantity": 1,
      "unit_amount": 1000,
      "sku": "coolthing2",
      "type": "physical"
    },
    {
      "name": "Shipping",
      "quantity": 1,
      "unit_amount": 500,
      "type": "shipping_fee"
    },
    {
      "name": "Tax",
      "quantity": 1,
      "unit_amount": 300,
      "type": "sales_tax"
    }
  ],
  "integration_client": "web"
}

Response

{
  "type": "transaction",
  "id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
  "status": "buyer_approval_pending",
  "amount": 6350,
  "currency": "USD",
  "payment_method": {
    "type": "payment-method",
    "method": "klarna"
  },
  "session_token": "s_tok_abc123..."
}

Step 2 — Fetch the session

Use the session_token from the transaction response to retrieve the client_token for the Klarna SDK.

Request

GET https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions/ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625/session?token=s_tok_abc123...
Authorization: Bearer {token}

Response

{
  "session_data": {
    "client_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
    "purchase_country": "US",
    "purchase_currency": "USD",
    "amount": 6350
  },
  "default_completion_url": "https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions/approve/s_tok_abc123...",
  "integration_client": "web"
}

Pass session_data.client_token to the Klarna JS SDK to render the widget. Store default_completion_url — you will need it in step 7.

Step 7 — Complete the transaction

After the Klarna SDK returns an authorization_token, your server appends it to the default_completion_url as a query parameter and makes a GET request.

Request

GET {default_completion_url}&authorization_token={klarna_authorization_token}

Response

{
  "type": "transaction",
  "id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
  "status": "authorization_succeeded",
  "amount": 6350,
  "currency": "USD",
  "payment_method": {
    "type": "payment-method",
    "method": "klarna"
  }
}

A status of authorization_succeeded means the transaction is authorized and ready to capture. Any other status should be treated as a failure — prompt the customer to select an alternate payment method.


Multi-Page Checkout

This is where the migration requires the most rethinking.

Radial has dedicated SDK methods for multi-page flows:

  • radial.initKlarnaPayment() on the payment page
  • radial.klarnaMultiPageSetup(clientToken, sessionId, paymentCategories, containerID) on the review page
  • radial.updateKlarnaOrder() if the order changes between pages
  • radial.processKlarnaPayment(categories, false) (note: autoFinalize: false)
  • radial.klarnaFinalize() for Bank Transfer specifically

Gr4vy does not have equivalent multi-page-specific methods. Instead:

  • The session token and client_token returned by transactions.create() are stateless — you store and re-use them across pages as needed
  • If the order changes between pages, you create a new transaction rather than calling an update method
  • There is no klarnaFinalize() equivalent — Bank Transfer and other methods follow the same completion pattern

What to replace:

Radial methodGr4vy equivalent
radial.klarnaMultiPageSetup()Re-initialize Klarna SDK with the stored client_token on the review page
radial.updateKlarnaOrder()Create a new Gr4vy transaction with updated order data; get a new client_token
radial.klarnaFinalize()Use the same default_completion_url flow for all payment methods
autoFinalize: false flagNo equivalent flag needed — the completion URL call handles finalization

If the order changes between pages

Create a new transaction with the updated order data. The old client_token and session_token are abandoned — do not attempt to update them.

Request

POST https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions
Authorization: Bearer {token}
Content-Type: application/json
 
{
  "amount": 7350,
  "currency": "USD",
  "country": "US",
  "payment_method": {
    "method": "klarna",
    "country": "US",
    "currency": "USD"
  },
  "cart_items": [
    {
      "name": "Cool Thing 1",
      "quantity": 1,
      "unit_amount": 5000,
      "sku": "coolthing1",
      "type": "physical"
    },
    {
      "name": "Cool Thing 2",
      "quantity": 2,
      "unit_amount": 1000,
      "sku": "coolthing2",
      "type": "physical"
    },
    {
      "name": "Shipping",
      "quantity": 1,
      "unit_amount": 500,
      "type": "shipping_fee"
    },
    {
      "name": "Tax",
      "quantity": 1,
      "unit_amount": 350,
      "type": "sales_tax"
    }
  ],
  "integration_client": "web"
}

Response

{
  "type": "transaction",
  "id": "f32bcd12-ef01-4a8b-bc3d-1d4e82fa0037",
  "status": "buyer_approval_pending",
  "amount": 7350,
  "currency": "USD",
  "session_token": "s_tok_def456..."
}

Fetch the new session (/transactions/\{new_id\}/session?token=s_tok_def456...) to get a fresh client_token and default_completion_url, then re-initialize the Klarna SDK on the review page with the new client_token.


Redirect vs. Direct Integration

Gr4vy offers a redirect integration that Radial does not. If your checkout flow can tolerate a redirect to a Klarna-hosted page, this is the simplest migration path.

Request

POST https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions
Authorization: Bearer {token}
Content-Type: application/json
 
{
  "amount": 1299,
  "currency": "USD",
  "country": "US",
  "payment_method": {
    "method": "klarna",
    "redirect_url": "https://example.com/callback",
    "country": "US",
    "currency": "USD"
  }
}

Response

{
  "type": "transaction",
  "id": "7c2a9b3d-1e4f-4a8c-bb2d-0f5e83cb1129",
  "status": "buyer_approval_pending",
  "payment_method": {
    "type": "payment-method",
    "method": "klarna",
    "approval_url": "https://cdn.sandbox.spider.gr4vy.app/connectors/klarna/apm.html?token=..."
  }
}

Redirect the buyer to payment_method.approval_url. Once the buyer completes the Klarna flow, they are sent back to your redirect_url and the transaction moves to authorization_succeeded.

This requires no client-side SDK setup, no client_token handling, and no authorization_token management. If your previous Radial integration used a custom-rendered Klarna widget and you want to preserve that experience, use the Direct / Standard Checkout path instead.


Express Checkout

Klarna Express Checkout renders a Klarna-hosted payment button directly in your page, with no session initialization required beforehand.

The flow differs from Standard Checkout:

  1. Get a client_id from Gr4vy’s /payment-services/\{id\}/sessions endpoint (or hard-code it from your Klarna Business Portal)
  2. Load the Klarna Payments Buttons SDK and call Klarna.Payments.Buttons.init(\{ client_id \})
  3. In the on_click handler, call authorize() with the order payload
  4. On approval, send the authorization_token to your server
  5. Server creates the Gr4vy transaction with the token in connectionOptions["klarna-klarna"].authorizationToken

Step 1 — Get the client ID

Request

POST https://api.sandbox.{gr4vy-id}.gr4vy.app/payment-services/{payment_service_id}/sessions
Authorization: Bearer {token}

Response

{
  "client_id": "klarna_live_client_abc123..."
}

Step 5 — Create the transaction with the authorization token

After the Klarna Payments Buttons SDK returns authorization_token in its on_click callback, your server creates the Gr4vy transaction directly — no session fetch or completion URL call is needed.

Request

POST https://api.sandbox.{gr4vy-id}.gr4vy.app/transactions
Authorization: Bearer {token}
Content-Type: application/json
 
{
  "amount": 2999,
  "currency": "USD",
  "country": "US",
  "payment_method": {
    "method": "klarna"
  },
  "cart_items": [
    {
      "name": "Example Product",
      "quantity": 1,
      "unit_amount": 2999,
      "sku": "item-001",
      "type": "physical"
    }
  ],
  "connection_options": {
    "klarna-klarna": {
      "authorization_token": "{klarna_authorization_token}"
    }
  }
}

Response

{
  "type": "transaction",
  "id": "b89de321-cc42-41a0-95f1-2b3c7dea0045",
  "status": "authorization_succeeded",
  "amount": 2999,
  "currency": "USD",
  "payment_method": {
    "type": "payment-method",
    "method": "klarna"
  }
}

The transaction moves directly to authorization_succeeded — no redirect or completion URL step is required. The authorization_token from the Klarna SDK expires in approximately 60 minutes; create the transaction immediately after receiving it.


Error Handling

Radial surfaces errors in two layers: Radial’s own error codes (e.g., 40001 InvalidAuthenticationError, 50003 RadialInternalError) from the auth endpoint, and Klarna-level action codes (SUCCESS, FAILURE, ERROR) from the JS SDK response.

Gr4vy surfaces errors through standard HTTP status codes and the transaction status field (authorization_succeeded, authorization_failed, etc.). The Klarna-specific action code pattern goes away — you react to the transaction status instead.

The FAILURE / ERROR distinction in Radial (where FAILURE means Klarna declined and ERROR means a system problem) maps roughly to checking whether the transaction moved to authorization_failed vs. whether your API call returned a 4xx/5xx response.

Error response shape

A failed API call returns a standard error body:

{
  "type": "error",
  "code": "unauthorized",
  "status": 401,
  "message": "No valid API authentication found",
  "details": []
}

A declined authorization returns a completed transaction response with a non-success status:

{
  "type": "transaction",
  "id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
  "status": "authorization_failed",
  "amount": 6350,
  "currency": "USD",
  "payment_method": {
    "type": "payment-method",
    "method": "klarna"
  }
}

In both cases, prompt the customer to select an alternate payment method.


Unsupported Capabilities to Be Aware Of

Gr4vy’s Klarna connector does not support the following, which may affect your implementation if you relied on adjacent features:

  • Partial authorization — Klarna cannot partially approve an amount
  • Over-capture — You cannot capture more than the authorized amount (Klarna enforces this strictly; the OMS logic described in Radial’s docs about not exceeding the authorization amount still applies)
  • Settlement reporting — Gr4vy does not automatically reconcile Klarna settlements
  • Create session (as a standalone call) — Session data is returned as part of transactions.create(), not as a separate endpoint