Skip to content

Migrating from Vipps Checkout

Replace a Vipps MobilePay Checkout integration with Kustom Checkout — auth, base URLs, the session-to-order payload mapping, the frontend swap, the push handler and the acknowledgment Vipps never required.

Vipps Checkout and Kustom Checkout do the same job with different vocabulary. Vipps has a session you create and then render with an SDK. Kustom has an order you create and then render from an HTML snippet. This guide walks the swap in the order you should make it — credentials, URLs, payload, frontend, callbacks — and flags the two places where Kustom needs something Vipps didn’t: the order acknowledgment, and matching totals.

You need Kustom credentials for both environments from the Kustom Merchant Portal, and a place to put the base URLs that isn’t scattered through your code.

Here is the whole migration in one table. Everything below is detail on one of these rows.

Vipps CheckoutKustom Checkout
AuthFive or more custom headersHTTP Basic — one Authorization header
Base URL (test)https://apitest.vipps.no/checkout/v3https://api.playground.kustom.co
Base URL (prod)https://api.vipps.no/checkout/v3https://api.kustom.co
What you createA session, keyed by your referenceAn order, keyed by Kustom’s order_id
FrontendLoad the SDK, init with a tokenInject the html_snippet into the DOM
Customer redirectreturnUrlmerchant_urls.confirmation
Server callbackcallbackUrl, a POST with the payloadmerchant_urls.push, a POST with the id in the query string
MarketImplied by the MSN headerpurchase_country on the order
LocaleSDK language parameterlocale on the order, full BCP 47
AcknowledgmentNot a thingRequired. Funds aren’t released without it
CaptureePayment APIOrder Management API

Vipps wants client_id, client_secret, Ocp-Apim-Subscription-Key, Merchant-Serial-Number and a family of Vipps-System-* headers on every request. Kustom wants one header. Encode your merchant credentials once:

echo -n "username:password" | base64
Authorization: Basic <the base64 above>
Content-Type: application/json

If your HTTP client sets headers in one place — a middleware, a client factory — this is a one-line change. Delete every Vipps header while you’re in there. Leaving them costs nothing but confuses the next reader.

Swap the host in your environment config. Production is https://api.kustom.co, and Playground is https://api.playground.kustom.co — see Authentication and environments.

3. Turn the session payload into an order payload

Section titled “3. Turn the session payload into an order payload”

This is the bulk of the work. POST /checkout/v3/session becomes POST /checkout/v3/orders, and the body is restructured rather than renamed. Two rules account for most of the failures:

  • order_amount must equal the sum of order_lines[].total_amount. Kustom validates it and returns 400 when it doesn’t. Vipps didn’t check this for you.
  • Amounts are minor-unit integers and tax_rate is basis points. 2500 is 25%.

The full mapping is below. Kustom also needs merchant_urls, which has no Vipps equivalent as a block: terms, checkout, confirmation and push are required, and confirmation and push must be HTTPS — in Playground as well as production. See Merchant URLs.

Vipps gives you a token and you initialize its SDK against it. Kustom gives you an html_snippet in the create-order response — a <div> plus a <script> — and you put it on your page. Server-side rendering is the simple path:

<div id="checkout">{{ kustom_order.html_snippet }}</div>

Client-side, innerHTML won’t execute the script, so you have to recreate the script tags. Render Checkout snippet has the loop, and the rules: keep the script tags exactly as received, and never put the snippet inside an iframe of your own.

5. Rewrite the callback handler, and add the acknowledgment

Section titled “5. Rewrite the callback handler, and add the acknowledgment”

The two callbacks aren’t the same shape, and this is where migrations most often break.

Vipps POSTs the terminal session state to callbackUrl, with your callbackAuthorizationToken in the Authorization header, and you can act on the body.

Kustom POSTs to your push URL, with no authorization header and no body you should act on. It’s a trigger, not a payload:

POST https://example.com/api/kustom/push?kustom_order_id=8cf27b55-53e8-6aba-9fb4-7c692e56ddee

Your handler must:

  1. Take the order id from the query string.
  2. Fetch the order from Order Management.
  3. Create the order in your own system.
  4. Acknowledge it. Kustom holds the payment until you do.
  5. Return 200.
GET /ordermanagement/v1/orders/{order_id}
Authorization: Basic <token>
POST /ordermanagement/v1/orders/{order_id}/acknowledge
Authorization: Basic <token>
Kustom-Idempotency-Key: <your internal order reference>

Send Kustom-Idempotency-Key on the acknowledgment. The API reference lists it under its older name, Klarna-Idempotency-Key, and both work. Put your own order reference in it: it makes the call idempotent and is stored as an alternate identifier on the Kustom order, so you can look the order up by it later.

There’s no equivalent of callbackAuthorizationToken. To satisfy yourself a push is genuine, look the order id up in your own database — if you have a pending order for it, it’s real.

