Skip to content
Last updated

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 as well. This guide covers only the recurring part.


Migration timeline

DateWhat happens
18 November 2026Vipps goes live as a recurring payment method in Kustom Checkout. You can create new Vipps tokens and charge them through Kustom.
21 February 2027Last day to create new Vipps agreements outside Kustom. From 22 February 2027, every new Vipps token is created through Kustom.
8 March 2027Your existing Vipps agreements become chargeable through Kustom.
30 April 2027All 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

DimensionVipps Recurring APIKustom
Auth methodAccess token + several Ocp-Apim-* / Merchant-Serial-Number / Vipps-System-* headersHTTP Basic Auth (username:password)
Base URL (test)https://apitest.vipps.nohttps://api.playground.kustom.co
Base URL (prod)https://api.vipps.nohttps://api.kustom.co
What represents the subscriptionAgreement (agreementId)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 widget
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 that Vipps schedulesImmediately, when you call the API
RetriesVipps retries for retryDaysYou retry on your own schedule
Capture modeltransactionType: DIRECT_CAPTURE or RESERVE_CAPTUREauto_capture: true, or capture later via Order Management
Read statusGET /recurring/v3/agreements/{agreementId}GET /customer-token/v1/tokens/{recurring_token}
StopPATCH /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}/...
IdempotencyIdempotency-Key header on every callNo idempotency key on the token charge. Klarna-Idempotency-Key on Order Management calls.
AmountsMinor 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:

Authorization: Bearer <access-token>
Ocp-Apim-Subscription-Key: <your-subscription-key>
Merchant-Serial-Number: <your-msn>
Idempotency-Key: <unique-key>
Vipps-System-Name: <your-system-name>
Vipps-System-Version: <version>
Vipps-System-Plugin-Name: <your-plugin-name>
Vipps-System-Plugin-Version: <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.

# Encode once and store as a constant
echo -n "username:password" | base64
Authorization: Basic <base64(username:password)>
Content-Type: application/json
EnvironmentVippsKustom
Test (Playground)https://apitest.vipps.nohttps://api.playground.kustom.co
Productionhttps://api.vipps.nohttps://api.kustom.co

Get your credentials from the Kustom Merchant Portal. Generate separate credentials for Playground and production.

Recurring must be enabled on your MID. Contact Kustom merchant support 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

{
  "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:

{
  "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.

{
  "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.

  • 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 fieldKustom fieldNotes
pricing.amount(none on the token)Kustom tokens have no stored price. You send the amount on every charge.
pricing.currencypurchase_currencyISO 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.
productNameorder_lines[].name
productDescription(none)The checkout shows its own consent text
initialCharge.amountorder_amount and order_lines of the checkout order
initialCharge.descriptionorder_lines[].name
initialCharge.transactionTypeoptions.auto_capture on the checkout orderOr capture later with Order Management
initialCharge.orderIdmerchant_reference1Your reference for the first payment
externalIdmerchant_reference2Your subscription ID
merchantRedirectUrlmerchant_urls.confirmationBrowser redirect after the customer completes
merchantAgreementUrl(none)Keep your "My subscriptions" page. Link to it from your own emails.
phoneNumberbilling_address.phoneOptional prefill
scope(none)The completed order already contains the billing address, email and phone
campaignA discount order line on the relevant chargeKustom has no campaign object. Apply the discounted price when you charge.
countryCodepurchase_countryNO, 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.
{
  "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 shows the full push handler.


Step 4 — Replace charge creation

Vipps: POST /recurring/v3/agreements/{agreementId}/charges

{
  "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.

POST https://api.kustom.co/customer-token/v1/tokens/8b7c6e1a-2f3d-4a5b-9c8d-7e6f5a4b3c2d/order
Authorization: Basic <base64>
Content-Type: application/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:

{
  "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 fieldKustom fieldNotes
agreementId (path)recurring_token (path)
amountorder_amountMust equal the sum of order_lines[].total_amount
(currency set on the agreement)purchase_currencyRequired on every charge
descriptionorder_lines[].nameVisible to the customer
transactionType: DIRECT_CAPTUREauto_capture: true
transactionType: RESERVE_CAPTUREauto_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
orderIdmerchant_reference1Use one unique value per billing period
externalIdmerchant_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 using the returned order_id.

ActionVippsKustom
Read the chargeGET /recurring/v3/agreements/{agreementId}/charges/{chargeId}GET /ordermanagement/v1/orders/{order_id}
Capture a reserved chargePOST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/capturePOST /ordermanagement/v1/orders/{order_id}/captures
Cancel a chargeDELETE /recurring/v3/agreements/{agreementId}/charges/{chargeId}POST /ordermanagement/v1/orders/{order_id}/cancel
Refund a chargePOST /recurring/v3/agreements/{agreementId}/charges/{chargeId}/refundPOST /ordermanagement/v1/orders/{order_id}/refunds

Capture a charge created with auto_capture: false:

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

Charge status mapping

Vipps charge statusKustom equivalent
PENDING, DUE, PROCESSINGNo equivalent. The charge does not exist in Kustom until you call the API.
RESERVEDOrder status AUTHORIZED
CHARGEDOrder status CAPTURED
PARTIALLY_CAPTUREDOrder status PART_CAPTURED
FAILEDThe create call returns an error, see Step 6
CANCELLEDOrder status CANCELLED
REFUNDED, PARTIALLY_REFUNDEDrefunded_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 statusBodyWhat to do
400error_code: BAD_VALUEA 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.
400EmptyThe token is cancelled, or recurring is not enabled on your merchant account. Retrying will not help.
403error_code: PAYMENT_METHOD_FAILEDThe payment did not go through. The body has reason and correlation_id. Retry later, then ask the customer to update their payment method.
404EmptyThe token was not found. Check the token value and that you are calling the right environment.
500EmptyThe 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 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:

GET https://api.kustom.co/customer-token/v1/tokens/{recurring_token}
Authorization: Basic <base64>
{
  "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:

PATCH https://api.kustom.co/customer-token/v1/tokens/{recurring_token}/status
Authorization: Basic <base64>
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 status
PENDINGNo token yet. The checkout order is still checkout_incomplete.
ACTIVEACTIVE
STOPPEDCANCELLED
EXPIREDNo 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 VippsWhat 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 in your "My subscriptions" page.
Charge captured, failed or cancelledRead 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.
  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)

# 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)

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

OperationVipps Recurring APIKustom
AuthenticatePOST /accesstoken/getHTTP Basic Auth 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

Further reading