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

With Checkout

Use this guide if you want ePay to host the payment window.

Checkout is the simplest custom integration. Your backend creates a payment session, and ePay returns a payment window URL. You redirect the customer to that URL so they can complete the payment.

What you will build

In this guide, you will:

Create a payment session from your backend

Redirect the customer to ePay Checkout

Complete a test payment

Receive the payment result

Confirm that your order can be updated correctly

How Checkout works

Checkout uses a hosted payment window.

Your system creates the payment. ePay handles the payment page.

The basic flow is:

Customer starts checkout on your website

Your backend creates a payment session with ePay

ePay returns a payment window URL

Your website redirects the customer to the payment window

Customer completes the payment

ePay redirects the customer back to your website

ePay sends the payment result to your backend

Example: Open the hosted Checkout page

This example creates a payment and shows a button that opens the returned Checkout URL.

script.js
// -------------------------------// Payment Request Setup// -------------------------------// Create headers for the payment requestconst headers = new Headers();const demoApiKey = "test_93dc98ae-ff87-4671-a821-cda33fdfc26e"; // Demo API key: NEVER use this client-side in productionheaders.append("Content-Type", "application/json");headers.append("Accept", "application/json");headers.append("Authorization", `Bearer ${demoApiKey}`);// Define the payment payload dataconst paymentData = {  pointOfSaleId: "0195a362-5bf4-7bb9-a419-2367fda11e6f",  amount: 100, // minor amount  currency: "DKK"};// Setup the fetch optionsconst requestOptions = {  method: "POST",  headers: headers,  body: JSON.stringify(paymentData),  redirect: "follow"};// -------------------------------// Helper: Create Payment Window Button// -------------------------------function ensureCheckoutDemoLayout() {  const existingRoot = document.getElementById("payment-link-root");  if (existingRoot) return existingRoot;  const main = document.createElement("main");  main.className = "checkout-demo";  const card = document.createElement("div");  card.className = "checkout-demo__card";  const eyebrow = document.createElement("p");  eyebrow.className = "checkout-demo__eyebrow";  eyebrow.textContent = "Hosted checkout example";  const root = document.createElement("div");  root.id = "payment-link-root";  card.appendChild(eyebrow);  card.appendChild(root);  main.appendChild(card);  document.body.appendChild(main);  return root;}/** * Creates and appends a payment button to the DOM. * The button text is set dynamically using the converted amount (major) and currency. * * @param {number} amount - The payment amount (in minor units). * @param {string} currency - The currency code. * @param {string} paymentWindowLink - Link to the ePay Payment Window. */function createPaymentLink(amount, currency, paymentWindowLink) {  // Convert minor amount to major amount (e.g., 100 -> 1.00)  const majorAmount = (amount / 100).toFixed(2);  const paymentLink = document.createElement("a");  paymentLink.classList.add("payment-link");  paymentLink.textContent = `Pay ${majorAmount} ${currency}`;  paymentLink.href = paymentWindowLink;  paymentLink.target = "_blank";  const root = ensureCheckoutDemoLayout();  root.appendChild(paymentLink);}// -------------------------------// Payment Request// -------------------------------fetch("https://payments.epay.eu/public/api/v1/cit", requestOptions)  .then((response) => response.json())  .then((result) => {    console.log("Payment response:", result);    // Extract Payment details from the response    const amount = result.session.amount;    const currency = result.session.currency;    const paymentWindowLink = result.paymentWindowUrl;    // Create the payment link using dynamic payment details    createPaymentLink(amount, currency, paymentWindowLink);  })  .catch((error) => {    console.error("Error during payment request:", error);  });

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

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

For the standard Checkout redirect flow, your backend authenticates the session creation request with your API key.

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",});

The order should not be marked as paid yet.

Step 2: Create a payment session

Create a payment session from your backend.

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": 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",  "scaMode": "NORMAL",  "timeout": 60,  "instantCapture": "OFF",  "maxAttempts": 3})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 actual ePay endpoint and the fields your checkout flow requires.

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.

pointOfSaleId
Id of the point of sale to associate the payment with.
amount
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.
currency
The currency code of the payment using ISO 4217 alpha-3, for example DKK.
reference?
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?
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?
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?
How long the session stays valid in minutes.

