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.
Migration timeline
Section titled “Migration timeline”| Date | What happens |
|---|---|
| November 18, 2026 | Vipps goes live as a recurring payment method in Kustom Checkout. You can create new Vipps tokens and charge them through Kustom. |
| February 21, 2027 | Last day to create new Vipps agreements outside Kustom. From February 22, 2027, every new Vipps token is created through Kustom. |
| March 8, 2027 | Your existing Vipps agreements become chargeable through Kustom. |
| April 30, 2027 | All 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.
Before you start
Section titled “Before you start”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 API | Kustom | |
|---|---|---|
| Auth | Access token plus Ocp-Apim-*, Merchant-Serial-Number and Vipps-System-* headers | HTTP Basic — one Authorization header |
| Base URL (test) | https://apitest.vipps.no | https://api.playground.kustom.co |
| Base URL (prod) | https://api.vipps.no | https://api.kustom.co |
| The subscription is | An agreement (agreementId) | A customer token (recurring_token) |
| Create | POST /recurring/v3/agreements | POST /checkout/v3/orders with recurring: true |
| Customer consent | Redirect to vippsConfirmationUrl, approve in the app | Complete the Kustom Checkout |
| First payment | initialCharge on the agreement | The order lines of the recurring checkout order |
| Charge | POST /recurring/v3/agreements/{agreementId}/charges | POST /customer-token/v1/tokens/{recurring_token}/order |
| When the charge runs | On the due date Vipps schedules | Immediately, when you call |
| Retries | Vipps retries for retryDays | You retry on your own schedule |
| Capture model | DIRECT_CAPTURE or RESERVE_CAPTURE | auto_capture: true, or capture later in Order Management |
| Stop | PATCH the agreement to STOPPED | PATCH /customer-token/v1/tokens/{recurring_token}/status to CANCELLED |
| Idempotency | Idempotency-Key on every call | None on the token charge. Kustom-Idempotency-Key on Order Management calls |
| Amounts | Minor units | Minor units |
Step by step
Section titled “Step by step”1. Replace credentials and base URLs
Section titled “1. Replace credentials and base URLs”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" | base64Authorization: Basic <the base64 above>Content-Type: application/jsonSwap 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/ordersAuthorization: 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: truemakes 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_buttonputs 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 field | Kustom field | Notes |
|---|---|---|
pricing.amount | (none on the token) | You send the amount on every charge |
pricing.currency | purchase_currency | ISO 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 |
productName | order_lines[].name | |
productDescription | (none) | The checkout shows its own consent text |
initialCharge.amount | order_amount and order_lines | Of the checkout order |
initialCharge.description | order_lines[].name | |
initialCharge.transactionType | options.auto_capture | Or capture later in Order Management |
initialCharge.orderId | merchant_reference1 | Your reference for the first payment |
externalId | merchant_reference2 | Your subscription id |
merchantRedirectUrl | merchant_urls.confirmation | The customer’s browser lands here |
merchantAgreementUrl | (none) | Keep your own “My subscriptions” page and link it from your emails |
phoneNumber | billing_address.phone | Optional prefill |
scope | (none) | The completed order already carries the billing address, email and phone |
campaign | A discount order line | Apply the discounted price on the charge it belongs to |
countryCode | purchase_country | NO, 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:
- Reads
kustom_order_idfrom the query string. - Reads the order with
GET /checkout/v3/orders/{order_id}and takes itsrecurring_token. - Stores the token against the customer’s subscription.
- Acknowledges the order with
POST /ordermanagement/v1/orders/{order_id}/acknowledge. - 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.
4. Replace charge creation
Section titled “4. Replace charge creation”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/orderAuthorization: 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
200means the charge went through. The order is authorized at once and, withauto_capture: true, captured too.fraud_statusis alwaysACCEPTED, so there’s nothing to branch on. auto_capturedefaults tofalse, which leaves the charge authorized but not collected — the Kustom equivalent ofRESERVE_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 field | Kustom field | Notes |
|---|---|---|
agreementId (path) | recurring_token (path) | |
amount | order_amount | Must equal the sum of order_lines[].total_amount |
| (currency on the agreement) | purchase_currency | Required on every charge |
description | order_lines[].name | Visible to the customer |
DIRECT_CAPTURE | auto_capture: true | |
RESERVE_CAPTURE | auto_capture: false | The default. Capture in Order Management |
due | (none) | Call on the day you want to charge |
retryDays | (none) | See step 6 |
orderId | merchant_reference1 | One unique value per billing period |
externalId | merchant_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 |
5. Manage the charge in Order Management
Section titled “5. Manage the charge in Order Management”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/capturesAuthorization: Basic <token>Content-Type: application/jsonKustom-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 status | Kustom |
|---|---|
PENDING, DUE, PROCESSING | No equivalent. The charge doesn’t exist until you call |
RESERVED | Order status AUTHORIZED |
CHARGED | Order status CAPTURED |
PARTIALLY_CAPTURED | Order status PART_CAPTURED |
FAILED | The charge call returned an error — see step 6 |
CANCELLED | Order status CANCELLED |
REFUNDED or PARTIALLY_REFUNDED | The status stays CAPTURED and refunded_amount grows |
The order lifecycle has every state.
6. Take over scheduling and retries
Section titled “6. Take over scheduling and retries”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:
| Status | Body | What to do |
|---|---|---|
403 | PAYMENT_METHOD_FAILED | The 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 |
400 | BAD_VALUE | A field is invalid (named in error_messages), or the token belongs to another merchant ID. Fix the request; don’t retry it unchanged |
400 | empty | The token is cancelled, or recurring isn’t enabled on your merchant account. Retrying won’t help |
404 | empty | No such token. Check the value and the environment |
500 | empty | The 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).
7. Read and stop a token
Section titled “7. Read and stop a token”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}/statusAuthorization: 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 status | Kustom token |
|---|---|
PENDING | No token yet. The checkout order is still checkout_incomplete |
ACTIVE | ACTIVE |
STOPPED | CANCELLED |
EXPIRED | No 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.
8. Replace agreement and charge webhooks
Section titled “8. Replace agreement and charge webhooks”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 for | What to do in Kustom |
|---|---|
| Agreement activated | Handle the push for the recurring checkout order (step 3) |
| Agreement stopped by the customer | Customers manage subscriptions with you. Cancel the token when they cancel on your “My subscriptions” page |
| Charge captured, failed or cancelled | Read the result of the charge call itself (steps 4 and 6). A token charge is never left pending |
9. Move your existing agreements
Section titled “9. Move your existing agreements”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:
- Import the mapping. Store each
recurring_tokenon its subscription and keep the VippsagreementIdbeside it for reconciliation and support cases. - Check the tokens. Read each one and confirm
status: ACTIVE. Spread the calls out to stay inside the rate limits. - Stop scheduling new Vipps charges. Don’t create charges with a
duedate after your cut-over, and cancel any already queued for later. - 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.
- Stop the Vipps agreement only after the first Kustom charge succeeds. Stop it earlier and you have no fallback for that customer.
- Finish before April 30, 2027. From that date all Vipps recurring charges must run through Kustom.
Endpoint mapping
Section titled “Endpoint mapping”| Operation | Vipps Recurring API | Kustom |
|---|---|---|
| Authenticate | POST /accesstoken/get | HTTP Basic on every call |
| Create subscription | POST /recurring/v3/agreements | POST /checkout/v3/orders with recurring: true |
| Read subscription | GET /recurring/v3/agreements/{agreementId} | GET /customer-token/v1/tokens/{recurring_token} |
| Stop subscription | PATCH /recurring/v3/agreements/{agreementId} | PATCH /customer-token/v1/tokens/{recurring_token}/status |
| Charge | POST /recurring/v3/agreements/{agreementId}/charges | POST /customer-token/v1/tokens/{recurring_token}/order |
| Read charge | GET /recurring/v3/agreements/{agreementId}/charges/{chargeId} | GET /ordermanagement/v1/orders/{order_id} |
| Capture charge | POST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture | POST /ordermanagement/v1/orders/{order_id}/captures |
| Cancel charge | DELETE /recurring/v3/agreements/{agreementId}/charges/{chargeId} | POST /ordermanagement/v1/orders/{order_id}/cancel |
| Refund charge | POST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/refund | POST /ordermanagement/v1/orders/{order_id}/refunds |
Check that it works
Section titled “Check that it works”Against Playground, in this order:
- Create an order with
recurring: trueand complete it with test data. The checkout offers no financing methods — that’s the flag taking effect. - Your push handler reads a
recurring_tokenoff the order, stores it and acknowledges the order. - Charge the token and get back an
order_idwithfraud_status: ACCEPTED. The order appears in Order Management, captured if you sentauto_capture: true. - Charge a deliberately mangled token id and confirm your billing job stops on the empty
404instead of retrying. - Cancel a token, read it back as
CANCELLED, charge it and confirm the empty400stops your retries.
Troubleshooting
Section titled “Troubleshooting”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.
Related
Section titled “Related”- Charge a customer token — the charge call and its failures in full
- Create a customer token — the sign-up order
- Capture an order — collecting charges made without
auto_capture - Charge a customer later — how the token and the charge fit together
- Customer Token API reference — every token call
- Migrating from Vipps Checkout — the one-off purchase half of a Vipps integration
- Vipps MobilePay Recurring API guide — the integration you’re leaving
This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.