Skip to content

Migrating from Vipps Recurring

Replace a Vipps MobilePay Recurring API integration with Kustom customer tokens — the sign-up checkout, storing the token, charging it, scheduling and retries you now own, stopping a token and moving existing agreements by April 30, 2027.

The Vipps MobilePay Recurring API (/recurring/v3) and Kustom solve the same problem with a different split of responsibilities. Vipps keeps an agreement with a price and an interval and runs the charges for you. Kustom gives you a customer token — the recurring_token on a completed checkout order — and charges it the moment you ask, with the schedule, the price and the retries living in your system. This guide replaces each Recurring API call with its Kustom equivalent and then moves the agreements you already have, without asking your customers to sign up again.

If you also use Vipps Checkout for one-off purchases, follow Migrating from Vipps Checkout too. This page covers only the recurring part.

DateWhat happens
November 18, 2026Vipps goes live as a recurring payment method in Kustom Checkout. You can create new Vipps tokens and charge them through Kustom.
February 21, 2027Last day to create new Vipps agreements outside Kustom. From February 22, 2027, every new Vipps token is created through Kustom.
March 8, 2027Your existing Vipps agreements become chargeable through Kustom.
April 30, 2027All Vipps recurring charges must run through Kustom from this date.

Your existing agreements stay valid throughout. Step 9 covers how they move.

You can build today. The Kustom recurring API is already live, and you can build and test the whole flow in Playground against the payment methods a recurring order offers now.

Vipps isn’t a recurring payment method in Kustom yet. Until November 18, 2026 a recurring checkout order doesn’t offer Vipps or MobilePay, so no Kustom token is backed by Vipps. Some Vipps-specific details aren’t confirmed yet, such as the payment_method_type a Vipps token reads back with. This page will be updated before Vipps recurring goes live.

You need Kustom credentials for Playground and production from the Kustom Merchant Portal, and recurring enabled on both merchant IDs. Contact Kustom merchant support to turn it on, and ask for Vipps to be included as a recurring payment method once it’s available. Without the entitlement, a create-order call with recurring: true is rejected with 400 and error_code: BAD_VALUE.

You also need a working Kustom Checkout integration — create an order, render the snippet and handle the push — because the sign-up is that flow with one extra field. If you’re coming from Vipps Checkout, the Vipps Checkout migration gets you there.

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

Vipps Recurring APIKustom
AuthAccess token plus Ocp-Apim-*, Merchant-Serial-Number and Vipps-System-* headersHTTP Basic — one Authorization header
Base URL (test)https://apitest.vipps.nohttps://api.playground.kustom.co
Base URL (prod)https://api.vipps.nohttps://api.kustom.co
The subscription isAn agreement (agreementId)A customer token (recurring_token)
CreatePOST /recurring/v3/agreementsPOST /checkout/v3/orders with recurring: true
Customer consentRedirect to vippsConfirmationUrl, approve in the appComplete the Kustom Checkout
First paymentinitialCharge on the agreementThe order lines of the recurring checkout order
ChargePOST /recurring/v3/agreements/{agreementId}/chargesPOST /customer-token/v1/tokens/{recurring_token}/order
When the charge runsOn the due date Vipps schedulesImmediately, when you call
RetriesVipps retries for retryDaysYou retry on your own schedule
Capture modelDIRECT_CAPTURE or RESERVE_CAPTUREauto_capture: true, or capture later in Order Management
StopPATCH the agreement to STOPPEDPATCH /customer-token/v1/tokens/{recurring_token}/status to CANCELLED
IdempotencyIdempotency-Key on every callNone on the token charge. Kustom-Idempotency-Key on Order Management calls
AmountsMinor unitsMinor units

Every Vipps Recurring call carries an access token from POST /accesstoken/get plus a set of merchant headers. Kustom wants one header and no token exchange. Encode your merchant credentials once:

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

Swap the host at the same time: https://api.kustom.co in production and https://api.playground.kustom.co in Playground — see Authentication and environments. Credentials are per environment, so generate a pair for each.

