Vipps MobilePay payment handler
This documentation is under development. Content and integration details may change as we continue internal discussions. Feedback is welcome.
- Handler Name:
com.vippsmobilepay.ucp.payment_handler - Version:
2026-01-23
Introduction
This payment handler supports payment creation through the ePayment API. Two payment methods are supported:
- WALLET - For customers with a wallet. Uses push notification flow
(
userFlow: PUSH_MESSAGE). - CARD - For customers without a wallet. Uses redirect flow (
userFlow: WEB_REDIRECT) where users enter card details manually.
Key benefits
- Efficient integration with our payment services.
- Seamless checkout experience for customers with or without a wallet.
- Secure handling of payment credentials and transactions.
Integration guide
| Participant | Integration Section |
|---|---|
| Business | Business Integration |
| Platform | Platform Integration |
Participants
This payment handler covers two participants: the business for which a customer is shopping from and the platform that facilitates the checkout process (such as an agent).
- The business is responsible for processing payments using this handler,
- The platform offers the supported payment instrument to the customer in the checkout flow.
- The business registers this handler and configures/implements it, and the platform discovers and utilizes the handler during checkout.
- The business needs to have an active merchant agreement and an ecommerce sales unit registered
- The platform needs to validate that a user has a valid wallet identity before offering this payment option. Suggestion is to utilize the Login Product to do so
Note on terminology: While this specification refers to the participant as the "business," technical schema fields may retain the standard industry nomenclature
merchant_*(e.g.,merchant_id). Mappings are documented below.
| Participant | Role | Prerequisites |
|---|---|---|
| Business | Creates a payment for the defined customer | Needs to have an active merchant agreement and ecom sales unit |
| Platform | Collects the customer information, along with validating that the customer has a wallet identity. | Needs to validate that the user has an wallet identity |
Payment flow diagram
The following diagram shows the async payment flow for this handler. The credential can be either MSISDN (phone number) or TOKEN (customer token):
Note: To create a payment, the business MUST create a Access Token using their credentials and then call the Epayment API CreatePayments endpoint to create a payment. The suggested payment
userFlowisPUSH_MESSAGE, which triggers a push notification to the user's app for approval. It is also possible to have the default user flow (REDIRECT_URL), the business must then echo back theredirectUrlto the platform using the UCPstatus = requires_escalationwith thecontinue_urlset to theredirectUrl.
Business integration
Business prerequisites
Before advertising this handler, businesses MUST complete:
- Create a merchant agreement and register an Ecommerce sales unit.
- Implement server-side logic to generate access tokens and create payments using the Access Token and ePayment APIs.
Prerequisites Output:
| Field | Description |
|---|---|
identity.clientId | The client id to call the APIs with |
identity.clientSecret | The secret to call the APIs with |
identity.subscriptionKey | The subscription key to call the APIs with. |
merchant.merchantSerialNumber | The merchant identifier in the systems |
Handler configuration
Businesses advertise support for this handler in their /.well-known/ucp profile under the
payment.handlers array.
Configuration schema
Schema URL: https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/wallet_payment_handler.json
| Field | Type | Required | Description |
|---|---|---|---|
merchant_serial_number | string | Yes | The merchant identifier (Merchant Serial Number). This is obtained when registering an ecommerce sales unit. Format varies by country (5-6 digits typically). |
environment | enum | No | The environment. Either TEST or PRODUCTION. Defaults to PRODUCTION if not specified. Use TEST for sandbox testing with test credentials. |
allowed_payment_instruments | array | No | The payment instruments allowed for this handler. If not specified, all instrument types are allowed. Supported types: WALLET and CARD. |
Example handler declaration
{
"payment": {
"handlers": [
{
"id": "vipps_mobilepay",
"name": "com.vippsmobilepay.ucp.payment_handler",
"version": "2026-01-23",
"spec": "https://ucp.vippsmobilepay.com/ucp/2026-01-23/vipps_mp_payment_handler",
"config_schema": "https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/wallet_payment_handler.json",
"instrument_schemas": [
"https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/payment_instrument.json"
],
"config": {
"merchant_serial_number": "123456",
"environment": "PRODUCTION"
}
}
]
}
}
Instrument schema
Schema URL: https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/payment_instrument.json
This payment instrument defines the structure for payments submitted by the platforms supporting this handler.
| Field | Type | Required | Description |
|---|---|---|---|
type | enum | Yes | The payment instrument type. One of WALLET or CARD. |
credential | object | Yes (WALLET only) | The credential information required to process WALLET payments. See Credential Schema. |
return_url | string | Depends | The URL to redirect the user back to after a payment. This is required for non-push wallet flows and card. Can be a universal link or website URL. |
Instrument types
| Type | Vipps paymentMethod.type | Vipps userFlow | Description |
|---|---|---|---|
WALLET | WALLET | PUSH_MESSAGE, WEB_REDIRECT | When userFlow is PUSH_MESSAGE, this triggers a push notification to user's app. Requires customer credential (MSISDN or TOKEN). If userFlow is WEB_REDIRECT, the user must be redirected to the URL returned in the payment created call. |
CARD | CARD | WEB_REDIRECT | Redirect user to Vipps payment page to enter card details. No wallet required. |
Credential schema
Schema URL:
https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/wallet_payment_credential.json
The credential object contains the buyer's wallet identifier. Two credential types are supported:
| Field | Type | Required | Description |
|---|---|---|---|
type | enum | Yes | The credential type. One of MSISDN or TOKEN. |
value | string | Yes | The credential value. Format depends on the credential type (see below). |
MSISDN credential (public)
The MSISDN credential uses the customer's phone number. This maps to the customer.phoneNumber
field in the
Vipps ePayment API.
| Field | Description |
|---|---|
value | The phone number in MSISDN format: digits only with country code and subscriber number, no leading zeros or plus sign. Example: 4712345678 for Norwegian number +47 12 34 56 78. |
Example MSISDN Instrument:
{
"type": "WALLET",
"credential": {
"type": "MSISDN",
"value": "4712345678"
}
}
TOKEN credential
The TOKEN credential uses an encoded customer token obtained from authenticated user sessions. This
maps to the customer.customerToken field in the
ePayment API.
| Field | Description |
|---|---|
value | An encoded customer token string obtained from authenticated sessions. |
Example TOKEN Instrument:
{
"type": "WALLET",
"credential": {
"type": "TOKEN",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
CARD instrument (no wallet required)
The CARD instrument allows customers who do not have a wallet to pay using their card. This uses the redirect flow where the user is sent to a payment page to enter their card details.
| Field | Type | Required | Description |
|---|---|---|---|
type | enum | Yes | Must be CARD. |
return_url | string | Yes | The URL to redirect the user back to after payment. Can be a universal link (e.g., myapp://payment-complete) or website URL (e.g., https://example.com/payment-complete). |
Example CARD Instrument:
{
"type": "CARD",
"return_url": "https://chat.vippsmobilepay.com/payment-complete?session_id=cs-ABC123XYZ"
}
Example CARD Instrument with Universal Link:
{
"type": "CARD",
"return_url": "vipps-chat://payment-complete?session_id=cs-ABC123XYZ"
}
Processing payments
Upon receiving a call for completing a checkout with this handler, businesses MUST:
- Validate Handler: Confirm
instrument.handler_idmatches an advertised handler. - Ensure Idempotency: If the request is a retry (matches a previous
checkout_idor idempotency key), return the previous result immediately without re-processing funds. - Determine Payment Method: Based on the instrument type:
WALLET→ UsepaymentMethod: { type: "WALLET" }anduserFlow: "PUSH_MESSAGE" or "WEB_REDIRECT (requires return_url)"CARD→ UsepaymentMethod: { type: "CARD" }anduserFlow: "WEB_REDIRECT"
- Build API Request: See WALLET Payment Flow or Redirect Payment Flow below.
- Call the ePayment API CreatePayment endpoint.
- Return Response: Based on instrument type:
WALLET→ Returnstatus: complete_in_progresswith message to check the appCARD→ Returnstatus: complete_in_progresswithcontinue_urlfor platform to redirect user
- Handle callback and poll: When the user approves/rejects, We sends a callback. Update the checkout session accordingly. As callbacks are not guaranteed, implement polling as a backup to check payment status. It is also possible to base this on webhooks if the platform supports that.
- Return completed order: When the platform polls and payment is authorized, return
status: completedwith the order.
paymentMethod mapping
The paymentMethod.type field in the ePayment API MUST match the instrument type:
| Instrument Type | paymentMethod.type | userFlow | Description |
|---|---|---|---|
WALLET | WALLET | PUSH_MESSAGE | Push notification to user's app |
CARD | CARD | WEB_REDIRECT | Redirect to a payment page for card entry |
See the
ePayment API documentation
for full details on the paymentMethod and userFlow fields.
WALLET payment flow
The PUSH_MESSAGE flow is asynchronous - the user must approve the payment in
their app. This can take seconds to minutes. Businesses MUST NOT block the HTTP response
waiting for user approval.
Why async?
| Approach | Problem |
|---|---|
| Block HTTP request | Timeouts after 30s, user often needs more time |
| Short timeout (5s) | User can't approve that fast |
| Keep connection open | Unreliable, poor scalability |
Recommended flow
- Business creates payment → We return
state: CREATED - Business returns immediately with
status: complete_in_progress - We send push notification to user's phone
- User approves in app
- We sends callback to business with
state: AUTHORIZED - Business updates checkout to
status: completed - Platform polls and receives completed order
Complete checkout response (pending approval)
When the payment is created but awaiting user approval:
{
"id": "cs-ABC123XYZ",
"status": "complete_in_progress",
"messages": [
{
"type": "info",
"code": "payment_pending_user_approval",
"content": "A payment request has been sent to your Vipps app. Please open Vipps and approve the payment to complete your order."
}
],
"payment": {
"state": "pending_approval",
"expires_at": "2026-01-26T12:35:00Z"
},
"ucp": {
"version": "2026-01-11",
"capability": "dev.ucp.shopping.checkout"
}
}
Message codes
| Code | Type | Instrument | Description |
|---|---|---|---|
payment_pending_user_approval | info | WALLET | Payment request sent, awaiting user approval in the app |
payment_redirect_required | info | CARD | User must be redirected to a payment page to enter card details |
payment_approved | info | Both | User approved/completed the payment |
payment_rejected | error | Both | User rejected or cancelled the payment |
payment_expired | error | Both | Payment request expired before user responded |
Timeout handling
Businesses SHOULD implement timeout handling:
- Default timeout: 5 minutes from payment creation
- On timeout: Cancel the payment and set checkout to error state
- Expose expiry: Include
payment.expires_atin responses
Callback handling
Businesses MUST implement a callback endpoint to receive payment status updates.
Important: We don't guarantee callback delivery. Businesses MUST also implement polling as a backup. See Polling Guidelines.
Callback URL: Configured in MerchantInfo.callbackUrl
Callback Payload:
{
"reference": "cs-ABC123XYZ",
"state": "AUTHORIZED",
"pspReference": "1234567890"
}
Callback States:
| State | Action |
|---|---|
AUTHORIZED | Update checkout to completed, create order |
ABORTED | Update checkout with error: user rejected |
EXPIRED | Update checkout with error: payment expired |
TERMINATED | Update checkout with error: payment cancelled |
Business-side polling (backup)
Since callbacks aren't guaranteed, businesses MUST implement polling as a backup mechanism to check payment status.
Polling Guidelines:
- Start polling: 5 seconds after payment creation
- Poll interval: Every 2 seconds
- Endpoint:
GET /epayment/v1/payments/{reference}
Example Polling Logic:
async function pollPaymentStatus(reference: string, maxAttempts = 150) {
const POLL_INTERVAL_MS = 2000; // 2 seconds
const INITIAL_DELAY_MS = 5000; // 5 seconds
// Wait 5 seconds before starting
await new Promise(resolve => setTimeout(resolve, INITIAL_DELAY_MS));
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const payment = await getPayment(reference);
if (payment.state === "AUTHORIZED") {
// Payment approved - complete the order
return { success: true, state: "AUTHORIZED" };
}
if (["ABORTED", "EXPIRED", "TERMINATED"].includes(payment.state)) {
// Payment failed - update checkout with error
return { success: false, state: payment.state };
}
// Still CREATED - wait and poll again
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
}
// Timeout - treat as expired
return { success: false, state: "TIMEOUT" };
}
Note: The callback and polling mechanisms are complementary. Whichever receives the status update first should process it, and the other should recognize that processing has already occurred (idempotency).
Redirect payment flow
This is applicable to CARD payments or WALLET payments with WEB_REDIRECT
In the payment flows that uses WEB_REDIRECT, the user is redirected to a
payment page to complete their payment. This flow is for customers who do not have a wallet identity (card) or for cases where PUSH_MESSAGE is not possible.
Flow diagram
Redirect specific request fields
When creating a payment with userFlow: { type: "WEB_REDIRECT" }, the business MUST include:
| Field | Description |
|---|---|
returnUrl | The URL to redirect the user back to after payment. Use the return_url from the instrument. |
Business response
When the payment is created, we return a redirectUrl. The business MUST return this to the
platform as continue_url per the
UCP Continue URL specification.
The continue_url enables checkout handoff from platform to business UI, allowing the buyer to
complete the payment on the Vipps MobilePay payment page. Per UCP, when status is
complete_in_progress, the business is processing the Complete Checkout request and may provide a
continue_url for user interaction.
{
"id": "cs-ABC123XYZ",
"status": "complete_in_progress",
"continue_url": "https://api.vipps.no/checkout/v3/session/...",
"messages": [
{
"type": "info",
"code": "payment_redirect_required",
"content": "Please complete your payment on the payment page."
}
],
"payment": {
"state": "pending_redirect",
"expires_at": "2026-01-26T12:35:00Z"
}
}
Platform redirect handling
When the platform receives a complete_in_progress response with a continue_url and message code
payment_redirect_required:
- Redirect the user: Open the
continue_urlin a browser or webview to hand off to the payment page - User completes payment: User enters card details on the payment page
- Return redirect: After payment, we redirect user to the
returnUrlprovided in the instrument - Poll for completion: After redirect, poll
GET /checkout-sessions/{id}to get the final status (completedor error)
Return URL Formats:
| Type | Example | Use Case |
|---|---|---|
| Universal Link | vipps-chat://payment-complete?session_id=cs-ABC123XYZ | Native mobile app |
| Website URL | https://chat.vippsmobilepay.com/payment-complete?session_id=... | Web-based chat or fallback |
Note: The
returnUrlshould include sufficient context (e.g.,session_id) for the platform to resume the checkout flow after the user returns.
Example business implementation
const instrument: PaymentInstrument; // The payment instrument from the request
const checkout: Checkout; // The checkout with lineItems
const credentials: Credentials; // API Credentials
const accessToken: string; // The accessToken you received
const idempotencyKey: string; // Your idempotency key
const url = "https://apitest.vipps.no/epayment/v1/payments"; // Replace for production
/**
* Build the API request body based on instrument type.
* See: https://developer.vippsmobilepay.com/api/epayment/#tag/CreatePayments/operation/createPayment
*/
function buildPaymentRequest(instrument: PaymentInstrument, checkout: Checkout) {
const baseRequest = {
amount: {
currency: checkout.currency,
value: checkout.totals.amount
},
reference: checkout.id,
paymentDescription: `Order ${checkout.id}`
};
if (instrument.type === "WALLET") {
// WALLET: Use PUSH_MESSAGE flow with customer credential
return {
...baseRequest,
customer: buildCustomerObject(instrument.credential),
paymentMethod: {
type: "WALLET" // REQUIRED for WALLET instruments
},
userFlow: "PUSH_MESSAGE" // Or web_Redirect, then returnUrl must be set
};
} else if (instrument.type === "CARD") {
// CARD: Use WEB_REDIRECT flow, no customer credential needed
return {
...baseRequest,
paymentMethod: {
type: "CARD" // REQUIRED for CARD instruments
},
userFlow: "WEB_REDIRECT",
returnUrl: instrument.return_url // Platform provides this for redirect back
};
}
throw new Error(`Unsupported instrument type: ${instrument.type}`);
}
/**
* Build the customer object for WALLET payments based on credential type.
*/
function buildCustomerObject(credential: WalletCredential): { phoneNumber?: string; customerToken?: string } {
switch (credential.type) {
case "MSISDN":
return { phoneNumber: credential.value };
case "TOKEN":
return { customerToken: credential.value };
default:
throw new Error(`Unsupported credential type: ${credential.type}`);
}
}
const body = buildPaymentRequest(instrument, checkout);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`,
"Ocp-Apim-Subscription-Key": credentials.subscriptionKey,
"Merchant-Serial-Number": credentials.merchantSerialNumber,
"Idempotency-Key": idempotencyKey
},
body: JSON.stringify(body)
});
const payment = await response.json();
if (!response.ok) {
return mapErrorToUCPResponse(response.status, payment);
}
// Build response based on instrument type
checkout.status = "complete_in_progress";
checkout.payment = {
reference: payment.reference,
expires_at: new Date(Date.now() + 5 * 60 * 1000).toISOString()
};
if (instrument.type === "WALLET") {
// WALLET: Return message to check app
checkout.payment.state = "pending_approval";
checkout.messages = [{
type: "info",
code: "payment_pending_user_approval",
content: "A payment request has been sent to your Vipps / Mobilepay app. Please open the app and approve the payment."
}];
} else if (instrument.type === "CARD") {
// CARD: Return continue_url for platform to redirect user (per UCP spec)
checkout.payment.state = "pending_redirect";
checkout.continue_url = payment.redirectUrl; // From Vipps response, returned as continue_url
checkout.messages = [{
type: "info",
code: "payment_redirect_required",
content: "Please complete your payment on the payment page."
}];
}
return checkout;
TypeScript Types:
type InstrumentType = "WALLET" | "CARD";
type CredentialType = "MSISDN" | "TOKEN";
interface WalletCredential {
type: CredentialType;
value: string;
}
interface WalletInstrument {
type: "WALLET";
credential: WalletCredential;
return_url?: string; // Only for non push-flow
}
interface CardInstrument {
type: "CARD";
return_url: string; // Universal link or website URL
}
type PaymentInstrument = WalletInstrument | CardInstrument;
// Example API payloads:
// WALLET with MSISDN -> paymentMethod.type: "WALLET", customer.phoneNumber
const walletMsisdnPayload = {
paymentMethod: { type: "WALLET" },
userFlow: "PUSH_MESSAGE",
customer: { phoneNumber: "4712345678" },
// ... other fields
};
// WALLET with TOKEN -> paymentMethod.type: "WALLET", customer.customerToken
const walletTokenPayload = {
paymentMethod: { type: "WALLET" },
userFlow: "PUSH_MESSAGE",
customer: { customerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." },
// ... other fields
};
// WALLET with msisdn and redirect
const walletTokenPayload = {
paymentMethod: { type: "WALLET" },
userFlow: "WEB_REDIRECT",
returnUrl: "vipps-chat://payment-complete?session_id=cs-ABC123XYZ",
// ... other fields
};
// CARD -> paymentMethod.type: "CARD", no customer field needed
const cardPayload = {
paymentMethod: { type: "CARD" },
userFlow: "WEB_REDIRECT",
returnUrl: "vipps-chat://payment-complete?session_id=cs-ABC123XYZ",
// ... other fields
};
NOTE: For
WEB_REDIRECTflows, thereturnUrlmust be exactly the same as provided by the platform, to ensure the customer is successfully redirected back to the platform after payment approval.
Error handling
Businesses MUST follow
UCP Checkout error handling: return
error messages in the checkout messages array only when the platform or buyer can act on them.
The severity field declares who resolves the error:
recoverable— The platform can fix via API (e.g. correct payment data and retry). Use for invalid instrument data the platform can correct.requires_buyer_input— The business requires input that cannot be provided via API (e.g. buyer must install or link a wallet). Contributes tostatus: requires_escalation; platform should hand off viacontinue_url.
Do not return UCP error messages for implementation or backend errors on the business side (e.g. merchant misconfiguration, auth failure, internal errors). The platform cannot resolve these; the checkout should be discarded or set to a terminal state. The business may handle such errors server-side (retries, alerts, logging) at their discretion.
Map
ePayment API errors
(RFC 7807, extraDetails error code when present) using the table below. If the error does not
appear in the mapping, treat it as a business-side/implementation error: do not surface it as a
UCP message to the platform; handle server-side and treat the checkout as non-recoverable.
Note: This mapping covers only payment creation and UCP payment-handling flows.
| HTTP Status | Vipps Error Code | Context (Vipps title / description) | UCP Error Code | UCP Severity | User-Facing Message |
|---|---|---|---|---|---|
| 400 | — | Request body validation (no error code) | invalid_payment_data | recoverable | The payment request contains invalid data. Please verify and try again. |
| 400 | 4050 | Invalid CustomerToken | invalid_customer_token | recoverable | The provided customer information is invalid. Please validate and try again. |
| 400 | 4070 | Invalid phone number | invalid_phone | recoverable | The provided phone number is invalid. |
| 400 | 7010 | Customer not found | wallet_not_found | recoverable | No wallet account found for the provided identifier. Please try a different payment method. |
For wallet_not_found and invalid_phone, the business has no continue_url to redirect to
(the buyer cannot “fix” the wallet link in business UI). The platform should treat these as
recoverable—e.g. offer another payment method such as CARD, or prompt the user to try a
different identifier.
Important: Error messages returned to platforms should be opaque and user-friendly. Do not expose internal API error details. Log detailed errors server-side for debugging.
Example UCP Error Response (e.g. for 7010 Customer not found):
{
"messages": [
{
"type": "error",
"code": "wallet_not_found",
"severity": "recoverable",
"content": "No wallet account found for this phone number. Please try a different payment method.",
"path": "$.payment.instruments[0]"
}
]
}
Platform integration
Platform prerequisites
Before using this handler, Platforms MUST complete:
- If applicable: Register with merchant to allow to call their
checkout_sessionsendpoint. This could involve getting an access token - Obtain a valid customer identifier based on the credential type:
- MSISDN: Capture the user's MSISDN registered with the wallet. A suggestion is to implement the Login Product in your application.
- TOKEN: Obtain a customer token from an authenticated session.
Prerequisites Output:
| Field | Description |
|---|---|
merchant.access_token | Access token to call merchant (if applicable) |
vippsUser.msisdn | User MSISDN registered on their wallet (for MSISDN credential) |
vippsUser.customerToken | Encoded customer token from authenticated session (for TOKEN credential) |
Payment protocol
Platforms MUST follow this flow to acquire a payment instrument:
Step 1: Discover handler
The Platform identifies com.vippsmobilepay.pay.payment_handler in the business's
payment.handlers array.
{
"id": "vipps_mobilepay",
"name": "com.vippsmobilepay.pay.payment_handler",
"version": "2026-01-23",
"config": {
"merchant_serial_number": "123456",
"environment": "PRODUCTION"
}
}
Step 2: Choose payment method and gather required data
The Platform MUST determine which payment method to use based on whether the customer has a Vipps MobilePay wallet:
Option A: WALLET (User has wallet)
For users with a Vipps MobilePay wallet, use the WALLET instrument. The Platform MUST obtain a customer identifier:
- MSISDN: Prompt the user for their MSISDN registered with Vipps MobilePay. Use the Login Product to authenticate and retrieve the MSISDN.
- TOKEN: Use an encoded customer token obtained from an authenticated session.
Option B: CARD (User does not have wallet)
For users without a wallet identity, use the CARD instrument. The Platform MUST provide:
- return_url: The URL to redirect the user back to after payment. This can be:
- Universal Link: e.g.,
vipps-chat://payment-complete?session_id=...(for native mobile apps) - Website URL: e.g.,
https://chat.vippsmobilepay.com/payment-complete?session_id=...(for web-based platforms)
Tip: Include the checkout session ID in the return URL so the platform can resume the checkout flow after redirect.
Step 3: Complete checkout
The Platform submits the checkout with the constructed payment instrument.
Example with MSISDN credential:
POST /checkout-sessions/{checkout_id}/complete
Content-Type: application/json
{
"payment_data": {
"id": "instr_vipps_1",
"handler_id": "vipps_mobilepay",
"type": "WALLET",
"credential": {
"type": "MSISDN",
"value": "4712345678"
}
},
"risk_signals": {
"device_id": "device_abc123",
"session_id": "session_xyz789",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0 ...",
"accept_language": "nb-NO"
}
}
Example with TOKEN credential:
POST /checkout-sessions/{checkout_id}/complete
Content-Type: application/json
{
"payment_data": {
"id": "instr_vipps_1",
"handler_id": "vipps_mobilepay",
"type": "WALLET",
"credential": {
"type": "TOKEN",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
Example with CARD instrument (No wallet required):
POST /checkout-sessions/{checkout_id}/complete
Content-Type: application/json
{
"payment_data": {
"id": "instr_card_1",
"handler_id": "vipps_mobilepay",
"type": "CARD",
"return_url": "vipps-chat://payment-complete?session_id=cs-ABC123XYZ"
}
}
Step 4: Handle async response
Both WALLET and CARD flows return status: complete_in_progress. The Platform MUST handle the
response based on the message code:
WALLET Response (pending user approval in Vipps app):
{
"id": "cs-ABC123XYZ",
"status": "complete_in_progress",
"messages": [
{
"type": "info",
"code": "payment_pending_user_approval",
"content": "A payment request has been sent to your Vipps Mobilepay app. Please open and approve the payment in the app to complete your order."
}
],
"payment": {
"state": "pending_approval",
"expires_at": "2026-01-26T12:35:00Z"
}
}
CARD Response (redirect required):
{
"id": "cs-ABC123XYZ",
"status": "complete_in_progress",
"continue_url": "https://api.vipps.no/checkout/v3/session/...",
"messages": [
{
"type": "info",
"code": "payment_redirect_required",
"content": "Please complete your payment on the payment page."
}
],
"payment": {
"state": "pending_redirect",
"expires_at": "2026-01-26T12:35:00Z"
}
}
Platform behavior for WALLET (payment_pending_user_approval):
- Inform the user: Display the message content (e.g., "Please check your app to approve the payment")
- Poll for completion: Call
GET /checkout-sessions/{checkout_id}every 2-5 seconds - Check status: Continue polling until
statusiscompletedor an error state - Handle timeout: If
payment.expires_atpasses, inform user the payment expired
Platform behavior for CARD (payment_redirect_required):
Per the
UCP Continue URL specification, the
continue_url enables checkout handoff from platform to business UI:
- Redirect the user: Open the
continue_urlin a browser or webview to hand off to the payment page - User completes payment: User enters card details on the payment page
- Handle return: After payment, we redirect user back to the
return_urlprovided in the instrument - Resume checkout: Extract session ID from URL and poll
GET /checkout-sessions/{id}for final status (completedor error)
Step 5: Poll for completion
GET /checkout-sessions/{checkout_id}
Response when payment is approved:
{
"id": "cs-ABC123XYZ",
"status": "completed",
"order": {
"id": "order-789",
"reference": "ORD-2026-001234",
"created_at": "2026-01-26T12:32:15Z"
},
"messages": [
{
"type": "info",
"code": "payment_approved",
"content": "Payment approved. Your order has been placed."
}
]
}
Response when user rejects:
{
"id": "cs-ABC123XYZ",
"status": "incomplete",
"messages": [
{
"type": "error",
"code": "payment_rejected",
"severity": "requires_buyer_input",
"content": "Payment was declined. Please try again or choose a different payment method."
}
]
}
Polling best practices
| Recommendation | Details |
|---|---|
| Poll interval | 2-5 seconds initially, increase if many retries |
| Max duration | Stop after payment.expires_at + small buffer |
| Exponential backoff | Consider increasing interval after 30 seconds |
| User feedback | Show progress indicator while polling |
Security considerations
| Requirement | Description |
|---|---|
| TLS Required | All communication with our APIs MUST use TLS 1.2 or higher. Connections using older TLS versions will be rejected. |
| MSISDN Validation | For MSISDN credentials, the value MUST be validated to ensure it is a valid phone number format (digits only, 7-15 digits, no leading zero) before sending to the API. |
| TOKEN Validation | For TOKEN credentials, the value MUST be validated to ensure it is a non-empty string. Token validity is verified by the API. |
| Data Residency | All payment data is processed within the EEA (European Economic Area). PII is handled according to GDPR requirements. |
References
- Handler Spec:
https://ucp.vippsmobilepay.com/ucp/2026-01-23/vipps_mp_payment_handler - Config Schema:
https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/wallet_payment_handler.json - Instrument Schema:
https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/payment_instrument.json - Credential Schema:
https://ucp.vippsmobilepay.com/ucp/2026-01-23/schemas/wallet_payment_credential.json