clientId, while partnerAccountId identifies the Partner Account that represents the Store under Stripe.payment_ confirmation_ token and klarna_ network_ session_ token to Stripe and creates the Stripe PaymentIntent. Stripe creates the Payment Transaction.initiate callback. After completion, Shopify forwards the tokens and Stripe creates the Payment Transaction.clientId and allowlist every Shopify domain that loads the SDK.payment. request. editable-shipping-details. updated (synchronous shipping callback)payment. request. editable-shipping-details. updated. failedpayment. request. state-change. completedpayment_ confirmation_ token and klarna_ network_ session_ token to Stripe when creating the PaymentIntent after the Payment Request completes.clientId and the Partner Account for the Store. Resolve partnerAccountId for each Store rather than hardcoding it.<script type="module">
const { KlarnaSDK } = await import(
"https://js.klarna.com/web-sdk/v2/klarna.mjs"
);
const klarna = await KlarnaSDK({
clientId: "[shopify-client-id]",
partnerAccountId: "[stripe-partner-account-id-for-store]",
products: ["PAYMENT"],
locale: "en-US",| Field | Value |
|---|---|
clientId | Shopify's Klarna Web SDK client ID. |
partnerAccountId | The Partner Account that represents the Store under Stripe. |
products | ["PAYMENT"]. |
| Do | Don't |
|---|---|
Load the Web SDK from https://js.klarna.com/web-sdk/v2/klarna.mjs. | Bundle or self-host the Web SDK. |
Keep the SDK in the top-level, first-party browsing context. | Load the Web SDK inside an iframe. |
Disclose Web SDK tracking in Shopify's notices. | Use the Web SDK without disclosing tracking technologies. |
<div id="klarna-button-container"></div>const klarnaPaymentButton = klarna.Payment.button({
id: "klarna-payment-button",
shape: "rect",
theme: "outlined",
intent: "PAY",
initiationMode: "DEVICE_BEST",
initiate: createExpressCheckoutPaymentRequest,
});
klarnaPaymentButton.mount("#klarna-button-container");complete runs on the storefront. Take the customer to Shopify's confirmation page in that handler. Do not wait for Shopify's backend to create the Stripe PaymentIntent. Return false so the SDK does not also follow customerInteractionConfig. returnUrl.initiate after the customer selects the Klarna button. Return Payment Request data with shippingConfig. mode set to "EDITABLE". The SDK creates the Payment Request and starts the Klarna Purchase Journey.async function createExpressCheckoutPaymentRequest() {
const cart = await fetch("/api/shopify/cart").then((response) =>
response.json()
);
return {
currency: cart.currency,
amount: cart.totalAmount,
paymentRequestReference: cart.checkoutReference,
shippingConfig: {payment. request. editable-shipping-details. updated when the customer enters or changes an address or selects a shipping option.| Event | Pattern | Shopify response |
|---|---|---|
payment. | Synchronous callback | HTTP 200 with a JSON body in the same request. |
payment. | Asynchronous webhook | HTTP 200, 201, 202, or 204. No response body is required. |
payment. | Asynchronous webhook | HTTP 200, 201, 202, or 204. No response body is required. |
Klarna-Signature using the signing key identified by Klarna-Signing-Key-ID.metadata envelope and no payload wrapper. Its customer_ action, payment_ request_ id, payment_ request_ reference, and shipping fields are at the root. Use payment_ request_ reference as Shopify's identifier for the checkout.payment_ request_ id. That identifier is the same for every shipping change in one Payment Request. The callback has no per-delivery event identifier and can be repeated after a timeout. Keep rate quoting free of side effects, recompute the quote, and return it.customer_ action: "SELECT_ SHIPPING_ ADDRESS", calculate rates and tax for the collected address. Only postal_ code, city, and country are guaranteed, so treat other address fields as optional.{
"customer_action": "SELECT_SHIPPING_ADDRESS",
"payment_request_id": "krn:payment:eu1:request:552603c0-fe8b-4ab1-aacb-41d55fafbdb4",
"payment_request_reference": "shopify-checkout-12345",
"shipping": {
"address": {
"street_address": "13 Palmer Square W",
"city": "Princeton",
"region": "US-NJ",
"postal_code": "08542",ACCEPTED with the available shipping options, a preselected option, and recalculated totals. Set selected_ shipping_ option_ reference to one of the returned shipping_ option_ reference values so Klarna preselects that option. Price updated_ payment_ request for the preselected option.{
"result": "ACCEPTED",
"shipping_selection": {
"available_shipping_options": [
{
"shipping_option_reference": "ups-ground-5day",
"display_name": "UPS Ground",
"description": "3–5 business days",
"amount": 599,
"shipping_carrier": "UPS",{
"result": "SHIPPING_ADDRESS_REJECTED",
"result_reason": "COUNTRY_NOT_SUPPORTED"
}customer_ action: "SELECT_ SHIPPING_ OPTION", validate the referenced option and return ACCEPTED with the final amount and line items.{
"customer_action": "SELECT_SHIPPING_OPTION",
"payment_request_id": "krn:payment:eu1:request:552603c0-fe8b-4ab1-aacb-41d55fafbdb4",
"payment_request_reference": "shopify-checkout-12345",
"shipping": {
"selected_shipping_option": {
"shipping_option_reference": "ups-2nd-day",
"display_name": "UPS 2nd Day Air",
"description": "2 business days",
"amount": 1299,ACCEPTED with that option preselected and totals priced for it:{
"result": "ACCEPTED",
"shipping_selection": {
"available_shipping_options": [
{
"shipping_option_reference": "ups-ground-5day",
"display_name": "UPS Ground",
"description": "3–5 business days",
"amount": 599,
"shipping_carrier": "UPS",{
"result": "SHIPPING_OPTION_REJECTED",
"result_reason": "SHIPPING_OPTION_NO_LONGER_AVAILABLE",
"shipping_selection": {
"available_shipping_options": [
{
"shipping_option_reference": "store-pickup-princeton",
"display_name": "Collect in store",
"description": "Ready in 2 hours",
"amount": 0,shipping_ option_ reference, keep that price stable. If a rate changes, return it under a new reference.payment_ request_ id. Shopify forwards the last accepted snapshot to Stripe after completion.payment. request. editable-shipping-details. updated. failed when it can't apply Shopify's callback response. This is an asynchronous webhook with the standard metadata and payload structure.{
"metadata": {
"event_type": "payment.request.editable-shipping-details.updated.failed",
"event_id": "d9f9b1a0-5b1a-4b0e-9b0a-9e9b1a0d5b1a",
"event_version": "v2",
"occurred_at": "2026-09-25T12:00:00Z"
},
"payload": {
"payment_request_id": "krn:payment:eu1:request:552603c0-fe8b-4ab1-aacb-41d55fafbdb4",
"payment_request_reference": "shopify-checkout-12345",metadata. event_ id. Log payload. validation_ errors with the Store and payment_ request_ id, and include payload. error_ id when contacting Klarna.payment. request. state-change. completed. Token fields are at the root of payload, not inside state_ context.{
"metadata": {
"event_type": "payment.request.state-change.completed",
"event_id": "eab0c2b1-6c2b-4c1f-8c1b-afac2b1e6c2b",
"event_version": "v2",
"occurred_at": "2026-09-25T12:00:00Z"
},
"payload": {
"payment_request_id": "krn:payment:eu1:request:552603c0-fe8b-4ab1-aacb-41d55fafbdb4",
"payment_request_reference": "shopify-checkout-12345",payment_ token — the confirmation token. Map this to payment_ confirmation_ token when Shopify creates the Stripe PaymentIntent. Klarna API v1 used the same name, payment_ confirmation_ token.klarna_ network_ session_ token — the session token Shopify must send with the Stripe PaymentIntent.payment_ confirmation_ token and klarna_ network_ session_ token with the last shipping snapshot Shopify accepted. The storefront complete handler must not wait for that call.{
"payment_confirmation_token": "[confirmation token from payment_token]",
"klarna_network_session_token": "[opaque Klarna Network Session Token]",
"amount": 48620,
"currency": "USD",
"payment_request_id": "krn:payment:eu1:request:552603c0-fe8b-4ab1-aacb-41d55fafbdb4",
"payment_request_reference": "shopify-checkout-12345",
"shipping": {
"shipping_option_reference": "ups-ground-5day",
"amount": 599ACCEPTED.payment_ confirmation_ token and klarna_ network_ session_ token. Shopify does not currently send these tokens to Stripe. This guide doesn't specify a Stripe API path, method, or payload. The conceptual handoff above shows the information Stripe needs, not the call that carries it.clientId and the correct Stripe Partner Account for the Store.initiate returns shippingConfig. mode: "EDITABLE".SHIPPING_ ADDRESS_ REJECTED.metadata. event_ id.complete handler takes the customer to the confirmation page without waiting for Stripe.payment_ confirmation_ token, klarna_ network_ session_ token, and the last accepted snapshot when creating the Stripe PaymentIntent.| Symptom | Likely cause | Fix |
|---|---|---|
| The customer's second address change is ignored | Callback deliveries are deduplicated on payment_. | Don't deduplicate synchronous shipping callbacks. |
| Klarna reports a signature mismatch | The signature was checked after parsing or reserializing the body. | Verify the signature against the raw request body. |
| Klarna sends the failed webhook | The callback response doesn't match the schema. | Inspect payload. and correlate with error_. |
| Stripe declines or requests confirmation | The Stripe amount or purchase data differs from the accepted snapshot. | Compare the handoff with Shopify's last ACCEPTED callback state. |
| Do | Don't |
|---|---|
Answer every shipping callback with HTTP 200 and a JSON body in the same request. | Acknowledge the shipping callback with an empty response and process it later. |
Verify webhook signatures before processing callback or webhook data. | Act on callback or webhook data before verifying its signature. |
Keep each shipping option reference tied to one stable amount. | Reprice an existing shipping option reference. |
Send payment_confirmation_token and klarna_network_session_token when creating the Stripe PaymentIntent. | Parse or reshape the completed value before forwarding it. |