# Migrating from Vipps Checkout to Kustom Checkout This guide walks you through replacing a Vipps MobilePay Checkout integration with Kustom Checkout. It covers every call and data point you need to change, in the order you should change them. > **Background:** Kustom Checkout is the rebranded Klarna Checkout (KCO). If you previously integrated Klarna Checkout and are now also moving to Kustom-branded endpoints, see the note on base URLs in Step 2 — old `*.klarna.com` API credentials stopped working on **March 31, 2026**. ## At a glance | Dimension | Vipps Checkout | Kustom Checkout | | --- | --- | --- | | **Auth method** | Multiple HTTP headers | HTTP Basic Auth (`username:password`) | | **Base URL (test)** | `https://apitest.vipps.no/checkout/v3` | See Merchant Portal for -regionspecific URL | | **Base URL (prod)** | `https://api.vipps.no/checkout/v3` | `https://api.kustom.co/checkout/v3` | | **Unit created** | Session (`reference`) | Order (`order_id`) | | **Create endpoint** | `POST /session` | `POST /orders` | | **Read status** | `GET /session/{reference}` | `GET /orders/{order_id}` | | **Update** | `PATCH /session/{reference}` | `POST /orders/{order_id}` | | **Abort** | Session expires (1 h TTL) | `POST /orders/{order_id}/abort` | | **Frontend** | Load SDK + init with token | Inject `html_snippet` into DOM | | **User redirect** | `returnUrl` | `merchant_urls.confirmation` | | **Server callback** | `callbackUrl` | `merchant_urls.push` | | **Country / market** | Implied by MSN header | `purchase_country` field | | **Locale** | SDK `language` param | `locale` field in order body | | **Order acknowledgment** | Not required | **Required** — `POST /ordermanagement/v1/orders/{id}/acknowledge` | | **Capture** | Via ePayment API | Via Order Management API | ## Step 1 — Replace credentials and authentication ### Vipps: header-based authentication Vipps requires five or more headers on every request: ```http client_id: client_secret: Ocp-Apim-Subscription-Key: Merchant-Serial-Number: Vipps-System-Name: Vipps-System-Version: 3.1.2 Vipps-System-Plugin-Name: Vipps-System-Plugin-Version: 4.5.6 ``` ### Kustom: HTTP Basic Auth Kustom uses standard HTTP Basic Auth. Your credentials are a `username` and `password` tied to your Kustom Merchant ID. ```bash # Encode once and store as a constant echo -n "username:password" | base64 # pwhcueUff0MmwLShJiBE9JHA== ``` ```http Authorization: Basic pwhcueUff0MmwLShJiBE9JHA== Content-Type: application/json ``` > **Get your credentials** from the [Kustom Merchant Portal](https://portal.kustom.co). Generate separate credentials for test and production — never share them. **What to change in your code:** Remove all Vipps-specific headers from your HTTP client. Add a single `Authorization: Basic ` header. If you set headers centrally in a middleware or HTTP client factory, this is a one-line change. ## Step 2 — Update base URLs Replace the Vipps base URL with the Kustom base URL in your environment config. | Environment | Vipps | Kustom | | --- | --- | --- | | Test (Playground) | `https://apitest.vipps.no/checkout/v3` | `https://api.playground.kustom.co/` | | Production | `https://api.vipps.no/checkout/v3` | `https://api.kustom.co/` | > **If you had a previous Klarna Checkout integration**, the old `*.klarna.com` production URLs are still active until **March 31, 2026**, but the Playground URL must be updated manually. Switch early to avoid disruptions. ## Step 3 — Replace session creation with order creation This is the largest change. The two payloads have different structures but map closely to each other. ### Vipps: `POST /checkout/v3/session` ```json { "merchantInfo": { "callbackUrl": "https://example.com/checkout/callback", "returnUrl": "https://example.com/checkout/result?ref={reference}", "callbackAuthorizationToken": "538dd1d0-9e7f-4732-8134-dfed7fd0b236" }, "transaction": { "amount": { "value": 49900, "currency": "NOK" }, "reference": "order-abc-123", "paymentDescription": "Yellow T-Shirt × 5", "orderSummary": { "orderLines": [ { "name": "Yellow T-Shirt", "id": "SKU-001", "totalAmount": 49900, "totalAmountExcludingTax": 39920, "totalTaxAmount": 9980, "taxRate": 2500, "unitInfo": { "unitPrice": 9980, "quantity": "5", "quantityUnit": "PCS" }, "discount": 0, "productUrl": "https://example.com/products/yellow-tshirt", "isReturn": false, "isShipping": false }, { "name": "Standard delivery", "id": "shipping-01", "totalAmount": 5000, "totalAmountExcludingTax": 5000, "totalTaxAmount": 0, "taxRate": 0, "discount": 0, "isReturn": false, "isShipping": true } ], "orderBottomLine": { "currency": "NOK" } } }, "configuration": { "elements": "PaymentAndContactInfo", "showOrderSummary": true } } ``` **Response:** ```json { "token": "eyJhbGciOiJSUzI1...", "checkoutFrontendUrl": "https://checkout.vipps.no/?token=eyJ...", "pollingUrl": "https://api.vipps.no/checkout/v3/session/order-abc-123" } ``` ### Kustom: `POST /checkout/v3/orders` ```json { "purchase_country": "NO", "purchase_currency": "NOK", "locale": "nb-NO", "order_amount": 54900, "order_tax_amount": 9980, "order_lines": [ { "type": "physical", "reference": "SKU-001", "name": "Yellow T-Shirt", "quantity": 5, "quantity_unit": "pcs", "unit_price": 9980, "tax_rate": 2500, "total_amount": 49900, "total_discount_amount": 0, "total_tax_amount": 9980 }, { "type": "shipping_fee", "reference": "shipping-01", "name": "Standard delivery", "quantity": 1, "quantity_unit": "pcs", "unit_price": 5000, "tax_rate": 0, "total_amount": 5000, "total_discount_amount": 0, "total_tax_amount": 0 } ], "merchant_urls": { "terms": "https://example.com/terms", "checkout": "https://example.com/checkout?kustom_order_id={checkout.order.id}", "confirmation": "https://example.com/confirmation?kustom_order_id={checkout.order.id}", "push": "https://example.com/api/kustom/push?kustom_order_id={checkout.order.id}" } } ``` **Response:** ```json { "order_id": "8cf27b55-53e8-6aba-9fb4-7c692e56ddee", "status": "checkout_incomplete", "purchase_country": "NO", "purchase_currency": "NOK", "locale": "nb-NO", "order_amount": 54900, "order_tax_amount": 9980, "order_lines": [...], "merchant_urls": {...}, "html_snippet": "
...
", "options": { "allow_separate_shipping_address": false, "date_of_birth_mandatory": false, "require_validate_callback_success": false } } ``` > Store the `order_id` — you will need it for all subsequent operations. ### Field mapping reference | Vipps field | Kustom field | Notes | | --- | --- | --- | | `transaction.amount.value` | `order_amount` | Sum of all `order_lines[].total_amount` | | `transaction.amount.currency` | `purchase_currency` | ISO 4217 | | *(implied by MSN)* | `purchase_country` | ISO 3166-1 alpha-2 | | *(SDK param)* | `locale` | BCP 47, e.g. `nb-NO`, `en-GB` | | `transaction.reference` | Not a request field | Kustom generates `order_id` in response | | `transaction.paymentDescription` | Not a direct field | Use `order_lines[].name` for line descriptions | | `merchantInfo.callbackUrl` | `merchant_urls.push` | Push = server-to-server notification | | `merchantInfo.returnUrl` | `merchant_urls.confirmation` | User browser redirect on success | | `merchantInfo.callbackAuthorizationToken` | Not used | Kustom does not send an auth token in push calls | | `orderSummary.orderLines[].id` | `order_lines[].reference` | Your SKU or line identifier | | `orderSummary.orderLines[].name` | `order_lines[].name` | Display name | | `orderSummary.orderLines[].totalAmount` | `order_lines[].total_amount` | Minor units (integers) | | `orderSummary.orderLines[].totalAmountExcludingTax` | *(derived)* | `total_amount - total_tax_amount` | | `orderSummary.orderLines[].totalTaxAmount` | `order_lines[].total_tax_amount` | Minor units | | `orderSummary.orderLines[].taxRate` | `order_lines[].tax_rate` | Basis points (2500 = 25%) | | `orderSummary.orderLines[].unitInfo.unitPrice` | `order_lines[].unit_price` | Minor units | | `orderSummary.orderLines[].unitInfo.quantity` | `order_lines[].quantity` | Kustom is an integer, not a string | | `orderSummary.orderLines[].unitInfo.quantityUnit` | `order_lines[].quantity_unit` | e.g. `pcs` | | `orderSummary.orderLines[].isShipping: true` | `order_lines[].type: "shipping_fee"` | Kustom uses `type` instead of a flag | | `orderSummary.orderLines[].isReturn: true` | Handled via Order Management API | Credit/return flow is separate in Kustom | | `orderSummary.orderLines[].discount` | `order_lines[].total_discount_amount` | Minor units | | `orderSummary.orderLines[].productUrl` | *(no direct field in basic payload)* | Can be set via Order Management API | #### `order_lines[].type` values | Type | Use for | | --- | --- | | `physical` | Tangible goods | | `digital` | Digital products / downloads | | `shipping_fee` | Shipping / delivery charges | | `sales_tax` | Tax line (if itemised separately) | | `discount` | Order-level discount | | `store_credit` | Credit applied to the order | | `gift_card` | Gift card redemption | ## Step 4 — Replace the frontend rendering ### Vipps: load SDK and initialise ```html
``` ### Kustom: inject the `html_snippet` ```html
``` > Kustom manages its own iframe lifecycle via the injected snippet. You do not load a separate SDK. The Kustom widget will redirect the user to `merchant_urls.confirmation` on successful payment. **What to remove:** The `