Looking for ePay classic docs? Go to docs.epay.dk
ePay documentationDocsePay documentation

With Subscription

Use this guide if the customer's first payment should also establish a subscription for later MIT charges.

Subscription is the right starting point when you need an initial customer-authorized payment now and the ability to charge the customer again later without asking for payment details again.

The subscription is created by sending the subscription object when you initialize a payment session through POST /public/api/v1/cit.

What you will build

In this guide, you will:

Create a pending order or subscription intent in your own system

Create a CIT payment session with the subscription object

Redirect the customer to ePay Checkout
Complete the first payment
Receive the server-side result
Store the subscription details for future MIT charges

How Subscription works

Subscription starts with a normal customer-authorized payment.

Your backend creates the initial CIT session. The customer completes the payment in Checkout. If the payment succeeds, the subscription becomes active and can be used for future MIT charges from your backend.

The basic flow is:

Customer starts a checkout or sign-up flow

Your backend creates a CIT payment session with a subscription object

ePay returns a payment window URL
The customer completes the first payment in Checkout
ePay sends the result to your backend
Your backend stores the subscription ID and activates the service
Future MIT charges are created server-side when needed

Before you start

You need:

  • An API key
  • A Point of Sale ID
  • A backend endpoint that can create payment sessions
  • A success URL
  • A failure URL
  • A notification URL
  • An acquirer agreement with recurring payments enabled

Your API key must only be used server-side. Never expose your API key in frontend code.

Step 1: Create a pending order or subscription intent

Before creating the payment session in ePay, create a pending order or subscription intent in your own system.

const subscriptionSignup = await createSubscriptionSignup({  customerId: "CUSTOMER-1001",  plan: "pro-monthly",  firstChargeAmount: 9900,  recurringAmount: 29900,  currency: "DKK",  status: "pending",});

Do not activate the subscription in your own system until your backend receives and verifies the successful payment result.

Step 2: Create a payment session

Create a payment session through POST /public/api/v1/cit and include the subscription object.

API referencePOSTInitialize Payment Session/public/api/v1/citInitializes a new payment session with the ePay Payments API.
const body = JSON.stringify({  "pointOfSaleId": "<POINT_OF_SALE_ID>",  "amount": 9900,  "currency": "DKK",  "reference": "SIGNUP-1001",  "notificationUrl": "https://example.com/api/epay/notification",  "successUrl": "https://example.com/billing/success",  "failureUrl": "https://example.com/billing/failure",  "subscription": {    "type": "SCHEDULED",    "amount": 29900,    "reference": "SUB-1001",    "interval": {      "period": "MONTH",      "frequency": 1    }  }})fetch("https://payments.epay.eu/public/api/v1/cit", {  method: "POST",  headers: {    "Content-Type": "application/json",    "Authorization": "Bearer <TEST_API_KEY>",    "Idempotency-Key": "<UUID>"  },  body})

In this example, the customer pays 99.00 DKK today and agrees to a recurring subscription amount of 299.00 DKK per month afterward.

Step 3: Understand the request body

pointOfSaleId, amount, and currency are always required in the request body.

The full schema below also includes advanced fields such as customer, orderLines, subscription, allowedPaymentMethods, returnUrl, retryUrl, generateQrCode, reportExpired, dynamicAmount, and ageVerification.

The table below covers the request fields used when initializing a payment session.

Prop

Type

pointOfSaleIduuid
Id of the point of sale to associate the payment with.
amountinteger
The amount of the payment in minor units. Example: 2.50 DKK must be sent as 250. If the amount is 0, a token is created without charging the customer.
currencystring
The currency code of the payment using ISO 4217 alpha-3, for example DKK.
reference?string | null
Your own payment or order reference. It should be unique per payment. Only ASCII alphanumeric characters and dashes are allowed, with a max length of 36.

Type

Worldline-specific behavior: - is removed if present, and if the value is longer than 12 characters, only the left part is used.
transactionType?PAYMENT | MOTO | null
The type of transaction to process. Most merchants should use PAYMENT.

Type

PAYMENT is the normal checkout flow. MOTO is for Mail Order / Telephone Order and requires a special acquirer agreement.

Default

PAYMENT
scaMode?SKIP | NORMAL | FORCE | null
Controls how 3D Secure is handled.

Type

SKIP does not attempt 3DS and leaves liability with the merchant. NORMAL attempts 3DS normally. FORCE requests a challenge flow, although third parties in the 3DS chain may still ignore it.
timeout?integer | null
How long the session stays valid in minutes.

Type

