Recurring API guide
The Vipps MobilePay Recurring API delivers recurring payment functionality for a merchant to create a payment agreement with a customer for fixed interval payments. When the agreement is accepted by the end user, the merchant can send charges that will be automatically processed on the due date.
Important: The Recurring v2 API will be phased out and will not be available from 1 November 2023. See the migration guide for an overview of what has changed from v2 to v3.
Requirements
Important: The Recurring API requires additional compliance checks (more than what is required for the ePayment API), as required by Finanstilsynet (the financial authorities).
To get access to the Recurring API in production, please order Recurring Payments. It is the same order form as Payment Integration (ePayment API). You will then get a new sales unit (MSN) that can be used for recurring payments.
If you need to use an existing sales unit that already has access to the eCom/ePayment API for the Recurring API too, please contact your KAM or customer service. Please have this information ready:
- Estimated total annual turnover for the sales unit. Example: 100 MNOK.
- Percentage of the payment volume that will be through recurring payments. Example: 50 MNOK.
- The length of the agreements. Example: Annual and monthly.
- The distribution (in %) of the lengths. Example: 80 % annual, 20 % monthly
Please note: You can check if you have access to the Recurring API:
- As a merchant: Check your sales unit(s) on portal.vippsmobilepay.com, in the Developer section.
- As a partner: Check the sales unit(s) with the Management API.
API version: 3.0.0.
Terminology
Term | Description |
---|---|
Agreement | A payment subscription with a set of parameters that a customer agrees to. |
Charge | A single payment within an agreement. |
Idempotency | The property of endpoints to be called multiple times without changing the result after the initial request. |
Flow
The overall flow is:
- The merchant creates a draft agreement and proposes it to the customer via Vipps MobilePay.
- The customer approves the agreement in Vipps MobilePay.
- The customer can find a full overview of the agreement in Vipps MobilePay, including a link to the merchant's website.
- The merchant sends a charge request to Vipps MobilePay at least two days before due date
- If the agreement is active, Vipps MobilePay authorizes the charge.
- Charge will be processed on due date.
This diagram shows a simplified flow:
See the How it works guides for details.
Call by call guide
There are two happy-flows based on how the sales unit is set up:
One for "direct capture" and one for "reserve capture".
This is specified with the transactionType
, and for "direct capture"
the sales unit must be configured for this by Vipps MobilePay.
For more details, see Knowledge base: Reserve and capture.
Please note: Vipps MobilePay will only perform a payment transaction on an agreement that
the merchant has created a charge for with the POST:/recurring/v3/agreements/{agreementId}/charges
endpoint.
You can also manage charges and agreements.
Direct capture
For a "transactionType": "DIRECT_CAPTURE"
setup, the normal flow would be:
- Create a (draft) agreement using the
POST:/recurring/v3/agreements
endpoint. The user can now confirm the agreement in Vipps MobilePay (the app). See Create a new agreement. - The user approves the agreement in Vipps MobilePay: This will result in a capture(or reserve) of the initial charge (if one was defined in the first step). See Initial charge.
- Retrieve the agreement by calling the
GET:/recurring/v3/agreements/{agreementId}
endpoint. See Retrieve an agreement. Please note: At this point the agreement will beACTIVE
if the user completed step 2. - All future charges can be created by using the
POST:/recurring/v3/agreements/{agreementId}/charges
endpoint. For direct capture you must set"transactionType": "DIRECT_CAPTURE"
. See Create a charge. Based on thedue
set in the request, we will try to process the charge on that day. If for some reason, a charge fails to be processed, we will retry for the number of days specified by theretryDays
value. We strongly recommend at least two days retry:retryDays: 2
.
Reserve capture
Please note: Reserve capture on recurring charges is available in the Recurring API v3. In the API V2, reserve capture is only available on initial charges.
For a "transactionType": "RESERVE_CAPTURE"
setup, the normal flow would be:
- Create a (draft) agreement using the
POST:/recurring/v3/agreements
endpoint. The user can now confirm the agreement in the Vipps or MobilePay app. See Create a new agreement. - The user approves the agreement in Vipps MobilePay: This will result in a capture(or reserve) of the initial charge (if one was defined in the first step). See Initial charge.
- Retrieve the agreement by calling the
GET:/recurring/v3/agreements/{agreementId}
endpoint. See Retrieve an agreement. Please note: At this point the agreement will beACTIVE
if the user completed step 2. - All future charges can be created by using the
POST:/recurring/v3/agreements/{agreementId}/charges
endpoint. For reserve capture you must set"transactionType": "RESERVE_CAPTURE"
. See Create a charge. Based on thedue
set in the request, we will try to process the charge on that day. If the charge is processed successfully, the status will beRESERVED
. If for some reason, a charge fails to be processed, we will retry for the number of days specified by theretryDays
value. We recommend at least 2 days retry. - If there is a product that is shipped to the customer, the charge should be captured at this point.
Capture the charge by calling the
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture
endpoint.
API endpoints
See Authentication and authorization.
See the Quick start guide for en easy way to test the API.
Authentication and authorization
All API calls are authenticated with an access token and an API subscription key. See Get an access token, for details.
Use the standard HTTP headers for all requests.
Idempotency Key header
V3 API only
The Idempotency-Key
header must be set in any request that creates or modifies a resource (POST
, PUT
, PATCH
or DELETE
).
This way, if a request fails for any technical reason, or there is a networking issue, it can be retried with the same Idempotency-Key
. The idempotency key should prevent operations and side effects from being performed more than once, and you should receive the same response as if you only sent one request.
Important: If the response is a client-error (4xx), you will continue to get the same error as long as you use the same idempotency key, as the requested operation is not retried.
Important: If you reuse an idempotency key on a different request, you will get a 409 CONFLICT.
See the Idempotency header for more details.
Pagination
The pageNumber
and pageSize
query parameters can be used for paginated requests.
orderId recommendations
An optional and recommended orderId
field can be set in the POST:/recurring/v3/agreements/{agreementId}/charges
request.
{
"amount": 49900,
"description": "Premier League subscription",
"due": "2030-12-31",
"transactionType": "DIRECT_CAPTURE",
"retryDays": 5,
"orderId": "acmeshop123order123abc"
}
Important: If the orderId
is provided:
- The value of the
orderId
is used for all instances ofchargeId
- The
orderId
(andchargeId
) is used for all subsequent identification of the charge. - The
orderId
is used in the settlement files if noexternalId
is specified. - This
orderId
must be unique across all Recurring and eCom transactions for the givenmerchantSerialNumber
.
If the field is not provided, we will automatically create a unique ID
prefixed with chr-
: chr-xxxxxxx
(where each x is an alphanumeric character).
👉 Please read orderId / reference in the Knowledge base for detailed recommendations.
Agreements
An agreement is between the Vipps MobilePay user and the merchant. This payment agreement allows you to routinely charge the customer without requiring them to manually approve every time. See charges for more details.
Create an agreement
This is an example of a request body for the POST:/recurring/v3/agreements
call:
{
"phoneNumber":"4712345678",
"interval": {
"unit" : "MONTH",
"count": 1
},
"merchantRedirectUrl": "https://example.com/confirmation",
"merchantAgreementUrl": "https://example.com/my-customer-agreement",
"pricing": {
"amount": 49900,
"currency": "NOK"
},
"productDescription": "Access to all games of English top football",
"productName": "Premier League subscription"
}
Agreement fields and their usage in the Vipps or MobilePay app
pricing
: Price of the subscription.interval
: Describes how often the user will be charged.productName
: A short description of the subscription. Will be displayed as the agreement name in the Vipps or MobilePay app.productDescription
: More details about the subscription. Optional field.merchantAgreementUrl
: URL where you will send the customer to view/manage their subscription. See Merchant agreement URL for more details.
Please note: To create agreements with support for variable amounts on charges, see Recurring agreements with variable amount.
Agreements may be initiated with or without an initial charge.
The agreement price and the amount for the initial charge, is given in øre for NOK and DKK, and in cent for EUR.
The minimum amount in NOK and DKK is 100 øre. The minimum amount in EUR is 1 cent.
# | Agreement | Description |
---|---|---|
1 | Agreement starting now | Agreement with an initialcharge that uses DIRECT_CAPTURE will only be active if the initial charge is processed successfully |
2 | Agreement starting in future | Agreement without an initialcharge , or with initialcharge that uses RESERVE_CAPTURE , can be approved, but no payment will happen until the first charge is provided |
The response contains an agreementResource
, a vippsConfirmationUrl
and an agreementId
.
This agreementResource
is a complete URL for performing a
GET:/recurring/v3/agreements/{agreementId}
request.
The vippsConfirmationUrl
should be used to redirect the
user to the Vipps MobilePay landing page in a desktop flow (with https://
),
or to Vipps MobilePay in a mobile flow (with vipps://
), where the
user can then approve the agreement.
See landing page from Knowledge base, for more details.
Please note: If payment should be required to activate an agreement, you need to specify an initial charge. If you are dealing with physical goods, this should be a RESERVE_CAPTURE, but for digital goods where the customer instantly gains access, DIRECT_CAPTURE might be easier to manage. See Initial charge.
Accept an agreement
The POST:/recurring/v3/agreements
endpoint will return the following JSON structure.
{
"vippsConfirmationUrl": "https://api.vipps.no/dwo-api-application/v1/deeplink/vippsgateway?v=2/token=eyJraWQiOiJqd3RrZXkiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJmMDE0MmIxYy02YjI",
"agreementId": "agr_TGSuPyV"
}
The vippsConfirmationUrl
should be used to redirect the user to the Vipps MobilePay landing
page. The user can then confirm their identity and receive a prompt to accept the
agreement within Vipps MobilePay.
If the payment is initiated in a native app, it is possible to explicitly force
a vipps://
URL by sending the isApp
parameter in the initiate call:
"isApp": false
: The URL ishttps://
, which handles everything automatically for you. The phone's operating system will know, through "universal linking", that thehttps://api.vipps.no
URL should open the Vipps or MobilePay app, and not the default web browser. Please note: In some cases, this requires the user to approve that Vipps MobilePay is opened, but this is usually only the first time."isApp": true
: The URL is for a deeplink, for forced app-switch to Vipps, withvipps://
. Please note: In our test environment (MT), the scheme isvippsMT://
If the user does not have Vipps MobilePay installed:
"isApp":false
: The Vipps MobilePay landing page will be shown, and the user can enter a phone number and pay on a device with Vipps MobilePay installed."isApp": true
: The user will get an error message saying that the link can not be opened.
For more details, see Knowledge base: Deep link flow.
Agreement activation or rejection
If the user accepts or rejects the agreement the user will be redirected back to themerchantRedirectUrl
.
Activation of the agreement is not guaranteed to be finished by the time the user is redirected back to the merchantRedirectUrl
.
The agreement could still have the status PENDING
.
Also, if the user closes the Vipps or MobilePay app before the redirect is done, the merchantRedirectUrl
will not be used.
Therefore, it is important to actively check the agreement's status with the
GET:/recurring/v3/agreements/{agreementId}
endpoint instead of relying on the redirect to the merchantRedirectUrl
.
See current rate limits for more details about polling.
Once a final status (ACTIVE
, EXPIRED
or STOPPED
) is returned by the API, the agreement can be updated in your system.
Stopping an agreement in the Vipps MobilePay app
Users can administer their agreements either in the Vipps MobilePay app or through the merchant. When the user stops their payment agreement in the app, the merchant must ensure the status of the subscription on their systems is updated and has the correct state.
- Merchants must listen to the
recurring.agreement-stopped.v1
webhook event. - The
actor
field in the payload will indicate if it was stopped by the merchant or the user. - In addition, merchants can poll and get the status of the agreement when needed.
- Already reserved charges are not cancelled. Merchants can either capture or cancel the reservation.
- All due/pending charges are cancelled.
Merchant agreement URL
The merchantAgreementUrl
is a link to the customer's account page on your website, where they
can manage the agreement (e.g., change, pause, cancel the agreement).
The URL is opened in the standard web browser.
Important: The integrator must implement such functionality for the
customer to manage the agreement in their system.
It is the integrator's responsibility to make sure the merchantAgreementUrl
in the agreement works for the user.
We don't have any specific requirements for the security of the page, other than using HTTPS, but strongly recommend using Login, so the user does not need a username or password, but is logged in automatically through Vipps MobilePay. See the Login API for more details.
Intervals
The interval defines how often the user will be charged.
When the charges on the agreement follow a particular frequency, set the interval unit YEAR
, MONTH
, WEEK
, or DAY
and frequency as a count. The count can be any number between 1 and 31.
Example for a bi-weekly subscription:
{
"interval": {
"unit": "WEEK",
"count": 2
}
}
Users can be charged the full amount every two weeks, regardless of the day in the week. (E.g. First charge can be due on Wednesday of week 1 and the second charge can be due on Monday of week 3).
Example for a quarterly subscription
{
"interval": {
"unit": "MONTH",
"count": 3
}
}
Users can be charged the full amount every third month, regardless of the day in the month. (E.g. First charge can be due on 05.01.2023 and second on 02.04.2023)
Examples for a yearly subscription
{
"interval": {
"unit": "YEAR",
"count": 1
}
}
OR
{
"interval": {
"unit": "MONTH",
"count": 12
}
}
Users can be charged the full amount once every year, regardless of the day in the year. (E.g. First charge can be due on 02.06.2022 and second charge on any day in 2023, for example on 01.01.2023)
Example for a subscription every 30th day:
{
"interval": {
"unit": "DAY",
"count": 30
}
}
Users can be charged the full amount once every 30 days, regardless of the day in the month. (E.g. First charge can be due on 12.06.2022 and second charge on 04.07.2022)
For the pay-per-use pricing model, where the charges on the agreement have no periodicity, the interval field must be omitted. For example, a user can subscribe to an agreement for e-scooter rental, where the user is charged every time they rent a scooter, without any interval involved.
Example for a pay-as-you-use subscription:
{
"pricing": {
"type": "VARIABLE",
"suggestedMaxAmount": 3000,
"currency": "NOK"
},
"merchantRedirectUrl": "https://example.com/redirect",
"merchantAgreementUrl": "https://example.com/agreement",
"phoneNumber": "4712345678",
"productName": "My e-scooter Rental"
}
Pricing representation
Legacy pricing
Available for Vipps and MobilePay.
LEGACY
is the default type.
pricing.amount
should represent the price that the user will pay every period.
The amount
of a charge is flexible and does not have to match the price
of the agreement.
A limit is in place however, which is 5 times the agreement price
.
For example, the agreement below has price
of 1000 NOK
. The maximum allowed charge would be 5000 NOK
. If this limit becomes a
hindrance the agreement price
can be updated. Although there is technically no limit to what you can update the price to, we strongly recommend that you are as user-friendly as possible.
Make sure the user understands any changes and are provided with updated information.
Here is a truncated example of request body for the POST:/recurring/v3/agreements
endpoint:
{
"pricing": {
"type": "LEGACY",
"amount": 100000,
"currency": "NOK"
},
"interval": {
"unit" : "MONTH",
"count": 1
},
"...": "..."
}
Variable amount pricing
Available for Vipps and MobilePay.
Recurring with variable amounts offer merchants a way to charge users a different amount each payment, as long as the amount is lower than the user's specified max amount.
This provides clarity for the customer and ensures that they know the maximum price they will have to pay for the product or service.
To create a variable amount agreement, use the VARIABLE
type in Pricing
.
With VARIABLE
pricing, you no longer specify a price, but a suggestedMaxAmount
for the user.
This field should be set to what the maximum price could be each payment.
This suggestedMaxAmount
is presented to the user together with a list of auto generated amount suggestions that is created by Vipps MobilePay.
The suggestedMaxAmount
is however pre-selected for the user.
The user chooses a max amount themselves when accepting the agreement, but we
recommended the user to choose the same amount as suggestedMaxAmount
. The max
amount can at any time be changed by the user. What the user has picked as their
max amount will be available in the GET agreement
response. Its recommended
that when you set the suggestedMaxAmount
, that you set a realistic amount -
as setting it to unrealistic amounts might scare off the user when they accept
the agreement.
See the How Recurring works with variable amount for details.
Here is a truncated example of request body for the POST:/recurring/v3/agreements
endpoint:
{
"pricing": {
"suggestedMaxAmount": 200000,
"currency": "NOK",
"type": "VARIABLE"
},
"interval": {
"unit" : "MONTH",
"count": 1
},
"...": "..."
}
Restrictions when using variable amount
-
There are limits for each currency for the
suggestedMaxAmount
:- NOK: 20 000 kr
- DKK: 300 000 kr
- EUR: 2 000 Euro
-
Campaign
can't be used when the agreement hasvariableAmount
.
The user will be presented with the variable agreement in the Vipps, where they can change the max amount they allow to be charged each interval. See the How Recurring works with variable amount for details.
Please note: The auto generated list is based on the suggestedMaxAmount
and can't be changed by the merchant individually.
It will however change if suggestedMaxAmount
changes, which can be done in the PATCH agreement
endpoint.
Retrieving the agreement shows the maxAmount
picked by the user
GET:/recurring/v3/agreements/{agreementId}
response:
{
"id": "agr_Yv2zYk3",
"start": "2021-06-18T19:56:22Z",
"stop": null,
"status": "ACTIVE",
"productName": "Power company A",
"pricing": {
"type": "VARIABLE",
"suggestedMaxAmount": 500000,
"maxAmount": 1800000,
"currency": "NOK"
},
"productDescription": "Access to subscription",
"interval": {
"unit": "MONTH",
"count": 1,
"text": "hver måned"
},
"campaign": null,
"sub": null,
"userinfoUrl": null
}
Change suggestedMaxAmount
It's possible to change the suggestedMaxAmount
on the agreement by calling the update agreement endpoint with the PATCH:/recurring/v3/agreements/{agreementId}
request below.
{
"suggestedMaxAmount": 300000
}
Please note: The user will not be alerted by this change.
Create charge
The amount of the charge/charges in the interval can't be higher than the suggestedMaxAmount
or maxAmount
field, depending on which is highest.
Examples:
- If
suggestedMaxAmount
is set to 5000 kr andmaxAmount
chosen by the user is 2000 kr, then the charge amount can't be higher than 5000 kr - If
suggestedMaxAmount
is set to 5000 kr andmaxAmount
chosen by the user is 7000 kr, then the charge amount can't be higher than 7000 kr
Charge amount higher than the user's max amount
If the amount of a charge is below (or equal) the suggestMaxAmount
but above the user's maxAmount
, the charge will be set
to DUE
and the user will be notified and encouraged to alter the max amount to a higher amount.
If the user does not update their maxAmount
to the same or a higher amount than the charge, it will fail when due
+ retryDays
is reached, and
the status will be FAILED
.
The user will also see a failure description on the charge in the Vipps or MobilePay app.
See the How Recurring works with variable amount for details.
Flexible pricing
Available for MobilePay only.
There is no set maximum limit for the price of product or service. This pricing leaves room for flexibility and is recommended if the customer pays a varied amount. For example, an electricity provider charges a varied amount, dependent on the customer's usage of electricity.
Here is a truncated example of request body for the POST:/recurring/v3/agreements
endpoint:
{
"pricing": {
"type": "FLEXIBLE",
"currency": "NOK"
},
"interval": {
"unit" : "MONTH",
"count": 1
},
"...": "..."
}
Initial charge
Please note: If the subscription is cheaper in the beginning than the normal price later, use
campaigns in combination with initial charge.
If you use initialcharge
alone for campaigns, users will be confused by how it appears in Vipps MobilePay,
as it looks like the full price period starts immediately.
Initial charge will be performed if the initialcharge
is provided when
creating an agreement. If there is no initial charge, don't send initialcharge
when creating the new agreement.
Unlike regular (or RECURRING
) charges, there is no price limit on an initialCharge
.
This allows for products to be bundled with agreements as one transaction
(for example, a phone). The user will be clearly informed when an initialCharge
is included in the agreement they are accepting.
See Charge Titles for explanation of how the charge description is shown to the user.
The initial charge has two forms of transaction, DIRECT_CAPTURE
and RESERVE_CAPTURE
.
DIRECT_CAPTURE
processes the payment immediately, while RESERVE_CAPTURE
reserves the payment for capturing at a later date. See:
What is the difference between "Reserve Capture" and "Direct Capture"
in the FAQ.
RESERVE_CAPTURE
must be
used when selling physical goods bundled with an agreement - such as a phone
when subscribing to an agreement.
This example shows the same agreement as above, with an initialCharge
of 499 NOK:
{
"phoneNumber": "4712345678",
"initialCharge": {
"amount": 49900,
"description": "Premier League subscription",
"transactionType": "DIRECT_CAPTURE"
},
"interval": {
"unit" : "MONTH",
"count": 1
},
"merchantRedirectUrl": "https://example.com/confirmation",
"merchantAgreementUrl": "https://example.com/my-customer-agreement",
"pricing": {
"amount": 49900,
"currency": "NOK"
},
"productDescription": "Access to all games of English top football",
"productName": "Premier League subscription"
}
Change the transactionType
field to RESERVE_CAPTURE
to reserve the initial charge.
{
"initialCharge": {
"transactionType": "RESERVE_CAPTURE",
"amount": 19900,
"description": "Phone"
}
}
A reserved charge can be captured with the
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture
endpoint
when the product is shipped.
Retrieve an agreement
A newly created agreement will be in status PENDING
for 10 minutes before it expires.
If the customer approves the agreement, and the initialCharge
(if provided) is successfully
processed, the agreement status will change to ACTIVE
.
The approved agreement is retrieved from the
GET:/recurring/v3/agreements/{agreementId}
endpoint
with "status":"ACTIVE"
when the customer has approved the agreement. It is important to keep retrieving the agreement until the status is ACTIVE
, STOPPED
or EXPIRED
.
See Agreement states.
This is an example response from a call to the
GET:/recurring/v3/agreements/{agreementId}
endpoint:
{
"id": "agr_ADbq4JK",
"created": "2018-08-22T12:59:56Z",
"start": "2018-08-22T13:00:00Z",
"stop": null,
"status": "ACTIVE",
"productName": "Premier League subscription",
"productDescription": "Access to all games of English top football",
"pricing": {
"type": "LEGACY",
"amount": 49900,
"currency": "NOK"
},
"interval": {
"unit" : "MONTH",
"count": 2,
"text": "every 2 months"
},
"campaign": null,
"merchantAgreementUrl": "https://www.merchant.no/subscriptions/1234/",
"uuid": "6080c099-d7f2-43ef-a82b-2991ccc3a239",
"countryCode": "NO"
}
Campaigns
A campaign in Recurring is a period where the price is lower than usual, and this is communicated to the customer with the original price shown for comparison. Campaigns cannot be used in combination with variable amount.
There are 3 different campaign types (just in V3 API version): price campaign, period campaign, event campaign. See more about the different campaign types in the table below.
Campaign types | Description | Example |
---|---|---|
price campaign | Different interval price until specified date. Same interval as agreement. | 1kr every week until 2022-12-25T00:00:00Z and then 50kr every week |
period campaign | A set price for a given duration. A duration is defined by a number of periods (DAY, WEEK, MONTH, YEAR) | 10 weeks for 1kr and then 349kr every month |
event campaign | A set price until a given event date with a text describing the event | 1kr until Christmas and then 349kr every month |
In order to start a campaign, the campaign
field has to be added to the agreement draft body in the
POST:/recurring/v3/agreements
call.
Product description guidelines for agreements with campaigns
We do not recommend you to use Product Description
for agreements with a campaign.
We see that the user experience is not optimal when a lot of text is "squeezed" in the purple bubble displaying an agreement.
Price campaign
{
"campaign": {
"type": "PRICE_CAMPAIGN",
"end": "2022-12-25T00:00:00Z",
"price": 100
}
}
Field | Description |
---|---|
type | The type of the campaign |
price | The price that the customer will pay for each interval during the campaign |
end | The end date of the campaign |
Period campaign
{
"campaign": {
"type": "PERIOD_CAMPAIGN",
"price": 100,
"period": {
"unit": "WEEK",
"count": 10
}
}
}
Field | Description |
---|---|
type | The type of the campaign |
price | The price that the customer will pay for the period of the campaign |
period | The period where the campaign price is applied. Consists of a Unit and a Count , for example: { "unit": "MONTH", "count": 1 } |
Event campaign
{
"campaign": {
"type": "EVENT_CAMPAIGN",
"price": 100,
"eventDate": "2022-09-01T00:00:00Z",
"eventText": "To the end of august"
}
}
Field | Description |
---|---|
type | The type of the campaign |
price | The price that the customer will pay until the event date |
eventDate | The date of the event marking the end of the campaign |
eventText | The event text to display to the end user |
Please note: We recommend starting the event text with lowercase for better user experience. See example below.
Charges
An agreement has payments, called charges.
orderId
and externalId
The optional orderId
-field will override the automatically generated chargeId
for the charge if specified,
and will be used for all subsequent identification of the charge, and in settlement reports unless an externalId
is specified.
The externalId
-field is a more flexible alternative to orderId
which does not override the chargeId
, but will be used in settlement reports.
This means that if both orderId
and externalId
are specified,
the orderId
will override chargeId
and be used for all subsequent identification of the charge,
while the externalId
will be used in settlement reports.
As externalId
does not have the same requirements for uniqueness as orderId
,
it is important that you understand how this will impact your settlements before using it.
Also see Recommendations for reference
and orderId
Charge type
There are two types available for a charge. RECURRING
and UNSCHEDULED
.
If charge type is not specified on charge creation, type defaults to RECURRING
.
Recurring charge
A recurring charge is a payment that is set up to occur on a specific due date. The charge will be processed at the due date, and retries are attempted according to the number of retryDays
specified.
We recommend using this type of charge for pre-planned payments that occur on a regular schedule (based on the agreement interval).
{
"amount": 49900,
"transactionType": "DIRECT_CAPTURE",
"description": "October",
"due": "2018-09-01",
"retryDays": 5,
"type": "RECURRING"
}
Unscheduled charge
To be able to create UNSCHEDULED
charges, ask to be added to the allow list. Please email us at
developer@vippsmobilepay.com
(or use your Slack channel if you have one) and include all the following information:
- Merchant serial number which needs to be added to the allow list
- A detailed summary of the user journey where you will use unscheduled charge. We will review the summary and add the merchant serial number to the allow list
If your integrations are handled by a partner, also remember to check with them on whether they support unscheduled charges.
An unscheduled charge is sporadic or one-time payment that do not follow a set schedule.
We recommend using this type of charge for a one-time purchase or a one-time service fee in relation to the agreement.
Unscheduled charge will be processed asynchronously after creation. It supports both the transaction types, DIRECT_CAPTURE
and RESERVE_CAPTURE
. If the charge is successful, recurring.charge-reserved
or recurring.charge.captured
event is sent (depending on the transaction type
of the charge).
If the charge fails because of an invalid payment method, recurring.charge.failed
event will be sent and the user is notified that the payment method linked to the agreement needs to be updated.
Use Case Examples:
Unscheduled charges are used in scenarios where payments are not planned ahead or when the total amount depends on certain factors. Here are some common use cases for unscheduled charges:
- On-Demand Ride Services: Charging customers for rides once they are completed, based on distance and time.
- Utility Services: Charging for electricity consumption after the usage is determined, such as charging for electric vehicle charging.
- Parking Services: Charging based on the duration of stay when a vehicle leaves a parking facility.
- Subscription Overages: Billing for overages in cloud storage or data usage beyond the subscription limit.
Please note: When unscheduled charges fail, we do not retry. It is the merchant or partner's responsibility to retry the unscheduled charges.
{
"amount": 49900,
"transactionType": "DIRECT_CAPTURE",
"description": "October",
"type": "UNSCHEDULED"
}
Due date
due
will define for which date a recurring charge will be performed.
This date has to be minimum two days (one day in the test environment) in the future and maximum two years in advance.
The minimum is set to two days because the user should be able to see the upcoming charge in the Vipps or MobilePay app.
All charges due
in 35 days or less are visible under the Payments tab in the Vipps or MobilePay app.
Example: If the charge is created on the 25th, the earliest the charge can be due is the 27th (25+2). This is so that the user can be informed about the upcoming charge. The user is only shown one charge per agreement, in order to not overwhelm the user when doing daily or weekly charges.
Please note: You can request to be put on a "one day minimum" allow list if
you have a need to be able to create charges that are DUE
1 day after being
created. This means that a charge can be created to be DUE
the next day.
Example: If the charge is created at any time on the 25th, the charge can be
due and processed at the 26th.
Transaction type
A charge has two forms of transaction, DIRECT_CAPTURE
and RESERVE_CAPTURE
.
Please note: RESERVE_CAPTURE
transaction type is only available in the V3 API.
DIRECT_CAPTURE
processes the payment immediately, while RESERVE_CAPTURE
reserves the payment for capturing at a later date. See
What is the difference between "Reserve Capture" and "Direct Capture"
in the FAQ for more details.
RESERVE_CAPTURE
must be used when selling physical goods or a need to provide access at a later point.
The advantage to using reserve capture is that you can release the reservation immediately:
- For a reserved payment, the merchant can make a /cancel call to immediately release the reservation and make it available in the customer's account.
- For a captured payment, the merchant must make a /refund call. It then takes a few days before the amount is available in the customer's account.
Charge descriptions
When charges are shown to users in the app, they will have a title, and a
description. The title of a charge is derived directly from
{agreement.productName}
whereas the description is set per charge, i.e.
{charge.description}
. For example, a charge on an agreement with product
name "Premier League subscription" with description "October" would look like
the following screenshot:
When the charge is completed (the money has been moved), the payment will
show up in the users' payment history. There, a charge from
Vipps MobilePay recurring payments will have a description with follow format
{agreement.ProductName} - {charge.description}
.
This is an example of a request body for the POST:/recurring/v3/agreements/{agreementId}/charges
call:
{
"amount": 49900,
"transactionType": "DIRECT_CAPTURE",
"description": "October",
"due": "2018-09-01",
"retryDays": 5
}
Please note: description
cannot be longer than 45 characters.
Create a charge
To create a charge use the POST:/recurring/v3/agreements/{agreementId}/charges
endpoint.
For agreements with VARIABLE_AMOUNT
pricing, see Recurring agreements with variable amount.
Create multiple charges
To create multiple charges with single request use the POST:/recurring/v3/agreements/charges
endpoint.
It is possible to create up to 2 000 charges in a single request.
We validate charges in two steps. First, we check them at the API level, and any failed charges are returned in the API response. Then, we schedule asynchronous creation, where charges are validated against different criteria. If a charge doesn't pass this validation, a webhook is sent with the event type "recurring.charge-creation-failed.v1." Learn more about Webhooks
Capture a charge
Capture payment allows the merchant to capture the reserved amount of a charge. The API allows for both a full amount capture and a partial amount capture (V3 API only)
The amount to capture cannot be higher than the reserved amount.
The description
text is mandatory and is displayed to the end user in the Vipps or MobilePay app.
Capture is done with the POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture
endpoint.
Please note: It is important to check the response of the /capture
call.
The capture is only successful when the response is HTTP 204 No Content
.
Do not capture before the product or service is provided to the customer, as per the capture regulations.
If it's not captured within the payment capture deadlines, it will be automatically cancelled.
Partial capture
Limited availability for MobilePay. You must inquire during onboarding or contact customer service.
V3 API only: Partial capture may be used in cases where a partial order is shipped or for other reasons. Partial capture can be called as many times as required while remaining reserved amount is available.
If one or more partial capture have been made, any remaining reserved amount will be automatically released after a few days. See FAQ: For how long is a payment reserved for more details.
If you cancel a charge that is PARTIALLY_CAPTURED
, the remaining funds on the charge will be released back to the customer
and the charge status will be set to CHARGED
.
Cancel a charge
You can cancel charges that are in the PENDING
, DUE
or RESERVED
state.
If you cancel a charge that is PARTIALLY_CAPTURED
, the remaining funds on the charge will be released back to the customer
and the charge status will be set to CHARGED
.
Please note: If you cancel an agreement, there is no need to cancel the charges that belong to the agreement. We will do this automatically for you.
A charge can be cancelled with the
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint.
Refund a charge
A charge can be refunded with the
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/refund
endpoint.
Charge attempts
Charge attempts are primarily made two times during the day: 07:00 and 15:00 UTC.
We may do extra attempts and/or change this without notice.
The processing of charges typically takes around one hour, however this varies, and we do not guarantee any time.
This is the same both for our production and test environment.
Subsequent attempts are made according to the retryDays
specified.
When a charge has reached its due
date, the status of the charge will be
DUE
until the charge is successful, for as long as the merchant has
specified with retryDays
. In other words, there will be no status updates
while we are attempting to charge.
Important: We do not "leak" the customers' information about insufficient funds, blocked cards, or other problems. Users are informed about all such problems in the Vipps or MobilePay app, which is the only place they can be corrected. The merchant's customer service should always ask the user to check in the app if a charge has failed.
Please note: Payments might get processed any time during the day (07:00 UTC - 23:59 UTC) due to special circumstances requiring it.
Please note: Since payments can be processed any time (07:00UTC - 23:59 UTC) it is advisable to fetch the charge at/after 00:00 UTC the day after the last retry day to be sure you get the last status.
Payment Batches Schedule:
We strongly recommend that merchants do not rely on the batch timings and design their systems. Due to various reasons both internal and external to Vipps MobilePay we cannot guarantee when the batches will be executed. The batch timings for each market are in respective local time zones.
For Norwegian Agreements:
- 9.00: Biggest batch with new charges
- 17.00: Retry batch
For Finnish Agreements:
- 3.00: Biggest batch with new charges
- 13.00, 18.00, 20.00, 22.00, and 23.00: Retry batches
For Danish Agreements:
- 2.00: Biggest batch with new charges
- 13.00, 18.00, 20.00, 22.00, and 23.00: Retry batches
Charge retries
We will retry the charge for the number of days specified in retryDays
.
The maximum number of retryDays
is 14.
This means that if the user's card has insufficient funds, the card has expired, the card is invalid, etc.: The user is notified and can correct the problem. We will make sure the user is able to pay.
We strongly recommend at least two days retry: retryDays: 2
.
The retryDays
are not tied to the agreement’s interval. This means that a charge
can be retried for a maximum of 14 days even though the next interval has started.
For example, an agreement with daily interval can have a charge retried for
multiple days, and it is possible to create new daily charges while others are
still retrying.
We don't provide details about each charge attempt to the merchant, but we do help the user to correct any problems in the Vipps or MobilePay app. This results in a very high success rate for charges.
The status of a charge will be DUE
while we are taking care of business,
from the due
date until the charge has succeeded, or until the
retryDays
have passed without a successful charge.
The final status will be CHARGED
or FAILED
.
See: Charge states.
Retrieve a charge
To retrieve a charge, we recommend using the GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint.
Please note: The endpoint GET:/recurring/v3/charges/{chargeId}
is not intended for automation.
There is a stricter rate limiting (See Rate limiting) on this endpoint because it is more expensive to fetch a charge without the agreementId.
Its purpose is to simplify investigations when the merchant lost track of which charge belongs to which agreement.
It should not be used as a substitute for the GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint.
Details on charges
The response from the GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint
contains the history of the charge and not just the current status.
It also contains a summary of the total of amounts captured, refunded and cancelled.
Truncated example of the response from the GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint:
{
"id": "chr_WCVbcA",
"status": "REFUNDED",
"amount": 1000,
"type": "RECURRING",
"transactionType": "RESERVE_CAPTURE",
"...": "...",
"summary": {
"captured": 1000,
"refunded": 600,
"cancelled": 0
},
"history": [
{
"occurred": "2022-09-14T10:31:15Z",
"event": "CREATE",
"amount": 1000,
"idempotencyKey": "e80bd8c6-3b83-4583-a49c-847021fcd839",
"success": true
},
{
"occurred": "2022-09-16T06:01:00Z",
"event": "RESERVE",
"amount": 1000,
"idempotencyKey": "chr-4assY8f-agr_FJw2Anb-ProcessPayment",
"success": true
},
{
"occurred": "2022-09-18T06:01:00Z",
"event": "CAPTURE",
"amount": 1000,
"idempotencyKey": "096b1415-2c77-4001-9576-531a856bbaf4",
"success": true
},
{
"occurred": "2022-09-20T06:01:00Z",
"event": "REFUND",
"amount": 600,
"idempotencyKey": "0bc7cc3b-fdef-4d24-b4fe-49b7da40d22f",
"success": true
}
]
}
List charges
All charges, including the optional initial charge, for an agreement can be retrieved with the
GET:/recurring/v3/agreements/{agreementId}/charges
endpoint.
Manage charges and agreements
It is the merchant's responsibility to manage and update charges and agreements, and to use the API to make sure everything is in sync.
Agreement states
# | State | Description |
---|---|---|
1 | PENDING | Agreement has been created, but not approved by the user in the Vipps or MobilePay app yet |
2 | ACTIVE | The agreement has been confirmed by the end user in the Vipps or MobilePay app and can receive charges |
3 | STOPPED | Agreement has been stopped, either by the merchant by the PATCH:/recurring/v3/agreements/{agreementId} endpoint, or by the user by cancelling or rejecting the agreement. |
4 | EXPIRED | The user did not accept, or failed to accept (due to processing an initialCharge ), the agreement in the Vipps or MobilePay app |
Update an agreement
A merchant can update an agreement by calling the
PATCH:/recurring/v3/agreements/{agreementId}
endpoint.
The following properties are available for updating:
{
"productName": "A new name",
"productDescription": "A new description",
"merchantAgreementUrl": "https://example.com/vipps-subscriptions/1234/",
"pricing": {
"amount": 25000,
"suggestedMaxAmount": 300000
},
"interval": {
"type": "RECURRING",
"period": {
"count": 1,
"unit": "MONTH"
}
}
}
Updating amount
is only possible for agreements with pricing.type:LEGACY
Updating suggestedMaxAmount
is only possible for agreements with pricing.type:VARIABLE
You can also update the type of the interval. For instance, to make a recurring agreement flexible,
you can change the interval to FLEXIBLE
, by sending the following payload:
{
"interval": {
"type": "FLEXIBLE"
}
}
Please note: As a PATCH
operation all parameters are optional. However,
when setting an agreement status to STOPPED
no other changes are allowed.
Attempts at changing other properties while stopping an agreement will result
in a 400 Bad Request
response.
Pause an agreement
Today unfortunately we do not have a pause/freeze status. This is something we are looking into. If there should be a pause in an agreement, like a temporary stop of a subscription: Simply do not create any charges during the pause. We recommend to use Agreement Description to communicate to the user that the agreement is paused/frozen.
We recommended not to set the agreement status to STOPPED
. STOPPED
agreements cannot be reactivated.
Stop an agreement
When a user notifies the merchant that they want to cancel a subscription or
service, the merchant must ensure that the status of the recurring agreement is
set to STOPPED
at a suitable time.
A merchant can stopped an agreement by calling the PATCH:/recurring/v3/agreements/{agreementId}
endpoint.
Request body for stopping an agreement:
{
"status": "STOPPED"
}
Stopping an agreement results in cancellation of any charges that are DUE/PENDING/RESERVED at the time of stopping it,
and it will not be possible to create new charges for a stopped agreement.
Note : Charges with status RESERVED
will not be cancelled if the stop of the agreement comes from the user side.
We recommend that the recurring agreement remains ACTIVE
for as long as the
user has access to the service.
For example; if the user cancels their subscription, but they are still able to
use the service until the end of the billing cycle, the agreement should only be
set to STOPPED
at the end of the billing cycle.
Since STOPPED
agreements cannot be reactivated, a benefit of waiting until
the "end of service" before setting the agreement status to STOPPED
is that
the merchant will be able to reactivate the user's subscription without having
to set up a new agreement.
Charge states
This table has all the details for the charge states returned by the
GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId}
endpoint:
State | Description |
---|---|
PENDING | The charge has been created, but is not yet be visible in the Vipps or MobilePay app. |
DUE | The charge is visible in the Vipps or MobilePay app and will be processed on the due date. |
PROCESSING | The charge is being processed right now. |
UNKNOWN | The charge status is unknown. This is usually very transient and will be resolved shortly. |
CHARGED | The charge has been successfully processed, and the available amount has been captured. |
FAILED | The charge has failed because of insufficient funds, no valid cards, etc. The Vipps or MobilePay app gives the user all possible opportunities to pay, including adding a new card, but does not provide the details to the merchant. |
REFUNDED | The charge has been refunded. Refunds are allowed up to 365 days after the capture date. |
PARTIALLY_REFUNDED | Part of the captured amount has been refunded. |
RESERVED | The charge amount has been reserved, and can now be captured POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture |
PARTIALLY_CAPTURED | Part of the reserved amount has been captured and the remaining amount as not been cancelled yet. If you do not plan on capturing the rest, you should cancel the remaining amount to release the funds to the customer. |
CANCELLED | The charge has been cancelled. |
IMPORTANT: We don't provide details about each charge attempt to the merchant, but we help the user to correct any problems in the Vipps or MobilePay app. This results in a very high success rate for charges.
Example charge flows
Scenario: Everything goes as it should: The user has money, and the charge is successful on the due
date:
PENDING
->DUE
(just for the one due day)->CHARGED
Scenario: The user does not have funds and retryDays = 0
:
PENDING
->DUE
->FAILED
Scenario: The user does not have funds on the due
date, retryDays = 10
, and has funds on the fifth day:
PENDING
->DUE
(for five days) ->CHARGED
Please note: Since charges are polled by the merchant, it is possible that
the charge status appears to "skip" a transition, e.g. moving directly from
PENDING
to CHARGED
, or even from PENDING
to REFUNDED
depending on your systems.
Charge failure reasons
When fetching a charge through the API, you can find two fields in the response
body to identify why the charge failed failureReason
and failureDescription
.
An example from a response:
{
"status": "FAILED",
"type": "RECURRING",
"failureReason": "user_action_required",
"failureDescription": "User action required"
}
Here is a list of possible values for failureReason
, their respective descriptions and possible actions that the user/merchant could take.
Reason | Description | Explanation | Action |
---|---|---|---|
user_action_required | User action required | Payment failed. Could be lack of funds, card is blocked for ecommerce, card is expired. If you want to send an email or similar to the user, you should encourage them to open the Vipps or MobilePay app and check the payment there to see why it is not paid. | User will get notified in their Vipps or MobilePay app and need to take action. This could be to add funds to the card or change the card on the agreement. |
charge_amount_too_high | Amount is higher than the users specified max amount | Amount is higher than the user's specified max amount. | The user has a lower maxAmount on the variableAmount agreement than the amount of the charge. The user must update their maxAmount on the agreement for the charge to be processed. |
non_technical_error | Non technical error | Payment failed. Could be that the user has deleted their Vipps MobilePay profile. | The user needs to take action in the app. |
technical_error | Technical error | Payment failed due to a technical error in Recurring or a downstream service. | As long as the charge is not in the status FAILED , we are retrying the payment. If the status is FAILED and the customer has not been charged by any other means, we recommend creating a new charge with a new due date. |
Notifications to users for failed charges
We notify the user in two ways, through push notifications and failure texts on the charge. When a charge fails to be processed, we send the user a push notification letting them know the charge failed to process. This push message is sent every time we try to process the charge, see Charge attempts for when the processing of charges happen.
Please refer to the table below for a summary of the push notification texts:
Event | Push message text (English) | Push message text (Norwegian) | Push message text (Danish) | Push message text (Finnish) |
---|---|---|---|---|
Insufficient funds | Make sure you have enough money. We'll try again later. | Pass på at du har nok penger. Vi prøver igjen litt senere. | Vær sikker på at du har penge nok på kortet. Vi prøver igen senere. | Tarkista, että tilillä on riittävästi katetta. Yritämme veloittaa maksua uudelleen myöhemmin. |
Invalid payment source (retryable) | The card is invalid, try another | Kortet er ugyldig, prøv et annet. | Kortet er ugyldigt, prøv et andet. | Kortti ei kelpaa, yritä toista korttia. |
Invalid payment source (not retryable) | The card is invalid, try changing it and contacting %s to continue the agreement. | Kortet er ugyldig, prøv å endre det og kontakt %s å fortsette avtalen. | Kortet er ugyldigt. Prøv at skifte kortet og kontakt %s for at fortsætte aftalen. | Kortti ei kelpaa. Vaihda korttia tai ota yhteyttä kauppiaan %s asiakaspalveuun jatkaaksesi sopimusta. |
Charge amount too high (variable amount) | The amount is higher than the agreed maximum amount. | Beløpet er høyere enn det avtalte maksimumsbeløpet. | Beløbet er højere end det aftalte maksimum beløb. | Summa on suurempi kuin maksun yläraja. |
Future charge amount too high (variable amount) | You have to raise the maximum amount in the agreement. If not, the payment will fail. | Du må øke maksbeløpet i avtalen for å unngå at betalingen feiler. | Du er nødt til at ændre maksimum beløbet for betalingsaftalen, for at undgå at betalingen fejler. | Muuta maksun ylärajaa, jotta maksu menee läpi. |
Please note: We send push notification for failed payments regardless if Notification upon payment
is toggled on or off on the agreement.
This toggle only determine if the user will get notified when a charge is successfully charged.
In addition to sending push notifications, a failure texts is also set on the charge.
By letting the customer know why the charge failed we enable them to fix the underlying issue before the retryDays
are over.
Example if a user has an expired card:
Please refer to the table below for a summary of the failure texts:
Reason | Charge failure text (English) | Charge failure text (Norwegian) | Charge failure text (Danish) | Charge failure text (Finnish) |
---|---|---|---|---|
Insufficient funds | Something went wrong. Please make sure your attached payment source contains the necessary funds to complete the payment. We will try again later. | Noe gikk galt. Sørg for at betalingskilden inneholder de nødvendige midlene for å fullføre betalingen. Vi prøver igjen senere. | Noget gik galt. Sørg for at, du har nok penge på dit betalingsmiddel til at gennemføre betalingen. Vi prøver igen senere. | Jokin meni pieleen. Varmista, että sinulla on riittävästi katetta maksua varten. Yritämme suorittaa veloitusta myöhemmin uudelleen. |
Invalid payment source | The payment source associated with your agreement is invalid. Please change your card or account. | Kortet som er knyttet til avtalen din er ugyldig. Bytt til et annet. | Betalingsmidlet som er tilknyttet din aftale er ugyldigt. Venligst skift dit kort eller konto. | Sopimukseesi liitetty maksutapa ei kelpaa. Ole hyvä ja vaihda kortti. |
Charge amount too high (variable amount) | Payment failed. Change the maximum amount in the agreement below. We'll try again later. | Betalingen feilet. Endre maksbeløpet i avtalen under. Vi prøver igjen senere. | Betaling fejlet. Ændre maksimum beløbet i aftalen nedenfor. Vi prøver igen senere. | Maksu epäonnistui. Muuta sopimuksen maksun ylärajaa alla. Yritämme myöhemmin uudelleen. |
Future charge amount too high (variable amount) | Payment will fail. Change the maximum amount in the agreement below. | Betalingen kommer til å feile. Endre maksbeløpet i avtalen under. | Betalingen vil fejle. Ændre maksimum beløbet i aftalen nedenfor. | Maksua ei voida veloittaa. Vaihda sopimuksen maksun ylärajaa alta. |
Profile sharing
We offer the possibility for merchants to ask for the user's profile information as part of the payment flow.
To enable the possibility to fetch profile information for a user the merchant can add a
scope
parameter to the POST:/recurring/v3/agreements
call.
See the Userinfo API guide for details.
Profile sharing call by call guide
Scenario: You want to complete a payment and get the name and phone number of a customer.
- Retrieve the access token by calling the
POST:/accesstoken/get
endpoint. - Add the scope field to the draft agreement request body and include the scope you wish to get
access to (valid scope) before calling the
POST:/recurring/v3/agreements
endpoint. - The user consents to the information sharing and accepts the agreement in Vipps.
- Retrieve the
sub
by calling theGET:/recurring/v3/agreements/{agreementId}
endpoint. - Using the sub from step 4, call the
GET:/vipps-userinfo-api/userinfo/{sub}
endpoint to retrieve the user's information.
Important note: The API call to the GET:/vipps-userinfo-api/userinfo/{sub}
endpoint
must not include the subscription key (the Ocp-Apim-Subscription-Key
header) used for the Recurring API.
This is because userinfo is part of Login and is therefore not under the same subscription,
and will result in a HTTP Unauthorized 401
error.
Example calls
To request this scope, add the scope to the initial POST:/recurring/v3/agreements
call
Example of request with scope:
{
"phoneNumber":"4712345678",
"interval": {
"unit": "MONTH",
"count": 1
},
"merchantRedirectUrl": "https://example.com/confirmation",
"merchantAgreementUrl": "https://example.com/my-customer-agreement",
"pricing": {
"type": "LEGACY",
"amount": 49900,
"currency": "NOK"
},
"productDescription": "Access to all games of English top football",
"productName": "Premier League subscription",
"scope": "address name email birthDate phoneNumber"
}
The user then consents and pays in Vipps.
Please note: This operation has an all or nothing approach, a user must complete a valid agreement and consent to all values in order to complete the session. If a user chooses to reject the terms the agreement will not be processed. Unless the whole flow is completed, this will be handled as a regular failed agreement by the recurring APIs.
Once the user completes the session a unique identifier sub
can be retrieved with the
GET:/recurring/v3/agreements/{agreementId}
endpoint, alongside the full URL to Userinfo.
Example sub
and userinfoUrl
format:
{
"sub": "c06c4afe-d9e1-4c5d-939a-177d752a0944",
"userinfoUrl": "https://api.vipps.no/vipps-userinfo-api/userinfo/c06c4afe-d9e1-4c5d-939a-177d752a0944"
}
This sub
is a link between the merchant and the user and can be used to retrieve
the user's details from the GET:/vipps-userinfo-api/userinfo/{sub}
endpoint.
The sub
is based on the user's national identity number ("fødselsnummer"
in Norway), and does not change (except in very special cases).
Please note: It is recommended to get the user's information directly after
completing the transaction. There is however a time limit of 168 hours
(one week) to retrieve the consented profile data from the /userinfo
endpoint to
better support merchants that depend on manual steps/checks in their process of
fetching the profile data. The merchant will get the information that is in the
user profile at the time when they actually fetch the information. This means
that the information might have changed from the time the user completed the
transaction and the fetching of the profile data.
Userinfo call
This endpoint returns the payload with the information that the user has consented to share.
Call the GET:/vipps-userinfo-api/userinfo/{sub}
endpoint with the sub
that was retrieved earlier.
See the Userinfo API guide for details.
Skip landing page
This functionality is only available for special cases.
See: Landing page
If the skipLandingPage
property is set to true
in the
POST:/recurring/v3/agreements
call, it will cause a push notification to be sent to the given phone number
immediately, without loading the landing page.
If the sales unit is not whitelisted, the request will fail and an error message will be returned.
HTTP responses
This API returns the following HTTP statuses in the responses:
HTTP status | Description |
---|---|
200 OK | Request successful |
204 No content | Request successful |
202 Accepted | Request accepted, indicates that the request has been accepted for processing, but the processing has not been completed yet. |
400 Bad Request | Invalid request, see the error for details |
401 Unauthorized | Invalid credentials |
403 Forbidden | Authentication ok, but credentials lacks authorization |
404 Not Found | The resource was not found |
409 Conflict | Unsuccessful due to conflicting resource |
422 Unprocessable Entity | Vipps MobilePay could not process |
429 Too Many Requests | Look at table below to view current rate limits |
500 Server Error | An internal Vipps MobilePay problem. |
Please note: Responses might include a Retry-After
-header that will indicate the earliest time you should
retry the request or poll the resource to see if an operation has been performed.
This will follow the spec,
and will either be a http-date
or a number indicating a delay in seconds.
This will mostly apply to 429 responses, but may also appear in certain other circumstances where it would be natural
to retry the request at a later point in time.
Error responses
HTTP responses for errors follow the RFC 7807 standard. For more details about error types, see Knowledge base: Errors.
Rate limiting
We have added rate-limiting to our API (HTTP 429 Too Many Requests
) to prevent
fraudulent and wrongful behavior, and increase the stability and security of
our API. The limits should not affect normal behavior, but please
contact us
if you notice any unexpected behavior.
The "Key" column specifies what we consider to be the unique identifier, and what we "use to count". The limits are of course not total limits.
API | Limit | Key used | Explanation |
---|---|---|---|
CreateCharge | 2 per minute | agreementId + chargeId (based on idempotency key) | Two calls per minute for each unique agreementId and chargeId |
[CreateChargesAsync][create-charges-async-endpoint] | 100 per minute | per merchant | Hundred calls per merchant |
CancelCharge | 5 per minute | agreementId + chargeId | Five calls per minute for each unique agreementId and chargeId |
CaptureCharge | 5 per minute | agreementId + chargeId | Five calls per minute for each unique agreementId and chargeId |
RefundCharge | 5 per minute | agreementId + chargeId | Five calls per minute for each unique agreementId and chargeId |
ListAgreements | 5 per minute | per merchant | Five calls per minute for each merchant |
UpdateAgreement | 5 per minute | agreementId | Five calls per minute for each unique agreementId |
FetchCharge | 10 per minute | agreementId + chargeId | Ten calls per minute for each unique agreementId and chargeId |
FetchChargeById | 100 per minute | chargeId | Hundred calls per minute for each merchant |
ListCharges | 10 per minute | agreementId | Ten calls per minute for each unique agreementId |
FetchAgreement | 120 per minute | agreementId | 120 calls per minute for each unique agreementId |
DraftAgreement | 300 per minute | per merchant | 300 calls per minute for each merchant |
Partner keys
In addition to the normal Authentication we offer partner keys, which let a partner make API calls on behalf of a merchant.
If you are a Vipps MobilePay partner who is managing agreements on behalf of Vipps MobilePay merchants, you
can use your own API credentials to authenticate, and then send
the Merchant-Serial-Number
header to identify which of your merchants you
are acting on behalf of. The Merchant-Serial-Number
must be sent in the header
of all API requests.
By including the HTTP Headers you will make
it easier to investigate problems, if anything unexpected happens. Partners may
re-use the values of the Vipps-System-Name
and Vipps-System-Plugin-Name
in
the plugins headers if having different values do not make sense.
Here's an example of headers (please refer to the Recurring API specification for all the details):
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni <snip>
Ocp-Apim-Subscription-Key: 0f14ebcab0ec4b29ae0cb90d91b4a84a
Merchant-Serial-Number: 123456
Vipps-System-Name: acme
Vipps-System-Version: 3.1.2
Vipps-System-Plugin-Name: acme-pos
Vipps-System-Plugin-Version: 4.5.6
Content-Type: application/json
Please note: The Merchant Serial Number (MSN) is a unique ID for the sale unit. This is a required parameter if you are a Vipps MobilePay partner making API requests on behalf of a merchant. The partner must use the merchant's MSN, not the partner's MSN. This parameter is also recommended for regular Vipps MobilePay merchants making API calls for themselves.
Polling guidelines
General guidelines for polling with the
GET:/recurring/v3/agreements/{agreementId}
endpoint can be found at: Knowledge base: Polling guidelines.
See also Knowledge base: Timeouts.
Testing
To facilitate automated testing in the
Test Environment (MT),
the Recurring API provides a force accept agreement
endpoint to avoid manual
agreement acceptance in the Vipps or MobilePay app:
The force approve endpoint allows developers to approve a payment through the Recurring API without the use of the Vipps or MobilePay app. This is useful for automated testing. The endpoint is only available in our test environment.
Handling redirects
You can redirect the user to a website or app once they have used the Vipps or MobilePay app to log in to your site, complete a payment, accept an agreement, or similar.
We have limited control over the redirect back to the merchant's website after a completed purchase or log-in. Your integration must not assume that the app will redirect to the exact same session. For example, don't rely entirely on cookies in order to handle the redirect event. The redirect may send the user to a different web browser
For more details, see Knowledge base: Recommendations regarding handling redirects.
Different agreement types and when to use them
Vipps MobilePay recurring payments is a fairly flexible service that allows merchants to tailor the user experience in the Vipps or MobilePay app by utilizing the normal agreements, initial charges, campaigns, or a combination of those.
This can be a bit confusing when deciding on which implementation to use. In short, our advice is to implement support for all our flows, and also implement features in your own systems for moving between the flows depending on the use case.
Normal agreement
This is the preferred flow whenever there is no campaign or no required payment on the start of an agreement.
In the normal agreement, the user gets presented with the agreement, agrees to it, and gets sent to a confirmation screen.
On the agreement we present the start date, the price of the agreements, the productName
and the product description
which are all defined by the merchant.
We also present an agreement explanation which is used to describe the agreement interval to the user.
For example, for an agreement with interval.unit=YEAR
and interval.count=1
, the agreement explanation will be hvert år til du sier opp
or every year until cancelled
Agreement with initial charge
If you require a payment to be completed at the same time that the agreement is created, you must use initial charge.
When an initial charge is present and the amount is different from the agreement price (or campaign price), the flow in the app will change. First the user gets presented with an overview over both the agreement and the initial charge. Then, when the user proceeds to confirm the agreement, the payment of the initial charge will be processed.
Here we also show productName
and the agreement explanation on the agreement, as well as description
on the initial charge.
productName
and initial charge description
are defined by the merchant.
The agreement explanation is created by automatically based on the interval and the campaign, if specified.
Initial charges are designed to be used whenever there is an additional cost in setting up the agreement. This could be bundling of a mobile phone together with a mobile subscription, or a TV setup-box when becoming a customer at a cable company. We do not recommend this flow to be used purely for campaigns, as it could be confusing to the user.
As an example: If you have a campaign of 10 NOK for a digital media subscription for 3 months, and the normal price is 299,- monthly, the user would see both the charge of 10 NOK, and have to confirm the agreement for 299,- monthly, which can lead the user to believe that both will be paid upon entering the agreement.
See How it works with initial charge
Agreement with campaign
This is the preferred flow whenever you have a type of campaign where the subscription has a certain price for a certain interval or time, before it switches over to ordinary price.
See Campaigns and How it works: Campaigns for details about campaigns.
When setting a campaign, this follows the normal agreement flow - with some changes. Instead of showing the ordinary price of the agreement, the campaign price will override this, and the ordinary price will be shown below together with information about when the change from the campaign price to the ordinary price will happen.
Please note: Campaign is not supported for variableAmount
agreements.
Agreement with initial charge and campaign
Ideally, this flow is intended for when you have a combination of an additional cost when setting up the agreement, presented as the initial charge, as well as having a limited time offer on the actual subscription.
In addition to campaigns and initial charges being available as individual flows, they can also be combined. In this case, the user would see first a summary of both the agreement, including the campaign as described in the sections on campaigns, as well as the initial charge. Again, all fields described in previous flows are available for the merchant to display information to the user.
Agreement screens with initial and campaign
Webhooks integration
You can receive instant notifications about important events, such as successful payments and agreement cancellations. To set up the basic webhook infrastructure, you need to register your webhook URL, as described in the Webhooks API guide. We'll send the real-time notifications about subscription events to the URL you specify.
We support up to 25 webhook registrations per event type per MSN. For more about these limits, see webhook limits.
Charge webhooks
Charge webhook events
Webhook event types for recurring charges:
Event type | Description |
---|---|
recurring.charge-reserved.v1 | Charge was reserved. The event is not sent for recurring charges with transaction type DIRECT_CAPTURE . |
recurring.charge-captured.v1 | Charge was fully or partially captured. |
recurring.charge-canceled.v1 | Charge was fully or partially cancelled. |
recurring.charge-failed.v1 | Charge failed and will no longer be retried. |
recurring.charge-creation-failed.v1 | Charge failed to be created asynchronously. |
Charge webhook event payloads
Payload properties may include:
Field name | Type | Description | Possible values |
---|---|---|---|
agreementId | string | ID of an agreement | agr_kFW4chk |
chargeExternalId | nullable string | Merchant provided external ID of charge | ExtId123 |
chargeId | string | ID of a charge | 82ce990f-d08a-448c-bd26-ee6be8418d06 |
amount | number | Amount of charge in øre (NOK, DKK) or cents (EUR) | 300 |
chargeType | enum | Indicates type of charge | RECURRING , INITIAL , UNSCHEDULED |
eventType | enum | Indicates what has happened to a charge | Values provided in a table above |
currency | enum | Currency of charge | DKK , NOK , EUR |
occurred | ISO 8601 UTC date | When change has occurred | 2023-10-10T13:30:36.079765975Z |
amountCaptured | number | Amount of payment that was captured | 100 |
amountCanceled | number | Amount of charge that was canceled | 200 |
amountRefunded | number | Amount of charge that was refunded | 100 |
failureCode | number | Code of an error during async creation | Listed below |
failureText | string | Explanation of an error during async creation | Listed below |
Possible failureCode
and failureText
combinations.
These fields are only present if the charge has failed async validation and
was created using the multiple charge creation endpoint:
POST:/recurring/v3/agreements/charges
.
Failure code | Failure text | Explanation |
---|---|---|
50006 | DeclinedBySystem | Unspecified exception during charge creation |
50003 | AgreementNotActive | Charge is requested for non-active agreement |
70001 | ChargeAmountTooHighForFixedAmountAgreement | Charge amount is 5 times higher than fixed amount agreement's amount |
70002 | ChargeCreationConflict | Trying to create charge with same idempotency key more than once |
70004 | ChargeTooFarInFuture | Due date is too far in the future |
70005 | ChargeDueDateTooSoon | Due date is too soon |
Example of the webhook payloads you could get upon a new charge event:
{
"agreementId": "agr_kFW4chk",
"chargeExternalId": "extId",
"chargeId": "82ce990f-d08a-448c-bd26-ee6be8418d06",
"amount": 300,
"chargeType": "RECURRING",
"eventType": "recurring.charge-canceled.v1",
"currency": "NOK",
"occurred": "2023-10-10T13:30:36.079765975Z",
"amountCaptured": 0,
"amountCanceled": 300,
"amountRefunded": 0,
"failureCode": null,
"failureText": null
}
Agreement webhooks
Agreement webhook events
Webhook event types for recurring agreements:
Event type | Description |
---|---|
recurring.agreement-activated.v1 | User has accepted agreement |
recurring.agreement-rejected.v1 | User has rejected agreement |
recurring.agreement-stopped.v1 | Agreement was stopped either by merchant either by user |
recurring.agreement-expired.v1 | Agreement has expired |
Agreement webhook event payloads
Payload properties may include:
Field name | Type | Description | Possible values |
---|---|---|---|
agreementId | string | ID of an agreement | agr_kFW4chk |
agreementUUID | UUID | ID of an agreement | 82ce990f-d08a-448c-bd26-ee6be8418d06 |
agreementExternalId | nullable string | Merchant provided external ID of agreement | ExtId123 |
eventType | enum | Indicates what has happened to an agreement | Values provided in a table above |
occurred | ISO 8601 UTC date | When change has occurred | 2023-10-10T13:30:36.079765975Z |
actor | nullable enum | Indicates who has initiated action. Applicable only for recurring.agreement-stopped.v1 webhook. | MERCHANT , USER |
Examples of the webhook payloads you could get upon a new agreement event:
{
"agreementId": "agr_hXbXJUN",
"occurred": "2023-10-11T09:51:04.562829303Z",
"agreementExternalId": null,
"eventType": "recurring.agreement-expired.v1",
"agreementUUID": "c81bf516-7972-488e-bbf1-146dcd8592f9",
"actor": null
}
{
"agreementId": "agr_hXbXJUN",
"occurred": "2023-10-11T09:51:04.562829303Z",
"agreementExternalId": null,
"eventType": "recurring.agreement-stopped.v1",
"agreementUUID": "c81bf516-7972-488e-bbf1-146dcd8592f9",
"actor": "MERCHANT"
}
For more details about webhooks, see the Webhooks API guide.