Recurring PSP API guide
Recurring API card passthrough for PSPs is in development and targeted for Q2/Q3 2026.
PSPs using the Recurring API process card payments through their own infrastructure using card passthrough.
This page covers the PSP-specific card passthrough additions; for full Recurring API documentation, see the Recurring API section.
Overview​
Key PSP constraints for card passthrough:
- Agreements: PSPs must process the Customer-Initiated Transaction (CIT) themselves to verify the payment source and confirm the agreement — Vipps MobilePay does not handle this on your behalf. See Agreements in the Recurring API guide.
- Charges: PSPs can only create unscheduled charges — scheduled recurring charges with retries are not available. An optional initial charge on the agreement controls the CIT amount during sign-up; if omitted, a zero-amount CIT is performed. See Charges in the Recurring API guide.
- Payment sources: Cards are the only supported payment source for PSPs initially. When a user changes their card on an existing agreement, you receive a zero-amount CIT callback to verify the new card — handled entirely through the card callback.
Flows​
There are three main PSP card passthrough flows:
- Agreement sign-up — user selects a card when signing an agreement; you process a CIT.
- Charge creation — you create a charge and receive card info in the response; you process the payment and update us asynchronously.
- Payment source update — user changes their card on an existing agreement; you process a zero-amount CIT.
| Flow | Triggered by | Card delivery | PSP response |
|---|---|---|---|
| Agreement sign-up | User confirms agreement | Card callback | Callback response |
| Charge creation | PSP creates a charge | Charge creation response | Charge status update |
| Payment source update | User changes card in app | Card callback | Callback response |
| Agreement status update | PSP or user cancels agreement | — | Agreement status update |
| Charge status update | PSP processes charge | — | Charge status update |
Endpoints​
The following table shows how Recurring API endpoints are used in a PSP card passthrough integration.
| Recurring API endpoints | PSP usage and notes |
|---|---|
Draft agreement:POST:/recurring/v3/agreements | Draft agreement with Psp-Id and cardPassthrough fields. Card token delivered via callback to cardCallbackUrl. |
Update an agreement:PATCH:/recurring/v3/agreements/{agreementId} | Stop an agreement or update its terms when the user cancels on your end or terms change. |
Check the agreement status:GET:/recurring/v3/agreements/{agreementId}GET:/recurring/v3/agreements/{agreementId}/charges/{chargeId} | Optional: compare current agreement and charge state with PSP records and decide if updates are needed. |
Create charge:POST:/recurring/v3/agreements/{agreementId}/chargesPOST:/recurring/v3/agreements/charges | Create single or batch charges with Psp-Id. Card info returned directly in the response body. PSPs can only create unscheduled charges — scheduled recurring charges with retries are not available. |
Update the status of the charge:POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capturePOST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/refundDELETE:/recurring/v3/agreements/{agreementId}/charges/{chargeId} | Communicate capture, refund, and cancel status to Vipps MobilePay. Payment processing happens in PSP/acquirer systems. |
PSPs can use all Recurring API endpoints with the Psp-Id header added to every request. The endpoints requiring additional PSP-specific configuration are the draft agreement (POST:/recurring/v3/agreements) and charge creation (POST:/recurring/v3/agreements/{agreementId}/charges) endpoints.
Agreements​
Agreement sign-up​
Agreement sign-up is a user-initiated flow. The user selects a card in the Vipps MobilePay app, and Vipps MobilePay calls your cardCallbackUrl synchronously with the card token. You process the CIT and respond with the result to confirm the agreement.
PSP merchant agreement sign-up flow
Steps:
Send the agreement request​
Agreement sign-up with card passthrough is initiated by sending a draft agreement request with the following settings:
- Add the
Psp-Idheader with your PSP identifier - Include the
cardPassthroughobject, filled outpspReference(required): Your unique reference for the agreement.cardCallbackUrl(required): URL where we will send the card token.cardCallbackAuthHeader(required): Authentication header value for the callback.allowedCardTypes: Card types the user can select. Values:VISA_DEBIT,VISA_CREDIT,VISA_DANKORT,DANKORT,MC_CREDIT,MC_DEBIT. If not specified, all types are allowed.preferVisaPartOfVisaDankort: Whentrue, prefer the Visa part of a Visa/Dankort co-branded card. Default:false.
Example request body:
{
"pricing": {
"type": "LEGACY",
"currency": "NOK",
"amount": 10000
},
"interval": {
"unit": "MONTH",
"count": "1"
},
"initialCharge": {
"amount": 10000,
"description": "First payment",
"transactionType": "DIRECT_CAPTURE"
},
"merchantRedirectUrl": "https://example.com/redirect",
"merchantAgreementUrl": "https://example.com/agreement",
"productName": "Streaming subscription",
"cardPassthrough": {
"pspReference": "subscription-product-123",
"cardCallbackUrl": "https://example.com/psp-callback",
"cardCallbackAuthHeader": "Bearer your-secure-token",
"allowedCardTypes": ["VISA_DEBIT", "VISA_CREDIT", "VISA_DANKORT", "MC_CREDIT", "MC_DEBIT"],
"preferVisaPartOfVisaDankort": true
}
}
Full example
Example request (PSP-specific fields are highlighted):
curl -X POST https://apitest.vipps.no/recurring/v3/agreements/ \
-H "Content-Type: application/json" \
-H "Psp-Id: YOUR-PSP-ID" \
-H "Authorization: Bearer YOUR-ACCESS-TOKEN" \
-H "Ocp-Apim-Subscription-Key: YOUR-SUBSCRIPTION-KEY" \
-H "Merchant-Serial-Number: YOUR-MSN" \
-H 'Idempotency-Key: YOUR-IDEMPOTENCY-KEY' \
-H "Vipps-System-Name: acme" \
-H "Vipps-System-Version: 3.1.2" \
-H "Vipps-System-Plugin-Name: acme-webshop" \
-H "Vipps-System-Plugin-Version: 4.5.6" \
-d '{
"cardPassthrough": {
"pspReference": "subscription-product-123",
"cardCallbackUrl": "https://example.com/psp-callback",
"cardCallbackAuthHeader": "Bearer your-secure-token",
"allowedCardTypes": ["VISA_DEBIT", "VISA_CREDIT", "VISA_DANKORT", "MC_CREDIT", "MC_DEBIT"],
"preferVisaPartOfVisaDankort": true
},
"interval": {
"unit" : "WEEK",
"count": 2
},
"pricing": {
"amount": 1000,
"currency": "NOK"
},
"merchantRedirectUrl": "https://example.com/redirect-url",
"merchantAgreementUrl": "https://example.com/agreement-url",
"phoneNumber": "12345678",
"productName": "Test product"
}'
The optional initialCharge field controls the CIT amount — omit it and a zero-amount CIT is performed instead. We forward the card details to you, and it is your responsibility to process the CIT and respond with the result.
Once the agreement is successfully signed, you can start charging the user according to the agreement terms.
The user may also reject the agreement or abandon the flow — subscribe to agreement webhooks to be notified of these and other events.
For all available fields, see Agreements in the Recurring API guide.
When the user confirms the agreement, Vipps MobilePay sends a card token to your cardCallbackUrl. See Card callback for the request format, HMAC authentication, and expected response.
Update the status of an agreement​
Use these Recurring API endpoints to keep us aligned with the agreement
state in your systems.
Include your PSP identifier in the Psp-Id header.
PATCH:/recurring/v3/agreements/{agreementId}- Use this to stop an agreement when the user cancels on your end, or to update the agreement terms such as pricing or product description.
For reconciliation, you can optionally use:
For the full range of options available, see Agreements in the Recurring API guide.
Charges​
Charge creation​
PSPs currently have access to unscheduled charges. You are responsible for creating charges according to the terms of each agreement.
You can send both single and batch charges. Here are the flows:
PSP merchant charge flow (single charges)
PSP merchant charge flow (batch charges)
Steps:
Send the charge request​
Create charges using one of the Recurring API charge endpoints:
POST:/recurring/v3/agreements/{agreementId}/chargesfor single chargesPOST:/recurring/v3/agreements/chargesfor batch charges
Include the Psp-Id header with your PSP identifier.
For example:
curl -X POST https://apitest.vipps.no/recurring/v3/agreements/UNIQUE-AGREEMENT-ID/charges \
-H "Content-Type: application/json" \
-H "Psp-Id: YOUR-PSP-ID" \
-H "Authorization: Bearer YOUR-ACCESS-TOKEN" \
-H "Ocp-Apim-Subscription-Key: YOUR-SUBSCRIPTION-KEY" \
-H "Merchant-Serial-Number: YOUR-MSN" \
-H 'Idempotency-Key: YOUR-IDEMPOTENCY-KEY' \
-H "Vipps-System-Name: acme" \
-H "Vipps-System-Version: 3.1.2" \
-H "Vipps-System-Plugin-Name: acme-webshop" \
-H "Vipps-System-Plugin-Version: 4.5.6" \
-d '{
"amount": 1000,
"description": "Monthly subscription.",
"due": "2026-08-08",
"retryDays": 0,
"transactionType": "DIRECT_CAPTURE",
"orderId": "UNIQUE-ORDERID"
}'
Unlike the agreement sign-up and payment source update flows, charges do not use a card callback. Card info is returned directly in the charge creation response.
Example response:
{
"chargeId": "chg_WCVbcAbRCmu2zk",
"pspReference": "subscription-product-123",
"authorizationAttemptId": "3030303thisisaguid",
"maskedCardNumber": "47969485XXXX1234",
"cardType": "VISA-DEBIT",
"cardIssuedInCountryCode": "NO",
"paymentInstrument": "TOKEN",
"networkToken": {
"number": "5000000000000000001",
"cryptogram": "aFgdgjdkfgjdFDF=",
"expiryMonth": "03",
"expiryYear": "2030",
"tokenType": "VISA",
"eci": "7",
"paymentAccountReference": "5001BO8B9NXVVIXCT0HAJU98I512Z"
},
"encryptedPan": "xyzxyz"
}
Always send the charge update. Without it, the charge remains unresolved in both our system and the user's app — the user will see a pending charge they cannot act on, which typically leads to confusion and support requests.
Process the charge​
This step is done by you, the PSP.
After receiving card data in the charge response, process the charge in your PSP/acquirer systems using the card token or PAN.
We aren't involved in the actual card processing. We only provide card data to the PSP.
Once you've processed the charge, update the charge status to keep us aligned.
Update the status of the charge​
Use these Recurring API endpoints to keep us aligned with the payment state in your PSP/acquirer systems.
For all calls below, include your PSP identifier in the Psp-Id header.
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/capture- Use this when the charge has been captured in your systems.
POST:/recurring/v3/agreements/{agreementId}/charges/{chargeId}/refund- Use this when the charge has been refunded in your systems.
DELETE:/recurring/v3/agreements/{agreementId}/charges/{chargeId}- Use this when remaining authorized funds are cancelled in your systems, or to prevent the user from completing a charge that has not yet been authorized.
For the full range of options available, see Recurring API guide: Charges.
Payment source​
Payment source update​
Each agreement has an attached payment source that can be changed by the user in the app at any time. The new payment source must be verified with a zero-amount CIT — it is your responsibility to process this transaction. The flow is user-initiated and otherwise identical to agreement sign-up.
PSP merchant payment source update flow
See Card callback for the request format, HMAC authentication, and expected response.
Card callback​
The card callback applies to both agreement sign-up and payment source updates. When the user confirms the payment in the Vipps MobilePay app and selects their card, we send a POST request to your cardCallbackUrl. You must respond within 20 seconds.
It will be encrypted and include the PAN if you included your publicEncryptionKeyId in the initial request.
Key behaviors:
- The callback is synchronous — we expect a response with the payment authorization result.
- If your response indicates a retryable error, the user can retry with the same or a different card.
- HTTP 500 errors and timeouts are treated as non-retryable.
- Agreements can be patched later to reflect whether the CIT was successfully processed on your end.
- If an initial charge was included in the agreement, it can be updated later to reflect the payment outcome.
- We will show the user an error message: "check the payment status at the merchant you came from".
Callback request​
We send a POST request to your cardCallbackUrl with the following properties:
pspReference: Your unique reference for this payment, as provided in the create payment request.authorizationAttemptId: Unique identifier for this authorization attempt.merchantSerialNumber: The merchant serial number for the payment.maskedCardNumber: The masked card number (e.g.,47969485XXXX1234).cardType: The card type (e.g.,VISA-DEBIT).cardIssuedInCountryCode: The ISO 3166-1 alpha-2 country code where the card was issued.paymentInstrument: The type of payment instrument.TOKENindicates a network token is provided.networkToken: Object containing the network token details (whenpaymentInstrumentisTOKEN):number: The token number.cryptogram: The cryptogram for the transaction.expiryMonth: Token expiry month.expiryYear: Token expiry year.tokenType: Token network type (e.g.,VISA).eci: Electronic Commerce Indicator.paymentAccountReference: Stable reference across token renewals.
encryptedPan: Encrypted PAN, when provided instead of a network token.softDeclineCompletedRedirectUrl: URL to redirect to after a soft decline is resolved.
For example:
POST /psp-makepayment HTTP/1.1
Host: example.com
Content-Type: application/json
{
"pspReference": "7686f7788898767977",
"authorizationAttemptId": "3030303thisisaguid",
"merchantSerialNumber": "123456",
"softDeclineCompletedRedirectUrl": "https://vipps.no/mobileintercept?transactionId=123456789&responsecode=OK",
"maskedCardNumber": "47969485XXXX1234",
"cardType": "VISA-DEBIT",
"cardIssuedInCountryCode": "DK",
"paymentInstrument": "TOKEN",
"networkToken": {
"number": "5000000000000000001",
"cryptogram": "aFgdgjdkfgjdFDF=",
"expiryMonth": "03",
"expiryYear": "2030",
"tokenType": "VISA",
"eci": "7",
"paymentAccountReference": "5001BO8B9NXVVIXCT0HAJU98I512Z"
},
"encryptedPan": "xyzxyz"
}
HMAC authentication​
To verify that the callback originates from us and has not been tampered with, we sign each callback using HMAC with a shared secret.
Upon receiving the callback, the PSP must:
- Compute the HMAC of the received payload using the shared secret.
- Compare the computed value with the signature included in the request.
- Reject the callback if the values do not match.
This ensures message integrity and authenticity. HMAC authentication does not encrypt the payload.
Callback response​
Respond with HTTP 200 OK and a JSON body with the following properties:
status(required): The payment verification result. One of:RESERVE— Payment verification succeeded.FAIL— Payment verification failed. Provide the error reason and error codes, so the user receives a meaningful error message.SOFT_DECLINE— Soft decline. IncludesoftDeclineRedirectUrl.
networkTransactionReference: Your reference for the network transaction.
For example:
{
"networkTransactionReference": "123456789",
"status": "RESERVE"
}
If we receive a FAIL response, we will allow the user to retry with the same or a new payment source (unless the error code maps to something non-retryable).
If there is a timeout or HTTP 500, the payment will cannot be tried again by the user. You will need to initiate a new payment request.
Related Recurring API features​
The Recurring API section covers the full breadth of what's possible with the underlying recurring payment platform — including detailed flow diagrams, user journey walkthroughs, and descriptions of advanced features that apply to PSP integrations as well.
It's worth exploring to understand what you can offer your customers:
- How it works — visual flows for payment agreements, charges, and other recurring scenarios
- Agreement guide — creating and managing payment agreements
- Charges guide — creating, capturing, and refunding charges
- Recurring API spec — the full technical specification for all endpoints, including required fields, data types, and valid formats