2. Replace agreement creation with a recurring checkout order

Section titled “2. Replace agreement creation with a recurring checkout order”

Switch your sign-up flow before February 21, 2027, the last day you can create agreements with POST /recurring/v3/agreements.

On Vipps you create a draft agreement and redirect the customer to vippsConfirmationUrl:

{
"pricing": { "type": "LEGACY", "amount": 29900, "currency": "NOK" },
"interval": { "unit": "MONTH", "count": 1 },
"initialCharge": {
"amount": 29900,
"description": "First month",
"transactionType": "DIRECT_CAPTURE",
"orderId": "sub-1001-0"
},
"merchantRedirectUrl": "https://example.com/subscription/result",
"merchantAgreementUrl": "https://example.com/my-subscriptions",
"productName": "Coffee subscription",
"externalId": "sub-1001"
}

The agreement stays PENDING until the customer approves it in the app, then turns ACTIVE.

On Kustom the sign-up is an ordinary checkout order with recurring: true, and the customer approves future charges by completing it:

POST /checkout/v3/orders
Authorization: Basic <token>
Content-Type: application/json
{
"purchase_country": "NO",
"purchase_currency": "NOK",
"locale": "nb-NO",
"order_amount": 29900,
"order_tax_amount": 5980,
"recurring": true,
"merchant_reference1": "sub-1001-0",
"merchant_reference2": "sub-1001",
"order_lines": [
{
"type": "physical",
"reference": "COFFEE-1KG",
"name": "Coffee subscription, first month",
"quantity": 1,
"quantity_unit": "pcs",
"unit_price": 29900,
"tax_rate": 2500,
"total_amount": 29900,
"total_discount_amount": 0,
"total_tax_amount": 5980
}
],
"options": {
"subscription_buy_button": true
},
"merchant_urls": {
"terms": "https://example.com/terms",
"checkout": "https://example.com/checkout?kustom_order_id={checkout.order.id}",
"confirmation": "https://example.com/subscription/result?kustom_order_id={checkout.order.id}",
"push": "https://example.com/api/kustom/push?kustom_order_id={checkout.order.id}"
}
}

Render the html_snippet from the response exactly as for any other order. What changes:

  • recurring: true makes completing the order create a token. It also removes the financing payment methods, so the customer sees a smaller set of options than on an ordinary order.
  • options.subscription_buy_button puts a subscribe label on the buy button — Subscribe and pay rather than Pay order in English.
  • The consent text is Kustom’s. The checkout tells the customer they’re setting up recurring payments with your store, by name. You don’t supply that copy.
  • There is no price or interval on the token. Your system decides what to charge and when. See Create a customer token.

Sign-up without a first payment. Vipps lets you create an agreement without initialCharge. That a recurring checkout order can carry order_amount: 0 isn’t confirmed — check with Kustom merchant support before you design a free trial around it.

Vipps fieldKustom fieldNotes
pricing.amount(none on the token)You send the amount on every charge
pricing.currencypurchase_currencyISO 4217
pricing.type: VARIABLE, suggestedMaxAmount(none)There’s no customer-chosen maximum. Tell the customer about amount changes yourself
interval.unit, interval.count(none)The schedule lives in your system
productNameorder_lines[].name
productDescription(none)The checkout shows its own consent text
initialCharge.amountorder_amount and order_linesOf the checkout order
initialCharge.descriptionorder_lines[].name
initialCharge.transactionTypeoptions.auto_captureOr capture later in Order Management
initialCharge.orderIdmerchant_reference1Your reference for the first payment
externalIdmerchant_reference2Your subscription id
merchantRedirectUrlmerchant_urls.confirmationThe customer’s browser lands here
merchantAgreementUrl(none)Keep your own “My subscriptions” page and link it from your emails
phoneNumberbilling_address.phoneOptional prefill
scope(none)The completed order already carries the billing address, email and phone
campaignA discount order lineApply the discounted price on the charge it belongs to
countryCodepurchase_countryNO, DK or FI
isApp, skipLandingPage(none)The checkout handles the app switch