Reading status becomes GET /checkout/v3/orders/{order_id}, and the field to check is status. Updating becomes POST to the same URL — not PATCH — with the fields you want to change. Omitted fields keep their values. You can only update while status is checkout_incomplete. Once the customer has begun paying, the update is rejected.

Where Vipps had you lock the SDK before a PATCH, Kustom has you suspend and resume the iframe around the update. Same idea, different call — see Render Checkout snippet.

Aborting is explicit in Kustom: POST /checkout/v3/orders/{order_id}/abort, rather than waiting out a one-hour session TTL. A Kustom order is valid for 48 hours.

7. Move capture and post-payment operations

Section titled “7. Move capture and post-payment operations”

Everything after authorization moves from the ePayment API to Order Management: captures, cancels and refunds all hang off /ordermanagement/v1/orders/{order_id}. See the endpoint mapping below and Capture an order.

Field mapping from Vipps session to Kustom order

Section titled “Field mapping from Vipps session to Kustom order”
Vipps fieldKustom fieldNotes
transaction.amount.valueorder_amountMust equal the sum of the line totals
transaction.amount.currencypurchase_currencyISO 4217
(implied by the MSN header)purchase_countryISO 3166-1 alpha-2
(SDK language parameter)localelanguage-COUNTRY, e.g. nb-NO
transaction.reference(not a request field)Kustom generates order_id in the response
transaction.paymentDescription(no direct field)Use order_lines[].name
merchantInfo.callbackUrlmerchant_urls.pushServer-to-server, and a POST
merchantInfo.returnUrlmerchant_urls.confirmationThe customer’s browser lands here
merchantInfo.callbackAuthorizationToken(no equivalent)Verify by looking up the order id instead
orderSummary.orderLines[].idorder_lines[].referenceYour SKU
orderSummary.orderLines[].nameorder_lines[].nameDisplay name
orderSummary.orderLines[].totalAmountorder_lines[].total_amountMinor units
orderSummary.orderLines[].totalAmountExcludingTax(derived)total_amount minus total_tax_amount
orderSummary.orderLines[].totalTaxAmountorder_lines[].total_tax_amountMinor units
orderSummary.orderLines[].taxRateorder_lines[].tax_rateBasis points
orderSummary.orderLines[].unitInfo.unitPriceorder_lines[].unit_priceMinor units
orderSummary.orderLines[].unitInfo.quantityorder_lines[].quantityInteger in Kustom, string in Vipps
orderSummary.orderLines[].unitInfo.quantityUnitorder_lines[].quantity_unitpcs, and so on
orderSummary.orderLines[].isShipping: trueorder_lines[].type: "shipping_fee"A type, not a flag
orderSummary.orderLines[].isReturn: true(Order Management)Returns are a separate flow
orderSummary.orderLines[].discountorder_lines[].total_discount_amountMinor units
orderSummary.orderLines[].productUrlorder_lines[].product_urlMax 1024 characters

Kustom’s order_lines[].type values are physical, digital, shipping_fee, sales_tax, discount, store_credit, gift_card and surcharge.

OperationVippsKustom
CreatePOST /checkout/v3/sessionPOST /checkout/v3/orders
Read statusGET /checkout/v3/session/{ref}GET /checkout/v3/orders/{id}
UpdatePATCH /checkout/v3/session/{ref}POST /checkout/v3/orders/{id}
Abort(session expires)POST /checkout/v3/orders/{id}/abort
Acknowledge(not required)POST /ordermanagement/v1/orders/{id}/acknowledge
CapturePOST /epayment/v1/payments/{ref}/capturePOST /ordermanagement/v1/orders/{id}/captures
CancelPUT /epayment/v1/payments/{ref}/cancelPOST /ordermanagement/v1/orders/{id}/cancel
RefundPOST /epayment/v1/payments/{ref}/refundPOST /ordermanagement/v1/orders/{id}/refunds

Against Playground, in this order:

  1. Create an order and get back an order_id and an html_snippet. A 400 here is almost always mismatched totals.
  2. Render the snippet and pay with Playground test data.
  3. Your push endpoint is hit with the order id in the query string.
  4. Your handler fetches the order, acknowledges it and returns 200.
  5. Read the order and confirm status is checkout_complete.
  6. Capture through Order Management and confirm the captured amount.

Order creation returns 400. order_amount doesn’t equal the sum of order_lines[].total_amount, or a quantity is a string. Both are Vipps habits.

The payment never settles. You’re not acknowledging. Kustom holds the funds until the acknowledgment arrives, and eventually times out.

Your push endpoint returns 403. A WAF is blocking it. Kustom’s push sends APIs-Kustom as its User-Agent, which doesn’t look like a browser, so allow that agent or that path.

A shipping line prices wrongly. It’s typed physical. Vipps used an isShipping flag. Kustom uses type: "shipping_fee".

Updates rejected. The order is no longer checkout_incomplete. Check status first.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.