Minimum 1, maximum 120.
instantCapture?OFF | VOID | NO_VOID | null
Controls whether the payment is captured immediately.

Type

OFF does not capture immediately. VOID captures immediately and voids the authorization if capture fails. NO_VOID captures immediately and keeps the authorization if capture fails.
allowedPaymentMethods?string[] | null
Limits which payment methods are shown for this session.

Type

If only one redirect-based method such as VIPPS_MOBILEPAY is allowed, the customer is redirected directly to that method. If they cancel, they return to returnUrl or failureUrl.
exemptions?string[] | null
Exemptions to apply when possible. Using exemptions can shift fraud liability from the issuer to the merchant.

Type

Supported values are TRA and LVT.
maxAttempts?integer | null
Maximum allowed payment attempts for the session.

Type

Minimum 1, maximum 25.
generateQrCode?boolean | null
If true, the response includes a Base64 QR code version of the payment window URL.

Default

false
reportFailure?boolean | null
If true, failed transaction attempts are also reported to notificationUrl.
reportExpired?boolean
If true, sessions that reach the expired state are also reported to notificationUrl.

Default

false
dynamicAmount?boolean | null
Lets the cardholder define the transaction amount instead of using the initialized amount.

Type

This gives client-side control over the amount and is only meant for special cases. Must be enabled by ePay support.

Default

false
notificationUrl?uri | null
The URL where ePay sends payment status notifications and webhooks.

Type

Maximum length 1024. Supports URL templating.
preAuthUrl?uri | null
Enables pre-authorization webhooks before authorization is attempted.

Type

Maximum length 1024.
returnUrl?uri | null
Where the browser is returned when the customer clicks the back button in the payment window.

Type

Checkout only. Overrides the payment window default. Maximum length 1024. Supports URL templating.
successUrl?uri | null
Where the customer is redirected after a successful payment.

Type

Maximum length 1024.
failureUrl?uri | null
Where the customer is redirected when no more payment attempts are allowed.

Type

Maximum length 1024.
retryUrl?uri | null
Optional redirect for failed attempts when more attempts are still allowed.

Type

If omitted, the customer is redirected to failureUrl. Maximum length 1024.
textOnStatement?string | null
Text shown on the cardholder bank statement.

Type

Minimum length 1, maximum length 39.
attributes?object | null
Pass-through attributes returned back to the merchant on webhooks.

Type

Maximum total size is 1kb.
customerId?string | null
Merchant-defined customer identifier that uniquely represents the customer in your own system.

Type

Required for stored payment methods and enhanced age verification. Do not use guest IDs.
customer?object | null
Customer details used during SCA / 3DS and by payment methods such as Klarna.

Type

See the customer table below.
orderLines?object[] | null
Line items that make up the purchase. Used by some payment methods such as Klarna.

Type

Maximum 1000 items. See the orderLines table below.
subscription?object | null
Creates or updates a reusable subscription from the same CIT checkout flow.

Type

See the subscription table below. When a subscription is initialized, scaMode is always set to FORCE.
ageVerification?object | null
Requires the customer to verify that they meet a minimum age before the transaction can continue.

Type

See the ageVerification table below.
customer

Prop

Type

firstName?string | null
The first name of the paying customer.
lastName?string | null
The last name of the paying customer.
birthdate?date | null
The date of birth of the customer in YYYY-MM-DD format.
email?string | null
The email address of the paying customer.
phoneNumber?string | null
The phone number of the paying customer.

Type

Use E.164 with a single space between country code and local number, for example +45 12345678.
shippingAddress?object | null
Shipping address of the purchase. Leave empty when nothing is shipped.

Type

Uses countryCode, postalCode, city, line1, and optional line2 / line3. countryCode must be ISO 3166-1 alpha-2.
billingAddress?object | null
Billing address of the customer.

Type

Uses the same address shape as shippingAddress. If billing and shipping are the same, send both with identical values.
orderLines[]

Prop

Type

descriptionstring
Descriptive name of the order line item.
imageUrl?url | null
Optional image URL that can be used in merchant-provider communications such as Klarna flows.

Type

Maximum length 1024. A minimum resolution of 250x250 is recommended.
quantityinteger
Quantity of the order line item.

Type

Must be a non-negative number.
totalAmountinteger
Total order line amount in minor units, including tax and discounts.
unitPriceinteger
Single-item price in minor units, before discount and typically including taxes.
typePHYSICAL | DISCOUNT | SHIPPING_FEE | SALES_TAX | DIGITAL | GIFT_CARD | STORE_CREDIT | SURCHARGE | OTHER
Type of the order line item.
subscription

