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 CIT payment session with the subscription object
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:
Your backend creates a CIT payment session with a subscription
object
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.
/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
amountinteger
2.50 DKK must be sent as 250. If the amount is 0, a token is created without charging the customer.currencystring
DKK.reference?string | null
36.Type
- is removed if present, and if the value is longer than 12 characters, only the left part is used.transactionType?PAYMENT | MOTO | null
Type
PAYMENT is the normal checkout flow. MOTO is for Mail Order / Telephone Order and requires a special acquirer agreement.Default
PAYMENTscaMode?SKIP | NORMAL | FORCE | null
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
Type
1, maximum 120.instantCapture?OFF | VOID | NO_VOID | null
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
Type
VIPPS_MOBILEPAY is allowed, the customer is redirected directly to that method. If they cancel, they return to returnUrl or failureUrl.exemptions?string[] | null
Type
TRA and LVT.maxAttempts?integer | null
Type
1, maximum 25.generateQrCode?boolean | null
Default
falsereportFailure?boolean | null
reportExpired?boolean
Default
falsedynamicAmount?boolean | null
Type
Default
falsenotificationUrl?uri | null
Type
1024. Supports URL templating.preAuthUrl?uri | null
Type
1024.returnUrl?uri | null
Type
1024. Supports URL templating.successUrl?uri | null
Type
1024.failureUrl?uri | null
Type
1024.retryUrl?uri | null
Type
failureUrl. Maximum length 1024.textOnStatement?string | null
Type
1, maximum length 39.attributes?object | null
Type
1kb.customerId?string | null
Type
customer?object | null
Type
orderLines?object[] | null
Type
1000 items. See the orderLines table below.subscription?object | null
Type
scaMode is always set to FORCE.ageVerification?object | null
Type
Prop
Type
firstName?string | null
lastName?string | null
birthdate?date | null
email?string | null
phoneNumber?string | null
Type
+45 12345678.shippingAddress?object | null
Type
countryCode, postalCode, city, line1, and optional line2 / line3. countryCode must be ISO 3166-1 alpha-2.billingAddress?object | null
Type
shippingAddress. If billing and shipping are the same, send both with identical values.Prop
Type
descriptionstring
imageUrl?url | null
Type
1024. A minimum resolution of 250x250 is recommended.quantityinteger
Type
totalAmountinteger
unitPriceinteger
typePHYSICAL | DISCOUNT | SHIPPING_FEE | SALES_TAX | DIGITAL | GIFT_CARD | STORE_CREDIT | SURCHARGE | OTHER
Prop
Type
id?uuid | null
amount?integer | null
Type
amount to 0 and set subscription.amount to the future recurring amount.typeSCHEDULED | UNSCHEDULED
Type
SCHEDULED is for fixed intervals such as monthly billing. UNSCHEDULED is for pay-as-you-go charging.reference?string | null
expiryDate?date | null
interval?object | null
Type
period (DAY, WEEK, MONTH, YEAR) and frequency (1-31). Omit for UNSCHEDULED.billingAgreement?object | null
Type
billingPlanId and optional reference / nextChargeAt. Mutually exclusive with subscription.id and subscription.interval.Prop
Type
minimumAgeinteger
Type
0, maximum 99.country?DK | null
Type
MitID.POS defaults and request overrides
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.scaMode: "SKIP" outside tests. Many acquirers do not accept payments without 3D Secure data, and liability can shift to the merchant.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:
amountfor what the customer pays nowsubscription.amountfor 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 CIT payment session with the subscription object
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:
amountis the first charge taken todaysubscription.amountis the future recurring amount- You use minor units such as
29900for299.00 DKK
The request is rejected for a scheduled subscription
Check that:
subscription.typeis set toSCHEDULEDsubscription.interval.periodis includedsubscription.interval.frequencyis 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.