Klarna Direct

Payment Tokenization for Hosted Checkout integration

Save a customer's Klarna payment method from a hosted checkout using the Klarna Web SDK. Create a Payment Request with a tokenization intent, launch the Klarna Purchase Journey, and store the resulting customer token for future on-demand and recurring authorizations.

Overview

This guide shows how Partners issue a Klarna customer_token from a hosted checkout that uses the Klarna Web SDK. The Web SDK presents Klarna in the payment selector, the Partner's backend creates a Payment Request that asks Klarna to issue a token, and the SDK launches the Klarna Purchase Journey so the customer can grant consent. Klarna then issues the customer_token that the Partner stores and reuses for subsequent authorizations.
Tokenization always requires a customer-present consent step in the Klarna Purchase Journey. Klarna will never issue a customer_token without it.
This page focuses only on what is specific to tokenization:
  • Choosing the right Web SDK intent for the tokenization use case.
  • Calling createPaymentRequestAPI with request_customer_token from the SDK's initiate callback.
  • Handling the Payment Request completion to retrieve the customer_token.
  • (Optional) Finalizing an initial Payment Transaction in the tokenization combined with authorization flow.
For everything else (loading the SDK, registering domains, presenting Klarna in the checkout, mounting payment buttons, configuring initiationMode, and listening to SDK events), see the canonical Klarna Web SDK guide. This page does not repeat that material.
Where this fits in the integration

Prerequisites

Before integrating tokenization with the Web SDK, ensure that the following are in place:
  • A working Klarna Web SDK integration that already presents Klarna and completes a one-time payment. Tokenization extends this integration, it is not a parallel one.
  • Webhook subscriptions for payment.request.state-change.completed events, registered as described in Webhooks registration.
  • A checkout or account-management flow where the customer is present and able to complete the Klarna Purchase Journey to give consent.
  • Customer-facing copy that clearly discloses tokenization terms (free-trial billing date, subscription frequency, on-demand re-use), as required by the chosen token scope.

Integration overview

Tokenization with the Web SDK reuses the standard Web SDK flow. The only differences are the intent passed on payment presentation, the request_customer_token field added to the Payment Request body, and how the completion webhook is handled.
sequenceDiagram autonumber participant C as Customer participant FE as Partner frontend (Web SDK) participant BE as Partner backend participant K as Klarna FE->>K: Initialize Klarna Web SDK (clientId, products) FE->>K: klarna.Payment.presentation({ currency, intent: "SUBSCRIBE" }) K-->>FE: Payment presentation (Klarna option + payment button) C->>FE: Selects Klarna and clicks the payment button FE->>FE: SDK invokes the initiate callback FE->>BE: POST /api/start-tokenization (paymentOptionId, klarnaNetworkSessionToken?) BE->>K: createPaymentRequest (request_customer_token, currency, customer_interaction_config) K-->>BE: payment_request_id, payment_request_url (state SUBMITTED) BE-->>FE: { paymentRequestUrl } FE->>K: Web SDK launches the Klarna Purchase Journey (paymentRequestUrl) C->>K: Authenticates and approves tokenization K-->>BE: Webhook payment.request.state-change.completed alt Tokenization only Note over K,BE: state COMPLETED, customer_token in state_context.klarna_customer BE->>BE: Persist customer_token else Tokenization combined with authorization Note over K,BE: state COMPLETED, klarna_network_session_token BE->>K: authorizePayment with Klarna-Network-Session-Token header K-->>BE: customer_token_response + payment_transaction_response end K-->>FE: SDK on("complete") callback fires FE->>C: Confirm tokenization (and payment, when combined)

Integration details

Step 1: Determine the tokenization scenario