Type

Minimum 1, maximum 120.
instantCapture?
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?
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?
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?
Maximum allowed payment attempts for the session.

Type

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

Default

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

Default

false
dynamicAmount?
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?
The URL where ePay sends payment status notifications and webhooks.

Type

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

Type

Maximum length 1024.
returnUrl?
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?
Where the customer is redirected after a successful payment.

Type

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

Type

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

Type

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

Type

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

Type

Maximum total size is 1kb.
customerId?
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?
Customer details used during SCA / 3DS and by payment methods such as Klarna.

Type

See the customer table below.
orderLines?
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?
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?
Requires the customer to verify that they meet a minimum age before the transaction can continue.

Type

See the ageVerification table below.
customer
firstName?
The first name of the paying customer.
lastName?
The last name of the paying customer.
birthdate?
The date of birth of the customer in YYYY-MM-DD format.
email?
The email address of the paying customer.
phoneNumber?
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?
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?
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[]
description
Descriptive name of the order line item.
imageUrl?
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.
quantity
Quantity of the order line item.

Type

Must be a non-negative number.
totalAmount
Total order line amount in minor units, including tax and discounts.
unitPrice
Single-item price in minor units, before discount and typically including taxes.
type
Type of the order line item.
subscription
id?
Optional subscription ID to update an existing active subscription with new payment details.
amount?
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.
type
Subscription type.

Type

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

Type

Contains period (DAY, WEEK, MONTH, YEAR) and frequency (1-31). Omit for UNSCHEDULED.
billingAgreement?
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
minimumAge
Minimum required age for the purchase.

Type

Minimum 0, maximum 99.
country?
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.

Step 4: Inspect the response

The response contains the values needed to continue the flow.

For Checkout, the important field is paymentWindowUrl, because that is the URL you redirect the customer to. The javascript and key fields are mainly relevant for Blocks or other embedded flows. If you set generateQrCode to true, the response also includes qrCode.

{  "paymentWindowUrl": "https://payments.epay.eu/payment-window?sessionId=0192473a-e382-79a9-bfc2-65da88fe812f&sessionKey=4651656e-f29e-4dfa-a1cd-a65647862011",  "javascript": "https://payments.epay.eu/sessions/0192473a-e382-79a9-bfc2-65da88fe812f/client.js",  "key": "4651656e-f29e-4dfa-a1cd-a65647862011",  "session": {    "id": "0192473a-e382-79a9-bfc2-65da88fe812f",    "subscriptionId": "01929a94-5fce-7ccc-a7e4-7e9249133b39",    "amount": 1000,    "attributes": { "key1": "value1", "key2": "value2" },    "exemptions": ["TRA"],    "currency": "DKK",    "instantCapture": "OFF",    "maxAttempts": 3,    "reportFailure": false,    "dynamicAmount": false,    "pointOfSaleId": "0192473a-e381-705c-b61c-fc2ac9624afc",    "reference": "ORDER-1001",    "state": "PENDING",    "scaMode": "NORMAL",    "timeout": 60,    "notificationUrl": "https://example.com/api/epay/notification",    "preAuthUrl": "https://example.com/pre-auth",    "successUrl": "https://example.com/payment/success",    "failureUrl": "https://example.com/payment/failure",    "textOnStatement": "The text",    "createdAt": "2024-10-01T10:38:14.658688472+02:00",    "expiresAt": "2024-10-01T12:41:14.658688472+02:00"  }}
qrCode?
Base64 QR code version of paymentWindowUrl. Present only when generateQrCode was true in the request.
paymentWindowUrl
The direct URL to the hosted ePay Checkout page for this payment session.
javascript
The ePay JavaScript client URL, mainly used by Blocks and other embedded flows.
key
The session-specific client secret used to authenticate client-side calls for embedded flows.
session
Representation of the created payment session in ePay.

Type

See the session table below.

session