3. Store the token when the customer completes

Section titled “3. Store the token when the customer completes”

On Vipps you poll the agreement or wait for its webhook until it’s ACTIVE, then store the agreementId. On Kustom the push for the checkout order is the signal. Your push handler:

  1. Reads kustom_order_id from the query string.
  2. Reads the order with GET /checkout/v3/orders/{order_id} and takes its recurring_token.
  3. Stores the token against the customer’s subscription.
  4. Acknowledges the order with POST /ordermanagement/v1/orders/{order_id}/acknowledge.
  5. Returns 200.
{
"order_id": "8cf27b55-53e8-6aba-9fb4-7c692e56ddee",
"status": "checkout_complete",
"recurring": true,
"recurring_token": "8b7c6e1a-2f3d-4a5b-9c8d-7e6f5a4b3c2d",
"order_amount": 29900
}

Treat the token as a credential. It can charge a real person, so store it the way you’d store a card token: encrypted at rest, never in a log or a URL. It can only be charged under the merchant ID that created it.

On Vipps you queue a charge at least two days before it’s due in production, and Vipps attempts it on the due date and for retryDays after:

{
"amount": 29900,
"transactionType": "DIRECT_CAPTURE",
"description": "Coffee subscription, March",
"due": "2027-03-15",
"retryDays": 5,
"orderId": "sub-1001-3"
}

On Kustom you charge the token the moment the payment is due. Each call is one synchronous payment — no queue, no due date, no pending state:

POST /customer-token/v1/tokens/8b7c6e1a-2f3d-4a5b-9c8d-7e6f5a4b3c2d/order
Authorization: Basic <token>
Content-Type: application/json
{
"purchase_currency": "NOK",
"order_amount": 29900,
"order_tax_amount": 5980,
"auto_capture": true,
"merchant_reference1": "sub-1001-3",
"merchant_reference2": "sub-1001",
"order_lines": [
{
"type": "physical",
"reference": "COFFEE-1KG",
"name": "Coffee subscription, March",
"quantity": 1,
"quantity_unit": "pcs",
"unit_price": 29900,
"tax_rate": 2500,
"total_amount": 29900,
"total_discount_amount": 0,
"total_tax_amount": 5980
}
]
}

A success is small:

{
"order_id": "a89ec121-1276-419d-882a-c343d58fd1bc",
"fraud_status": "ACCEPTED",
"authorized_payment_method": {
"type": "card"
}
}
  • A 200 means the charge went through. The order is authorized at once and, with auto_capture: true, captured too. fraud_status is always ACCEPTED, so there’s nothing to branch on.
  • auto_capture defaults to false, which leaves the charge authorized but not collected — the Kustom equivalent of RESERVE_CAPTURE.
  • Store the order_id. It’s the only handle you get for capturing, cancelling or refunding the charge.
  • A failed charge is an error response, not a pending order. See step 6.

Charge a customer token has the call in full.

Vipps fieldKustom fieldNotes
agreementId (path)recurring_token (path)
amountorder_amountMust equal the sum of order_lines[].total_amount
(currency on the agreement)purchase_currencyRequired on every charge
descriptionorder_lines[].nameVisible to the customer
DIRECT_CAPTUREauto_capture: true
RESERVE_CAPTUREauto_capture: falseThe default. Capture in Order Management
due(none)Call on the day you want to charge
retryDays(none)See step 6
orderIdmerchant_reference1One unique value per billing period
externalIdmerchant_reference2
type: UNSCHEDULED(no difference)Every token charge is on demand
Idempotency-Key header(none)The token charge takes no idempotency key. See step 6 before you retry

A Vipps charge lives under its agreement. A Kustom token charge is an ordinary order, so you manage it with Order Management by the order_id the charge returned — see the endpoint mapping below and Capture and ship.

Capturing a charge made with auto_capture: false:

POST /ordermanagement/v1/orders/a89ec121-1276-419d-882a-c343d58fd1bc/captures
Authorization: Basic <token>
Content-Type: application/json
Kustom-Idempotency-Key: sub-1001-3-capture
{
"captured_amount": 29900,
"description": "Coffee subscription, March"
}

The Order Management API reference lists this header as Klarna-Idempotency-Key. Both names work; see Idempotency and retries.

Vipps charge statusKustom
PENDING, DUE, PROCESSINGNo equivalent. The charge doesn’t exist until you call
RESERVEDOrder status AUTHORIZED
CHARGEDOrder status CAPTURED
PARTIALLY_CAPTUREDOrder status PART_CAPTURED
FAILEDThe charge call returned an error — see step 6
CANCELLEDOrder status CANCELLED
REFUNDED or PARTIALLY_REFUNDEDThe status stays CAPTURED and refunded_amount grows

The order lifecycle has every state.

Vipps runs the charge on the due date and retries it for you. Kustom does neither, so your billing job must decide which subscriptions are due today, charge each token once per billing period and record that it did, then handle the result.

Only one failure carries an error code that describes the charge, and most come back with no body at all, so branch on the HTTP status first:

StatusBodyWhat to do
403PAYMENT_METHOD_FAILEDThe method didn’t work this time. Retry with backoff, keep the correlation_id, and if it keeps failing ask the customer to update their payment method
400BAD_VALUEA field is invalid (named in error_messages), or the token belongs to another merchant ID. Fix the request; don’t retry it unchanged
400emptyThe token is cancelled, or recurring isn’t enabled on your merchant account. Retrying won’t help
404emptyNo such token. Check the value and the environment
500emptyThe outcome is unknown. Check whether the order was created before you charge again

With no idempotency key on the charge, that last row is the one that double-charges customers: if a charge times out or returns 500, find out whether the order exists before you send it again. The error catalogue has the coded errors in full.

Updating the payment method means a new token, not a repair of the old one. Send the customer through a new recurring checkout order (step 2), store the new recurring_token, then cancel the old one (step 7).

Vipps agreements read back as PENDING, ACTIVE, STOPPED or EXPIRED, and a PATCH stops them or changes their price or interval. A Kustom token has no price or interval to change, so the only things you do to it are read it and cancel it.

GET /customer-token/v1/tokens/{recurring_token}
Authorization: Basic <token>
{
"payment_method_type": "CARD",
"status": "ACTIVE",
"card": {
"brand": "visa",
"expiry_date": "11/2031",
"masked_number": "************4242"
}
}

A token reads back ACTIVE while it can be charged and CANCELLED once it’s been cancelled — it doesn’t expire from disuse. A card token carries a card object and a direct-debit token a direct_debit one. An unknown token returns 404 with an empty body. Check the status before a billing run rather than finding a dead token in a failed batch.

To cancel:

PATCH /customer-token/v1/tokens/{recurring_token}/status
Authorization: Basic <token>
Content-Type: application/json
{
"status": "CANCELLED"
}

Kustom answers 202 Accepted with an empty body. CANCELLED is final — a cancelled token can’t be made active again, and cancelling it a second time returns 400. Orders already created with the token can still be captured and refunded.

Vipps agreement statusKustom token
PENDINGNo token yet. The checkout order is still checkout_incomplete
ACTIVEACTIVE
STOPPEDCANCELLED
EXPIREDNo equivalent. A token stays ACTIVE until you cancel it

Changing the price or interval is a change to your own records: send the new amount on the next charge and tell the customer as your terms require. Pausing is skipping charges in your billing job — there’s no pause status.

Vipps sends webhooks when agreements and charges change state. Kustom has none for tokens, because nothing happens to a token without a call from you.

Vipps event you listened forWhat to do in Kustom
Agreement activatedHandle the push for the recurring checkout order (step 3)
Agreement stopped by the customerCustomers manage subscriptions with you. Cancel the token when they cancel on your “My subscriptions” page
Charge captured, failed or cancelledRead the result of the charge call itself (steps 4 and 6). A token charge is never left pending