Prop

Type

id?uuid | null
Optional subscription ID to update an existing active subscription with new payment details.
amount?integer | null
Optional recurring amount in minor units when it differs from the current checkout amount.

Type

If the customer should not pay now, set top-level amount to 0 and set subscription.amount to the future recurring amount.
typeSCHEDULED | UNSCHEDULED
Subscription type.

Type

SCHEDULED is for fixed intervals such as monthly billing. UNSCHEDULED is for pay-as-you-go charging.
reference?string | null
Optional merchant-defined subscription reference. Falls back to the session reference if omitted.
expiryDate?date | null
Optional subscription expiry date used during 3DS. ePay does not reject charges after this date.
interval?object | null
Optional subscription schedule for SCHEDULED subscriptions.

Type

Contains period (DAY, WEEK, MONTH, YEAR) and frequency (1-31). Omit for UNSCHEDULED.
billingAgreement?object | null
Optional automatic billing agreement created from the subscription.

Type

Contains required billingPlanId and optional reference / nextChargeAt. Mutually exclusive with subscription.id and subscription.interval.
ageVerification

Prop

Type

minimumAgeinteger
Minimum required age for the purchase.

Type

Minimum 0, maximum 99.
country?DK | null
Country code deciding which electronic identity method is presented to the customer.

Type

For Danish customers this is typically verified with MitID.

POS defaults and request overrides

In Advanced Point of Sale Settings in the ePay backoffice, you can configure defaults for scaMode, timeout, instantCapture, maxAttempts, notificationUrl, successUrl, failureUrl, and exemptions. If a value is sent in the API request, it overrides the Point of Sale default for that session.
Avoid using scaMode: "SKIP" outside tests. Many acquirers do not accept payments without 3D Secure data, and liability can shift to the merchant.
DocumentationUse pre-authorization webhooks for risk checks and dynamic updatesReview the full pre-auth flow, payload shape, response fields, and the tradeoffs before enabling preAuthUrl on a payment session.

The subscription object is what tells ePay to create a reusable subscription after a successful first payment.

Step 4: Understand scheduled vs. unscheduled subscriptions

Use SCHEDULED when the customer should be charged on a predictable interval such as every month.

Use UNSCHEDULED when future charges depend on usage, such as parking, rentals or other pay-as-you-go services.

If you use SCHEDULED, include subscription.interval.

If you use UNSCHEDULED, omit subscription.interval.

Step 5: Know the important subscription rules

When you initialize a subscription, scaMode is always set to FORCE.

This is required for subscription initialization and helps ensure the first payment is strongly authenticated before later MIT charges are used.

If the recurring amount is different from the first charge, use:

  • amount for what the customer pays now
  • subscription.amount for the recurring amount later

If no payment should be taken today, you can set amount to 0 and still create or update the subscription consent through the same CIT flow.

Step 6: Redirect the customer to Checkout

The response contains the payment window URL needed to complete the first payment.

Redirect the customer to ePay Checkout.

return redirect(payment.paymentWindowUrl);

The customer now completes the first payment and authorizes the subscription in the ePay-hosted payment flow.

Step 7: Handle the result server-side

Do not activate the subscription only because the customer lands on your success page.

Use your notification URL or webhook to verify the result and store the subscription details in your backend.

Recommended backend flow:

Create the subscription sign-up as pending

Create the CIT payment session with the subscription object

Redirect the customer to Checkout
Receive the payment result server-side
Verify that the first payment succeeded
Store the returned subscription ID and payment identifiers
Activate the subscription in your own system

Common problems

The first payment succeeds, but later recurring charges fail

Check that:

  • Your acquirer agreement supports recurring payments
  • You stored the correct subscription ID from the successful flow
  • You are using MIT for follow-up charges instead of another CIT flow

My recurring amount is wrong

Check that:

  • amount is the first charge taken today
  • subscription.amount is the future recurring amount
  • You use minor units such as 29900 for 299.00 DKK

The request is rejected for a scheduled subscription

Check that:

  • subscription.type is set to SCHEDULED
  • subscription.interval.period is included
  • subscription.interval.frequency is included

The customer returns to success, but I cannot activate the subscription

Check that:

  • Your notification URL is publicly available
  • Your backend receives the payment result
  • Your backend verifies the payment before activating access
  • You persist the subscription identifiers returned from the successful flow

What you built

You have now created your first subscription payment with ePay.

You created a CIT payment session, included the subscription object, completed the first customer-authorized payment and prepared your backend to use the resulting subscription for future MIT charges.

Next steps

How is this guide?

On this page