Tokenization with the Web SDK supports two scenarios. The choice drives the createPaymentRequest body and how the completion webhook is handled.
ScenarioDescriptionWeb SDK intent
Tokenization onlySave a payment method without an immediate charge (for example, free trials, wallet linking, or account setup). The Payment Request completion delivers the customer_token directly. No second API call is needed.SUBSCRIBE or ADD_TO_WALLET
Tokenization combined with authorizationSave a payment method and charge the customer in the same flow (for example, a subscription that starts with a first payment). The Payment Request completion delivers a Klarna Network Session Token, and Partners call authorizePaymentAPI to create the initial Payment Transaction.PAY (or the tokenization intent the use case requires)
Pick the token scope that matches the re-authorization model:
  • payment:customer_present: on-demand and one-click authorizations (the customer triggers each authorization).
  • payment:customer_not_present: subscription renewals, scheduled billing.

Step 2: Configure the Web SDK with a tokenization intent

When calling klarna.Payment.presentation(), set intent to the value that matches the tokenization use case. The intent personalizes the Klarna Purchase Journey copy, payment options, and consent screens for the customer.
IntentUse case
SUBSCRIBERecurring authorizations at regular intervals (subscriptions). The customer saves a payment method for future automatic authorizations.
ADD_TO_WALLETWallet linking. Save a payment method to a customer account for later customer-initiated purchases.
PAYOne-time payments. Use this intent when tokenization is combined with an initial Payment Transaction (for example, a subscription that charges its first month immediately).
Pass the intent on payment presentation:
JAVASCRIPT
1 2 3 4 5 6 7
const paymentPresentation = await klarna.Payment.presentation({ currency: "USD", // amount is required when intent is PAY or SUBSCRIBE amount: 999, locale: "en-US", intent: "SUBSCRIBE" });
For the full presentation surface (initialization, rendering, payment-option selection, and button mounting), see Build checkout form with Klarna WebSDK.
Omit amount when the intent is ADD_TO_WALLET. The PAY and SUBSCRIBE intents both require amount.

Step 3: Wire the initiate callback to the tokenization backend

The Klarna payment button calls the initiate callback when the customer clicks it. In a one-time payment flow this callback creates a Payment Request for a charge. For tokenization, it instead asks the Partner's backend to create a Payment Request with request_customer_token and return the resulting paymentRequestUrl to the SDK.
The callback signature is the same as for one-time payments, initiate(klarnaNetworkSessionToken, paymentOptionId), and it must resolve to { paymentRequestUrl }.
JAVASCRIPT
1 2 3 4 5 6 7 8 9 10
async function initiateKlarnaTokenization(klarnaNetworkSessionToken, paymentOptionId) { try { const response = await fetch('/api/start-tokenization', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ klarnaNetworkSessionToken, paymentOptionId }) }); if (!response.ok) throw new Error('Network or server error');
For button mounting, theming, initiationMode choices, and Web SDK event handlers (on("complete"), on("abort"), on("error")), refer to the Web SDK guide. They apply to tokenization unchanged.
The klarnaNetworkSessionToken provided to the callback carries Klarna context from a Boost feature (Express checkout, Sign in with Klarna, on-site messaging). It is optional on the tokenization Payment Request. Klarna will create the Klarna Purchase Journey from scratch when it is absent. Forward it to the backend whenever the SDK provides one so Klarna can personalize the journey.

Step 4: Create the tokenization Payment Request (backend)

The backend handler calls createPaymentRequestAPI with request_customer_token. The body shape depends on whether the customer is also charged.
To save the payment method only:
  • Include request_customer_token with the scopes that matches the re-authorization model and an optional customer_token_reference to link the token to an internal user.
  • Provide currency (no amount, no request_payment_transaction).
  • Provide customer_interaction_config with method: "HANDOVER" and a return_url. This enables the SDK to launch the Klarna Purchase Journey.
  • Provide supplementary_purchase_data describing the intended use case. Use subscriptions[] for recurring re-authorizations or ondemand_service for on-demand re-authorizations.
JSON
1 2 3 4 5 6 7 8 9 10
POST /v2/payment/requests Content-Type: application/json { "currency": "USD", "request_customer_token": { "scopes": ["payment:customer_not_present"], "customer_token_reference": "subscription-user-12345" }, "supplementary_purchase_data": {
Klarna returns the Payment Request in SUBMITTED state with the payment_request_id that the frontend will hand to the SDK:
JSON
1 2 3 4 5 6 7 8 9 10
{ "payment_request_id": "krn:payment:eu1:request:bcb5ca7d-[...]", "state": "SUBMITTED", "currency": "USD", "state_context": { "customer_interaction": { "method": "HANDOVER", "payment_request_id": "krn:payment:eu1:request:bcb5ca7d-[...]", "payment_request_url": "https://pay.klarna.com/l/ZjW2Xipgh0tjbrBGD7hzJM" }
Token scope is critical. It must match how the customer will be re-authorized. See Token scopes for guidance on choosing between payment:customer_present and payment:customer_not_present.

Backend handler example

JAVASCRIPT
1 2 3 4 5 6 7 8 9 10
app.post('/api/start-tokenization', async (req, res) => { try { const { klarnaNetworkSessionToken, paymentOptionId } = req.body; const klarnaResponse = await callKlarnaCreatePaymentRequest({ klarnaNetworkSessionToken, currency: 'USD', requestCustomerToken: { scopes: ['payment:customer_not_present'], customer_token_reference: 'subscription-user-12345'

Configure return URLs

The return_url (web) and app_return_url (mobile) parameters in customer_interaction_config define where Klarna brings the customer back after the Klarna Purchase Journey completes or is stopped. Configure them following the same rules used for one-time payments. See Configure customer-interaction parameters for the details. Tokenization adds no extra requirements.

Step 5: Hand the paymentRequestUrl to the SDK and launch the Klarna Purchase Journey

When the initiate callback resolves with { paymentRequestUrl }, the Klarna Web SDK launches the Klarna Purchase Journey using the configured initiationMode on the payment button. The customer authenticates with Klarna, reviews the consent terms (subscription frequency, free-trial dates, on-demand re-use), and approves tokenization.
  • For DEVICE_BEST, the SDK automatically selects either an on-page or redirect experience based on the device. When the experience is on-page the customer stays on the page; when it is a redirect, Klarna brings the customer back to the configured return_url on completion or stop.
  • For REDIRECT, the customer is redirected to the Klarna Purchase Journey and Klarna brings them back to the configured return_url on completion or stop.
  • For ON_PAGE, the Klarna Purchase Journey is rendered in a popup or in-page modal. The customer never leaves the page.
Use the standard Web SDK event handlers (on("complete"), on("abort"), on("error")) to update the UI, but never finalize tokenization or take a Payment Transaction from a frontend event. Always rely on Klarna webhooks (Step 6) to drive backend state changes.
For the full details on initiationMode, on() events, and Klarna Purchase Journey UX, see Web SDK: Configure initiationMode and Web SDK: Track the Klarna Purchase Journey.

Step 6: Receive the Payment Request completion webhook

When the customer completes the Klarna Purchase Journey, Klarna sends a payment.request.state-change.completed webhook to the Partner's backend. The webhook payload differs by scenario.
The webhook payload contains the issued customer_token directly. No further API call is required. Persist the token, mark the tokenization as successful in local records, and confirm to the customer.
JSON
1 2 3 4 5 6 7 8 9 10
{ "metadata": { "event_type": "payment.request.state-change.completed", "event_id": "d9f9b1a0-5b1a-4b0e-9b0a-9e9b1a0d5b1a", "event_version": "v2", "occurred_at": "2026-04-01T16:55:17Z", "correlation_id": "2d1557e8-17c3-466c-924a-bbc3e91c2a02", "subject_account_id": "krn:partner:global:account:test:HGBY07TR", "recipient_account_id": "krn:partner:global:account:test:LWT2XJSE", "product_instance_id": "krn:partner:product:payment:ad71bc48-8a07-[...]",
The same customer_token can also be retrieved from state_context.klarna_customer.customer_token by calling readPaymentRequestAPI as a fallback.
Subscribe to webhook events through the Webhooks registration guide. Use readPaymentRequestAPI as an optional fallback when the webhook handler missed the event. The same customer_token and klarna_network_session_token are available in state_context.
Webhook delivery is the source of truth. The Web SDK on("complete") callback can update the UI optimistically, but it must not be used to confirm tokenization or take a payment.

Step 7 (combined only): Authorize the initial Payment Transaction

This step applies only to the tokenization combined with authorization scenario. Tokenization itself is already complete after the webhook in Step 6. This step finalizes the initial Payment Transaction using the Klarna Network Session Token issued by Klarna.
Call authorizePaymentAPI with:
  • The Klarna-Network-Session-Token request header set to the Klarna Network Session Token from the webhook.
  • The same currency, amount, and supplementary_purchase_data sent on createPaymentRequest.
  • The same payment_transaction_reference to link the request to the Payment Transaction.
  • The same request_customer_token (scopes and customer_token_reference). This is required so Klarna includes customer_token_response on the result.
SHELL
1 2 3 4 5 6 7 8 9 10
curl https://api-global.test.klarna.com/v2/payment/authorize \ -H 'Authorization: Basic <API key>' \ -H 'Content-Type: application/json' \ -H 'Klarna-Network-Session-Token: krn:network:us1:test:session-token:eyJhbGciOiJFU[...]' \ -d '{ "currency": "USD", "request_payment_transaction": { "amount": 999, "payment_transaction_reference": "subscription-first-payment-001" },
The Klarna Network Session Token is valid for 1 hour. When it expires, or when the payment context (amount, currency, supplementary_purchase_data) does not match the original Payment Request, Klarna returns DECLINED.
The response carries both customer_token_response and payment_transaction_response. Inspect each independently. Even when the Payment Transaction is declined, the customer's tokenization consent may still result in an issued customer_token.
JSON
1 2 3 4 5 6 7 8 9 10
{ "payment_transaction_response": { "result": "APPROVED", "payment_transaction": { "payment_transaction_id": "krn:payment:us1:transaction:5cb3e0e5-a6f0-[...]", "amount": 999, "currency": "USD", "payment_funding": { "type": "GUARANTEED", "state": "FUNDED"
ResultMeaningNext step
APPROVEDKlarna created the Payment Transaction and the customer_token is ready for re-use.Persist payment_transaction.payment_transaction_id and customer_token. Continue with capture/refund through the Payment Transaction operations.
DECLINEDThe Payment Transaction failed. Inspect customer_token_response separately. The customer_token may still be valid.Show the customer an error and offer an alternative payment method. Decide based on customer_token_response.result whether to keep or discard the token.
For deeper details on step_up_config, mismatch handling, and the full authorize contract, see Authorize a customer-initiated payment (server-side).

Step 8: Store the customer token and confirm to the customer

After Step 6 (tokenization-only) or Step 7 (combined), persist the customer_token securely. Treat it as a sensitive credential. It allows authorizations on behalf of the customer until it expires or is revoked.
PracticeDescription
Store securelyPersist customer_token values in encrypted storage; restrict access to the systems that issue authorizations.
Associate with the customerLink each token to the relevant customer record together with the issued scope and the issuance timestamp.
Track scope and intentRecord the token's scopes and the business intent (subscription, wallet, on-demand) so the correct token is used at authorization time.
Honor lifecycle eventsWhen the customer cancels their subscription or removes a saved payment method, stop using the token and mark it inactive.
Do not expose tokensNever return raw customer_token values to a frontend, mobile clients, or third parties. Reference them by an internal identifier instead.
Update the frontend through the SDK's on("complete") callback (or a success page) to confirm to the customer that the payment method is saved.
For the resource-level lifecycle and revocation operations, see Customer Token.
Related articles
Build Payment Presentation with the Klarna Web SDK
Payment Tokenization Overview
Payment Tokenization for Server-side integration
Set up your webhooks
Payment Tokenization