From March 8, 2027 your existing active Vipps agreements can be charged through Kustom. You don’t create these tokens yourself: Kustom and Vipps transfer them and give you a mapping from each Vipps agreementId to a Kustom recurring_token.

How you receive the mapping is still being confirmed with Vipps and Kustom’s payment partner. Kustom will contact each affected merchant with the delivery details and the format before March 8, 2027, so make sure your Kustom account has an up-to-date technical contact.

Finish steps 1 to 8 before the mapping arrives. Then cut over:

  1. Import the mapping. Store each recurring_token on its subscription and keep the Vipps agreementId beside it for reconciliation and support cases.
  2. Check the tokens. Read each one and confirm status: ACTIVE. Spread the calls out to stay inside the rate limits.
  3. Stop scheduling new Vipps charges. Don’t create charges with a due date after your cut-over, and cancel any already queued for later.
  4. Switch each subscription at its next billing date, charging it as in step 4. Charge each billing period through exactly one provider, and keep a per-subscription record of which one charged each period until April 30, 2027.
  5. Stop the Vipps agreement only after the first Kustom charge succeeds. Stop it earlier and you have no fallback for that customer.
  6. Finish before April 30, 2027. From that date all Vipps recurring charges must run through Kustom.
OperationVipps Recurring APIKustom
AuthenticatePOST /accesstoken/getHTTP Basic on every call
Create subscriptionPOST /recurring/v3/agreementsPOST /checkout/v3/orders with recurring: true
Read subscriptionGET /recurring/v3/agreements/{agreementId}GET /customer-token/v1/tokens/{recurring_token}
Stop subscriptionPATCH /recurring/v3/agreements/{agreementId}PATCH /customer-token/v1/tokens/{recurring_token}/status
ChargePOST /recurring/v3/agreements/{agreementId}/chargesPOST /customer-token/v1/tokens/{recurring_token}/order
Read chargeGET /recurring/v3/agreements/{agreementId}/charges/{chargeId}GET /ordermanagement/v1/orders/{order_id}
Capture chargePOST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/capturePOST /ordermanagement/v1/orders/{order_id}/captures
Cancel chargeDELETE /recurring/v3/agreements/{agreementId}/charges/{chargeId}POST /ordermanagement/v1/orders/{order_id}/cancel
Refund chargePOST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/refundPOST /ordermanagement/v1/orders/{order_id}/refunds

Against Playground, in this order:

  1. Create an order with recurring: true and complete it with test data. The checkout offers no financing methods — that’s the flag taking effect.
  2. Your push handler reads a recurring_token off the order, stores it and acknowledges the order.
  3. Charge the token and get back an order_id with fraud_status: ACCEPTED. The order appears in Order Management, captured if you sent auto_capture: true.
  4. Charge a deliberately mangled token id and confirm your billing job stops on the empty 404 instead of retrying.
  5. Cancel a token, read it back as CANCELLED, charge it and confirm the empty 400 stops your retries.

Customers were charged early. A Vipps charge is queued for its due date; a Kustom charge is taken when you call. A job that created Vipps charges days in advance has to move to the billing date itself.

Failed charges are never retried. Kustom doesn’t retry. Build retries on PAYMENT_METHOD_FAILED into your billing job before you move any volume.

The charge returns 400 BAD_VALUE. Most often the amounts don’t add up: order_amount must equal the sum of order_lines[].total_amount, and order_tax_amount the sum of total_tax_amount. A single amount isn’t enough. It’s also what a token from another merchant ID gets — if you run several MIDs, store which one each token belongs to.

The charge is rejected for its currency. Vipps stores the currency on the agreement. Kustom needs purchase_currency on every charge.

Every charge returns an empty 400. Recurring isn’t enabled on that merchant ID. Ask Kustom merchant support to enable it on both your Playground and production MIDs.

A customer was charged twice during cut-over. A billing period went through both providers, or a charge that returned 500 was resent without checking. Record per subscription which provider charged each period.

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