id
The payment session ID.
subscriptionId?
Subscription ID related to the session when the flow creates or updates a subscription.
amount
The session amount in minor units.
attributes?
Pass-through data returned back to the merchant on webhooks.
exemptions?
Applied exemptions, if any.
allowedPaymentMethods?
Allowed payment methods configured for the session, if the request restricted them.
currency
The payment currency.
expiresAt
When the session expires and can no longer be used.
instantCapture?
The applied instant capture mode.
maxAttempts?
Maximum allowed payment attempts for the session.
attempts?
How many payment attempts have been made so far.
minimumAge?
Minimum required age when age verification is enabled for the session.
ageVerified?
Indicates whether the customer has verified they meet the minimum age requirement.
reportFailure?
Whether failed transactions are reported to notificationUrl for this session.
reportExpired?
Whether expired sessions are reported to notificationUrl for this session.
dynamicAmount?
Whether the session allows the client to determine the amount dynamically.
notificationUrl?
Webhook URL for transaction attempts.
preAuthUrl?
Optional URL receiving a webhook just before authorization is attempted.
successUrl?
Redirect URL for successful payments.
returnUrl?
Back-button return URL used by the payment window, if configured.
failureUrl?
Redirect URL for failed payments when no more attempts are possible.
retryUrl?
Redirect URL for failed payments when more attempts are still allowed.
customerId?
Merchant-defined customer ID, if provided.
pointOfSaleId?
The associated point of sale ID.
reference?
The merchant reference, typically the order ID.
state?
The current session state.
textOnStatement?
Text shown on the cardholder bank statement, if configured.
scaMode?
The applied SCA mode.
timeout?
Session timeout in minutes.
createdAt?
When the session was created.

Step 5: Redirect the customer

The response contains the information needed to open the payment window.

Redirect the customer to the payment window.

return redirect(payment.paymentWindowUrl);

The customer now completes the payment on the ePay-hosted payment page.

Optional: Use URL templating

Redirect and notification URLs can include dynamic values from the session or transaction.

This is especially useful for notificationUrl, successUrl, failureUrl, retryUrl, and returnUrl.

Example:

{  "successUrl": "https://example.com/payment/success/${transaction.id}",  "failureUrl": "https://example.com/payment/failure/${session.id}",  "notificationUrl": "https://example.com/api/epay/notification/${transaction.reference}"}

Supported template values include:

KeyDescription
${transaction.id}The ID of the transaction
${transaction.reference}The reference supplied when creating the session
${session.id}The ID of the payment session

Unknown template keys are rejected during request validation.

Optional: Override the default payment window

In some cases, you may want to use a specific payment window configuration instead of the default configuration defined on your Point of Sale.

This lets you choose another visual or functional checkout setup for a single payment session without changing the POS defaults in the ePay backoffice.

When you append paymentWindowId to the returned paymentWindowUrl, the payment window configuration associated with that ID is used instead of the POS default, as long as the payment window belongs to the same account as the session.

Common use cases:

  • Apply campaign-specific branding or themes
  • Use a simplified flow for selected customer groups
  • Test a new payment window configuration without updating the POS default

Example:

https://payments.epay.eu/payment-window?sessionId=0192473a-e382-79a9-bfc2-65da88fe812f&sessionKey=4651656e-f29e-4dfa-a1cd-a65647862011&paymentWindowId=01234567-89ab-cdef-0123-456789abcdef

Important notes:

  • The provided paymentWindowId must belong to the same account as the Point of Sale used for the payment session.
  • If paymentWindowId is invalid or does not exist, the default payment window configuration is used.
  • The override happens entirely by modifying the returned paymentWindowUrl; you do not need to change the session initialization request.

Step 6: Complete a test payment

Use a test card to complete the payment.

A successful test 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

The customer may close the browser before returning to your website.

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

Redirect customer to Checkout

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.

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

If the request still fails, use the error code and response body to identify whether the issue is authentication, validation, or Point of Sale configuration.

GuideError CodesReference the most common ePay API error codes, their meaning, and how they should be handled in client integrations.

The payment window does not open

Check that:

  • The payment session was created successfully
  • You are redirecting to the correct payment window URL
  • The Point of Sale ID is correct
  • The amount and currency are valid

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

What you built

You have now created your first ePay Checkout payment.

You created a payment session from your backend, redirected the customer to ePay and completed a test payment.

Next steps

How is this guide?

On this page