With Verify
Use this guide if you need to verify the customer's age as part of the payment flow.
ePay Verify lets you add age verification directly to ePay Checkout. This is useful when selling age-restricted products where you need to confirm that the customer meets a required minimum age before the payment can be completed.
Verify works as part of the payment session. Your backend creates a payment session with age verification requirements, and ePay handles the verification flow in Checkout.
What you will build
In this guide, you will:
Create an order in your system
Create a payment session with age verification
Redirect the customer to ePay Checkout
Let ePay handle the age verification flow
Complete a test payment
Receive the payment result
Confirm that your order can be updated correctly
How Verify works
Verify adds an age verification requirement to a payment session.
The basic flow is:
Customer starts checkout on your website
Your backend creates a payment session with age verification
ePay returns the information needed to open Checkout
Customer opens ePay Checkout
ePay determines whether age verification is required
Customer completes the age verification if needed
Customer completes the payment
ePay sends the payment result to your backend
How the verification is handled depends on the selected payment method.
For MobilePay, ePay can use available age information from MobilePay/Vipps. If the customer does not meet the age requirement, the payment is declined in the MobilePay/Vipps flow.
For cards and other payment methods, the customer may be asked to verify their age with MitID in Checkout. If the verification succeeds, the customer can continue the payment. If the verification fails, the payment cannot be completed.
Example: Start age verification before payment
This example creates a payment session with age verification and shows a button that starts the verification flow.
Before you start
You need:
- An API key
- A Point of Sale ID
- A backend endpoint that can create payments
- A success URL
- A failure URL
- A notification URL
- The minimum age required for the purchase
- The delivery country for the age verification
Your API key must only be used server-side. Never expose your API key in frontend code.
Step 1: Create an order in your system
Before creating the payment in ePay, create an order in your own system.
Set the order status to pending.
const order = await createOrder({ amount: 10000, currency: "DKK", status: "pending", requiresAgeVerification: true, minimumAge: 18,});The order should not be marked as paid yet.
Step 2: Create a payment session with age verification
Create a payment session from your backend and include the ageVerification object.
/public/api/v1/citInitializes a new payment session with the ePay Payments API.const body = JSON.stringify({ "pointOfSaleId": "<POINT_OF_SALE_ID>", "amount": 10000, "currency": "DKK", "reference": "ORDER-1001", "notificationUrl": "https://example.com/api/epay/notification", "successUrl": "https://example.com/payment/success", "failureUrl": "https://example.com/payment/failure", "ageVerification": { "minimumAge": 18, "country": "DK" }})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})Use your own order reference so you can match the ePay payment to the order in your system.
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 ageVerification object tells ePay that the customer must meet a minimum age before the payment can be completed.
Step 4: Understand the age verification object
Use:
ageVerification.minimumAgefor the required minimum age. It must be between0and99.ageVerification.countryfor the delivery country as an ISO 3166 alpha-2 country code such asDK.
The country determines which electronic ID flow is presented to the customer. For Denmark, this is MitID.
Step 5: Redirect the customer to Checkout
The response contains the information needed to open ePay Checkout.
Redirect the customer to the payment window URL returned by ePay.
return redirect(payment.paymentWindowUrl);The customer now completes the age verification and payment in the ePay-hosted payment page.
Step 6: Complete a test payment
Open Checkout and complete the payment using a test payment method.
If age verification is required, the customer must complete the verification before the payment can continue.
A successful payment should redirect the customer to your success URL.
A failed payment should redirect the customer to your failure URL or allow the customer to try again, depending on your setup.
Step 7: Handle the payment result
Use your notification URL or webhook to update the order in your backend.
Do not mark the order as paid only because the customer lands on your success page.
DocumentationHandle notification URLs and webhooks correctlyUse the full guide for webhook payloads, authorization validation, retries, duplicate handling, and safe order updates.Recommended order flow:
Create order as pending
Create ePay payment session with age verification
Redirect customer to Checkout
Customer completes age verification if required
Customer completes the payment
Receive payment result server-side
Verify the result
Mark order as paid
Step 8: Show the final status to the customer
Your success page should show the customer that the payment was completed or is being confirmed.
If your backend has not processed the payment result yet, show a neutral message such as:
Your payment has been received and is being confirmed.
Avoid showing a final paid state before your backend has confirmed the payment result.
Optional: Avoid repeated age checks for returning customers
For returning customers, you may want to avoid asking for age verification every time they make a purchase.
There are two common ways to do this:
Store the verification result in your own system
Send a unique customerId when creating the payment session
If you store the result in your own system, associate the verification outcome with the customer's profile and use it when deciding whether future payments require age verification.
If you send a unique customerId, ePay can associate the verification result with that customer and reuse it for future payments with the same customer ID.
{ "customerId": "CUSTOMER-1001", "ageVerification": { "minimumAge": 18, "country": "DK" }}Only use stable customer IDs that identify the same customer across payments.
Common problems
I get unauthorized when creating the payment
Check that:
- You are using the correct API key
- The API key belongs to the correct environment
- The request is made from your backend
- The authorization header is formatted correctly
The customer is not asked to verify age
Check that:
- The
ageVerificationobject is included in the payment session request minimumAgeis set correctlycountryis set correctly- The payment method supports the expected verification flow
- The customer has not already been verified through a reusable customer ID
The payment is declined during age verification
Check that:
- The customer meets the required minimum age
- The correct delivery country is used
- The customer completed the required electronic ID flow
- The selected payment method supports the age verification flow
The customer returns, but my order is not paid
Check that:
- Your notification URL is publicly available
- Your backend receives the notification
- Your backend matches the payment to the correct order
- Your system updates the order based on the server-side result
Returning customers are asked to verify age again
Check that:
- You send the same
customerIdfor the same customer - The customer ID is stable across payments
- Your own system stores and reuses the verification result if you handle this locally
- The payment session still includes the correct age verification requirements
What you built
You have now created your first ePay Verify payment.
You created a payment session with age verification, redirected the customer to ePay Checkout, completed the verification flow and handled the payment result server-side.