# Migrating from Vipps Recurring to Kustom tokens This guide is for merchants who create and charge subscriptions through the Vipps MobilePay **Recurring API** (`/recurring/v3`). It shows how to replace each Recurring API call with its Kustom equivalent, and how to move the agreements you already have to Kustom without asking your customers to sign up again. In Kustom, a subscription has two parts: 1. The customer completes a normal Kustom Checkout order with `recurring: true`. That order carries a `recurring_token`, which plays the role of the Vipps `agreementId`. 2. You charge the token with the **Customer Token API** (`/customer-token/v1`) whenever a payment is due. Each charge creates an ordinary Kustom order that you manage with the Order Management API. > **Note:** If you also use Vipps Checkout for one-off purchases, follow [Migrating from Vipps Checkout to Kustom Checkout](/contents/checkout/guides/vipps-checkout-to-kustom-checkout) as well. This guide covers only the recurring part. ## Migration timeline | Date | What happens | | --- | --- | | **18 November 2026** | Vipps goes live as a recurring payment method in Kustom Checkout. You can create new Vipps tokens and charge them through Kustom. | | **21 February 2027** | Last day to create new Vipps agreements outside Kustom. From 22 February 2027, every new Vipps token is created through Kustom. | | **8 March 2027** | Your existing Vipps agreements become chargeable through Kustom. | | **30 April 2027** | All Vipps recurring charges must run through Kustom from this date. | Your existing agreements remain valid throughout the migration. The plan is to move them to Kustom without asking customers to sign up again. Step 9 describes how. You can start building today The Kustom recurring API is already live. Today a recurring checkout order offers card, Apple Pay, Google Pay, invoice, Klarna and Amazon Pay. You can build and test your integration against these in the Playground now. Vipps is not a recurring payment method in Kustom yet Until 18 November 2026, a recurring checkout order does not offer Vipps or MobilePay, so no Kustom token is backed by Vipps. Some Vipps-specific details are not confirmed yet, such as the `payment_method_type` a Vipps token returns. Kustom will update this guide before Vipps recurring goes live. ## At a glance | Dimension | Vipps Recurring API | Kustom | | --- | --- | --- | | **Auth method** | Access token + several `Ocp-Apim-*` / `Merchant-Serial-Number` / `Vipps-System-*` headers | HTTP Basic Auth (`username:password`) | | **Base URL (test)** | `https://apitest.vipps.no` | `https://api.playground.kustom.co` | | **Base URL (prod)** | `https://api.vipps.no` | `https://api.kustom.co` | | **What represents the subscription** | Agreement (`agreementId`) | 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 widget | | **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 that Vipps schedules | Immediately, when you call the API | | **Retries** | Vipps retries for `retryDays` | You retry on your own schedule | | **Capture model** | `transactionType`: `DIRECT_CAPTURE` or `RESERVE_CAPTURE` | `auto_capture: true`, or capture later via Order Management | | **Read status** | `GET /recurring/v3/agreements/{agreementId}` | `GET /customer-token/v1/tokens/{recurring_token}` | | **Stop** | `PATCH /recurring/v3/agreements/{agreementId}` with `"status": "STOPPED"` | `PATCH /customer-token/v1/tokens/{recurring_token}/status` with `"status": "CANCELLED"` | | **Capture, cancel, refund a charge** | `/recurring/v3/agreements/{agreementId}/charges/{chargeId}/...` | `/ordermanagement/v1/orders/{order_id}/...` | | **Idempotency** | `Idempotency-Key` header on every call | No idempotency key on the token charge. `Klarna-Idempotency-Key` on Order Management calls. | | **Amounts** | Minor units (øre / cent) | Minor units (øre / cent) | ## Step 1 — Replace credentials and base URLs ### Vipps: access token and header set Every Recurring API call carries an access token from `POST /accesstoken/get` plus a set of merchant headers: ```http Authorization: Bearer Ocp-Apim-Subscription-Key: Merchant-Serial-Number: Idempotency-Key: Vipps-System-Name: Vipps-System-Version: Vipps-System-Plugin-Name: Vipps-System-Plugin-Version: ``` ### Kustom: HTTP Basic Auth Kustom uses standard HTTP Basic Auth with a `username` and `password` tied to your Kustom Merchant ID (MID). There is no token exchange. ```bash # Encode once and store as a constant echo -n "username:password" | base64 ``` ```http Authorization: Basic Content-Type: application/json ``` | Environment | Vipps | Kustom | | --- | --- | --- | | Test (Playground) | `https://apitest.vipps.no` | `https://api.playground.kustom.co` | | Production | `https://api.vipps.no` | `https://api.kustom.co` | > **Get your credentials** from the [Kustom Merchant Portal](https://portal.kustom.co). Generate separate credentials for Playground and production. > **Recurring must be enabled on your MID.** Contact [Kustom merchant support](https://help.kustom.co/en/) to turn on recurring payments before you start testing. Ask for Vipps to be included as a recurring payment method once it is available. ## Step 2 — Replace agreement creation with a recurring checkout order From 22 February 2027, every new Vipps token is created through Kustom. Switch your sign-up flow to the Kustom recurring checkout below, and stop creating agreements with `POST /recurring/v3/agreements` after 21 February 2027. ### Vipps: `POST /recurring/v3/agreements` ```json { "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", "productDescription": "1 kg of coffee every month", "phoneNumber": "4791234567", "externalId": "sub-1001" } ``` **Response:** ```json { "agreementId": "agr_5kSeqz", "uuid": "a3a7a0e3-3b6b-4b0e-8e0a-6f1d2e2c9b11", "vippsConfirmationUrl": "https://api.vipps.no/v2/register/U6JUjQXq8HQmmV", "chargeId": "sub-1001-0" } ``` You redirect the customer to `vippsConfirmationUrl`. The agreement stays `PENDING` until the customer approves it in the app, and then becomes `ACTIVE`. ### Kustom: `POST /checkout/v3/orders` with `recurring: true` In Kustom, the sign-up is a normal checkout order. Add `recurring: true` and render the checkout as usual. The customer approves future charges in the checkout widget. ```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}" } } ``` The response contains `order_id` and `html_snippet`. Render the snippet exactly as for any Kustom Checkout order. If you have not done this before, see the frontend step in [Migrating from Vipps Checkout to Kustom Checkout](/contents/checkout/guides/vipps-checkout-to-kustom-checkout). - **`recurring: true`** tells Kustom to create a token when the customer completes the order. Only payment methods that can be charged again are offered. Financing, Vipps, MobilePay and external payment methods are not. - **`options.subscription_buy_button`** changes the buy button to show that the customer is subscribing. It only works together with `recurring: true`. - **The consent text is Kustom's.** The checkout tells the customer that automatic payments are being set up with your store, using your merchant name. You don't supply this text. - **There is no interval on the token.** Your own system decides when to charge. Kustom doesn't store the price or the interval. > **Sign-up without a first payment.** Vipps lets you create an agreement without `initialCharge`. The Kustom docs don't confirm that a recurring checkout order can have `order_amount: 0`. Check with Kustom merchant support before you design a free trial around it. ### Field mapping reference | Vipps field | Kustom field | Notes | | --- | --- | --- | | `pricing.amount` | *(none on the token)* | Kustom tokens have no stored price. You send the amount on every charge. | | `pricing.currency` | `purchase_currency` | ISO 4217. A token can only be charged in a currency its payment method supports. | | `pricing.type: VARIABLE`, `suggestedMaxAmount` | *(none)* | Kustom has no customer-chosen maximum. Tell the customer about amount changes yourself. | | `interval.unit`, `interval.count` | *(none)* | The schedule lives in your system. Kustom doesn't store an interval. | | `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` on the checkout order | Or capture later with Order Management | | `initialCharge.orderId` | `merchant_reference1` | Your reference for the first payment | | `externalId` | `merchant_reference2` | Your subscription ID | | `merchantRedirectUrl` | `merchant_urls.confirmation` | Browser redirect after the customer completes | | `merchantAgreementUrl` | *(none)* | Keep your "My subscriptions" page. Link to it from your own emails. | | `phoneNumber` | `billing_address.phone` | Optional prefill | | `scope` | *(none)* | The completed order already contains the billing address, email and phone | | `campaign` | A `discount` order line on the relevant charge | Kustom has no campaign object. Apply the discounted price when you charge. | | `countryCode` | `purchase_country` | `NO`, `DK` or `FI` | | `isApp`, `skipLandingPage` | *(none)* | The checkout handles the app switch for Vipps | ## Step 3 — Store the token when the customer completes ### Vipps You poll `GET /recurring/v3/agreements/{agreementId}` or wait for an agreement webhook until the status is `ACTIVE`. Then you store the `agreementId`. ### Kustom When the customer completes the checkout, Kustom calls your `merchant_urls.push` URL. Your push handler should: 1. Read `kustom_order_id` from the query string. 2. Fetch the order with `GET /checkout/v3/orders/{order_id}` and read `recurring_token`. 3. Store `recurring_token` against the customer's subscription in your system. 4. Acknowledge the order with `POST /ordermanagement/v1/orders/{order_id}/acknowledge`. 5. Return `HTTP 200`. ```json { "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.** Store it where you would store a card token. A token can only be charged by the MID that created it. The acknowledge call is the same as for any checkout order. The callback handler step in [Migrating from Vipps Checkout to Kustom Checkout](/contents/checkout/guides/vipps-checkout-to-kustom-checkout) shows the full push handler. ## Step 4 — Replace charge creation ### Vipps: `POST /recurring/v3/agreements/{agreementId}/charges` ```json { "amount": 29900, "transactionType": "DIRECT_CAPTURE", "description": "Coffee subscription, March", "due": "2027-03-15", "retryDays": 5, "orderId": "sub-1001-3", "externalId": "sub-1001" } ``` Vipps queues the charge. It must be created at least two days before `due` in production, and Vipps attempts it on the due date and for `retryDays` after. ### Kustom: `POST /customer-token/v1/tokens/{recurring_token}/order` Kustom charges the token the moment you call it. Each call is one synchronous payment. There is no queue, no due date and no pending state. ```http POST https://api.kustom.co/customer-token/v1/tokens/8b7c6e1a-2f3d-4a5b-9c8d-7e6f5a4b3c2d/order Authorization: Basic Content-Type: application/json ``` ```json { "purchase_currency": "NOK", "locale": "nb-NO", "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 } ] } ``` **Response:** ```json { "order_id": "a89ec121-1276-419d-882a-c343d58fd1bc", "fraud_status": "ACCEPTED", "authorized_payment_method": { "type": "card" } } ``` Store the `order_id`. You need it to capture, cancel or refund the charge. - **A `200` means the charge went through.** The order is authorized straight away. With `auto_capture: true` it is also captured. `fraud_status` is always `ACCEPTED`, so there is nothing to branch on. - **A failed charge is an error response,** not a pending order. See Step 6. - **`redirect_url`** is only returned if you send `merchant_urls.confirmation`, and it echoes that URL. - **A `shipping_address`, if you send one, must be complete:** `given_name`, `family_name`, `email`, `street_address`, `postal_code`, `city` and `country`. A missing field is rejected with `BAD_VALUE`. ### Charge field mapping | Vipps field | Kustom field | Notes | | --- | --- | --- | | `agreementId` (path) | `recurring_token` (path) | | | `amount` | `order_amount` | Must equal the sum of `order_lines[].total_amount` | | *(currency set on the agreement)* | `purchase_currency` | Required on every charge | | `description` | `order_lines[].name` | Visible to the customer | | `transactionType: DIRECT_CAPTURE` | `auto_capture: true` | | | `transactionType: RESERVE_CAPTURE` | `auto_capture: false` (default) | Capture later with Order Management, see Step 5 | | `due` | *(none)* | Call the API on the day you want to charge | | `retryDays` | *(none)* | See Step 6 | | `orderId` | `merchant_reference1` | Use one unique value per billing period | | `externalId` | `merchant_reference2` | | | `type: UNSCHEDULED` | *(no difference)* | Every Kustom token charge is on demand | | `Idempotency-Key` header | *(none)* | The token charge takes no idempotency key. See Step 6 before you retry. | ## Step 5 — Manage the charge after it is created A Vipps charge is managed under the agreement. A Kustom token charge is an ordinary order, so you manage it with the [Order Management API](/contents/api/order-management) using the returned `order_id`. | Action | Vipps | Kustom | | --- | --- | --- | | Read the charge | `GET /recurring/v3/agreements/{agreementId}/charges/{chargeId}` | `GET /ordermanagement/v1/orders/{order_id}` | | Capture a reserved charge | `POST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture` | `POST /ordermanagement/v1/orders/{order_id}/captures` | | Cancel a charge | `DELETE /recurring/v3/agreements/{agreementId}/charges/{chargeId}` | `POST /ordermanagement/v1/orders/{order_id}/cancel` | | Refund a charge | `POST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/refund` | `POST /ordermanagement/v1/orders/{order_id}/refunds` | **Capture a charge created with `auto_capture: false`:** ```http POST https://api.kustom.co/ordermanagement/v1/orders/a89ec121-1276-419d-882a-c343d58fd1bc/captures Authorization: Basic Content-Type: application/json Klarna-Idempotency-Key: sub-1001-3-capture ``` ```json { "captured_amount": 29900, "description": "Coffee subscription, March" } ``` ### Charge status mapping | Vipps charge status | Kustom equivalent | | --- | --- | | `PENDING`, `DUE`, `PROCESSING` | No equivalent. The charge does not exist in Kustom until you call the API. | | `RESERVED` | Order status `AUTHORIZED` | | `CHARGED` | Order status `CAPTURED` | | `PARTIALLY_CAPTURED` | Order status `PART_CAPTURED` | | `FAILED` | The create call returns an error, see Step 6 | | `CANCELLED` | Order status `CANCELLED` | | `REFUNDED`, `PARTIALLY_REFUNDED` | `refunded_amount` on the order is greater than zero | ## Step 6 — Take over scheduling, retries and failed payments Vipps runs the charge on the due date and retries it for you. Kustom does neither, so your billing job must: 1. Decide which subscriptions are due today. 2. Charge each token once per billing period, and record in your own system that you did. 3. Handle the result. On `PAYMENT_METHOD_FAILED`, retry later. If the retries also fail, ask the customer to update their payment method. Only some errors from `POST /customer-token/v1/tokens/{recurring_token}/order` have a body, so check the HTTP status code first: | HTTP status | Body | What to do | | --- | --- | --- | | 400 | `error_code: BAD_VALUE` | A field is invalid, and `error_messages` names it. Also returned when the token belongs to another merchant ID (`Token do not belong to merchant`). Fix the request. Do not retry it unchanged. | | 400 | Empty | The token is cancelled, or recurring is not enabled on your merchant account. Retrying will not help. | | 403 | `error_code: PAYMENT_METHOD_FAILED` | The payment did not go through. The body has `reason` and `correlation_id`. Retry later, then ask the customer to update their payment method. | | 404 | Empty | The token was not found. Check the token value and that you are calling the right environment. | | 500 | Empty | The order may or may not have been created. Check before you charge again, so the customer isn't charged twice. | See [Place order from customer token](/contents/checkout/use-cases/place-order-token) for the full reference. > **Updating the payment method.** To replace a failing payment method, send the customer through a new recurring checkout order (Step 2). Store the new `recurring_token`, then cancel the old one (Step 7). ## Step 7 — Read and stop a token ### Vipps - `GET /recurring/v3/agreements/{agreementId}` returns `PENDING`, `ACTIVE`, `STOPPED` or `EXPIRED`. - `PATCH /recurring/v3/agreements/{agreementId}` with `{"status": "STOPPED"}` stops the agreement. - The same `PATCH` can change `productName`, `pricing` or `interval`. ### Kustom **Read a token:** ```http GET https://api.kustom.co/customer-token/v1/tokens/{recurring_token} Authorization: Basic ``` ```json { "payment_method_type": "CARD", "status": "ACTIVE", "card": { "brand": "visa", "expiry_date": "11/2031", "masked_number": "************4242" } } ``` - `status` is `ACTIVE` or `CANCELLED`. A token stays `ACTIVE` until it is cancelled. It doesn't expire when it isn't used. - `payment_method_type` is `CARD`, `INVOICE`, `DIRECT_DEBIT` or `WALLET`. It can be missing on a cancelled token. - Only card tokens return a `card` object. - An unknown token returns `404` with an empty body. Check the status before a billing run rather than discovering a dead token in a failed batch. **Cancel a token:** ```http PATCH https://api.kustom.co/customer-token/v1/tokens/{recurring_token}/status Authorization: Basic Content-Type: application/json ``` ```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 status | | --- | --- | | `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 price or interval.** A Kustom token has no stored price or interval, so there is nothing to patch. Update your own records and send the new amount on the next charge. Tell the customer about the change as your terms require. > **Pausing a subscription.** There is no pause status. Skip charges in your own billing job while the subscription is paused. ## Step 8 — Replace agreement and charge webhooks Vipps sends webhooks when agreements and charges change state. Kustom has no agreement webhooks, because nothing happens to a token without an API call from you. | Event you listened for in Vipps | 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 in your "My subscriptions" page. | | Charge captured, failed or cancelled | Read the result of the token order call directly (Steps 4 and 6). A token charge is never left pending. | ## Step 9 — Move your existing Vipps agreements to Kustom From **8 March 2027**, your existing active Vipps agreements can be charged through Kustom. You do not 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 How existing agreements are transferred is still being confirmed with Vipps and Kustom's payment partner. Kustom will contact each affected merchant with the delivery details and format of the token mapping before 8 March 2027. Make sure your Kustom account has an up-to-date technical contact. Complete Steps 1 to 8 before the mapping arrives. Then cut over like this: 1. **Import the mapping.** Store each `recurring_token` on the matching subscription. Keep the Vipps `agreementId` as well, for reconciliation and support cases. 2. **Check the tokens.** Call `GET /customer-token/v1/tokens/{recurring_token}` for each token and confirm `status: ACTIVE`. Spread the calls out to stay within the [API rate limits](/contents/api/api-basics/rate-limit). 3. **Stop scheduling new Vipps charges.** Do not create Vipps charges with a `due` date after your cut-over date. Cancel any that are already queued for later. 4. **Switch each subscription at its next billing date.** Charge it through Kustom as in Step 4. Charge each billing period through exactly one provider, so no customer is charged twice. 5. **Stop the Vipps agreement only after the first Kustom charge succeeds.** 6. **Finish before 30 April 2027.** From that date, all Vipps recurring charges must run through Kustom. From 22 February 2027, every new Vipps token is created through Kustom, so customers who sign up from that date always go through the Kustom recurring checkout (Step 2). ## Complete before and after ### Before (Vipps) ```bash # 1. Get an access token curl -X POST https://api.vipps.no/accesstoken/get \ -H "client_id: $VIPPS_CLIENT_ID" \ -H "client_secret: $VIPPS_CLIENT_SECRET" \ -H "Ocp-Apim-Subscription-Key: $VIPPS_SUB_KEY" \ -H "Merchant-Serial-Number: $VIPPS_MSN" # 2. Create a draft agreement, then redirect the customer to vippsConfirmationUrl curl -X POST https://api.vipps.no/recurring/v3/agreements \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Ocp-Apim-Subscription-Key: $VIPPS_SUB_KEY" \ -H "Merchant-Serial-Number: $VIPPS_MSN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "pricing": { "type": "LEGACY", "amount": 29900, "currency": "NOK" }, "interval": { "unit": "MONTH", "count": 1 }, "initialCharge": { "amount": 29900, "description": "First month", "transactionType": "DIRECT_CAPTURE" }, "merchantRedirectUrl": "https://example.com/subscription/result", "merchantAgreementUrl": "https://example.com/my-subscriptions", "productName": "Coffee subscription" }' # 3. Each month: queue a charge at least two days before it is due curl -X POST https://api.vipps.no/recurring/v3/agreements/$AGREEMENT_ID/charges \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Ocp-Apim-Subscription-Key: $VIPPS_SUB_KEY" \ -H "Merchant-Serial-Number: $VIPPS_MSN" \ -H "Idempotency-Key: sub-1001-3" \ -H "Content-Type: application/json" \ -d '{ "amount": 29900, "transactionType": "DIRECT_CAPTURE", "description": "Coffee subscription, March", "due": "2027-03-15", "retryDays": 5, "orderId": "sub-1001-3" }' ``` ### After (Kustom) ```bash KUSTOM_AUTH="Basic $(echo -n "$KUSTOM_USER:$KUSTOM_PASS" | base64)" # 1. Create a recurring checkout order and render html_snippet curl -X POST https://api.kustom.co/checkout/v3/orders \ -H "Authorization: $KUSTOM_AUTH" \ -H "Content-Type: application/json" \ -d '{ "purchase_country": "NO", "purchase_currency": "NOK", "locale": "nb-NO", "order_amount": 29900, "order_tax_amount": 5980, "recurring": true, "order_lines": [ { "type": "physical", "reference": "COFFEE-1KG", "name": "Coffee subscription, first month", "quantity": 1, "unit_price": 29900, "tax_rate": 2500, "total_amount": 29900, "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}" } }' # 2. On push: read recurring_token, then acknowledge curl https://api.kustom.co/checkout/v3/orders/$ORDER_ID \ -H "Authorization: $KUSTOM_AUTH" curl -X POST https://api.kustom.co/ordermanagement/v1/orders/$ORDER_ID/acknowledge \ -H "Authorization: $KUSTOM_AUTH" \ -H "Klarna-Idempotency-Key: sub-1001-0" # 3. Each month, on the billing date: charge the token curl -X POST https://api.kustom.co/customer-token/v1/tokens/$RECURRING_TOKEN/order \ -H "Authorization: $KUSTOM_AUTH" \ -H "Content-Type: application/json" \ -d '{ "purchase_currency": "NOK", "locale": "nb-NO", "order_amount": 29900, "order_tax_amount": 5980, "auto_capture": true, "merchant_reference1": "sub-1001-3", "order_lines": [ { "type": "physical", "reference": "COFFEE-1KG", "name": "Coffee subscription, March", "quantity": 1, "unit_price": 29900, "tax_rate": 2500, "total_amount": 29900, "total_tax_amount": 5980 } ] }' # → returns order_id, fraud_status and authorized_payment_method ``` ## Common gotchas **Charges run immediately** A Vipps charge is queued for its `due` date. A Kustom token charge is taken the moment you call the API. Do not port a job that creates Vipps charges days in advance without changing when it runs, or customers will be charged early. **No automatic retries** Vipps retries a failed charge for `retryDays`. Kustom returns `PAYMENT_METHOD_FAILED` and leaves the retry to you. Build retry logic into your billing job before you move any volume. **Amounts must add up** `order_amount` must equal the sum of `order_lines[].total_amount`, and `order_tax_amount` the sum of `order_lines[].total_tax_amount`. A single `amount` field is not enough in Kustom. **Currency on every charge** Vipps stores the currency on the agreement. Kustom needs `purchase_currency` on every token order, and it must match what the payment method supports. **Tokens belong to one MID** A `recurring_token` can only be used with the credentials of the MID that created it. Another MID gets `400 BAD_VALUE` with `Token do not belong to merchant`. If you run several MIDs, store which MID each token belongs to. **Recurring must be switched on** If recurring is not enabled on your MID, a token charge is rejected with `400` and an empty body. Ask Kustom merchant support to enable it on both your Playground and production MIDs. **Sign-ups that still create Vipps agreements** From 22 February 2027, every new Vipps token is created through Kustom. Switch your sign-up flow to the Kustom recurring checkout (Step 2) before then. **Double charging during cut-over** Charge each billing period through one provider only. Keep a record per subscription of which provider charged each period until 30 April 2027. **Stopping the Vipps agreement too early** If you stop a Vipps agreement before the first Kustom charge succeeds, you have no fallback for that customer. Stop it only after the first Kustom charge succeeds. **No idempotency key on token charges** Vipps deduplicates charges with `Idempotency-Key`. The Kustom token charge takes no idempotency key. If a charge times out or returns `500`, check whether the order was created before you send it again. **Error codes are few, and most errors have no body** Branch on the HTTP status code. Only `BAD_VALUE` and `PAYMENT_METHOD_FAILED` come with an `error_code`. **Legacy fields don't carry over** You don't need `recurring_description` or the order-line `subscription` object. Kustom Checkout shows its own consent text, and the Customer Token API ignores the subscription object. ## Endpoint summary | Operation | Vipps Recurring API | Kustom | | --- | --- | --- | | Authenticate | `POST /accesstoken/get` | HTTP Basic Auth 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` | ## Further reading - [Recurring / Tokenization](/contents/checkout/use-cases/recurring) - [Place order from customer token](/contents/checkout/use-cases/place-order-token) - [Read customer token](/contents/checkout/additional-resources/read-customer-token) - [Customer token lifecycle](/contents/checkout/additional-resources/customer-token-lifecycle) - [Customer Token API reference](/contents/api/customer-token) - [Migrating from Vipps Checkout to Kustom Checkout](/contents/checkout/guides/vipps-checkout-to-kustom-checkout) - [Vipps MobilePay Recurring API guide](https://developer.vippsmobilepay.com/docs/APIs/recurring-api/)