Skip to content

Integrate · Hosted Payment Page

Accept card payments without touching card data

Create a session on your server, redirect the customer to a checkout hosted by Finrelay, and bring them back to your success, cancel or error URL.

1 Your checkout Built by you · creates the session
2 Hosted Payment Page Hosted by Finrelay · collects card details PCI DSS
3 Your redirect URLs Built by you · success, cancel or error
Finrelay PCI DSS environment Your systems

What your customer sees

The page keeps one layout for every merchant. The numbered parts change per merchant — try the controls on the right.

Illustrative preview with test data.
  1. Your logoUpload it in the Merchant Portal. It replaces the Finrelay logo at the top of the page.
  2. Brand colorsSet the colour scheme and font per merchant in the Merchant Portal.
  3. WalletsApple Pay and Google Pay appear only when they are enabled on the merchant terminal.
  4. Payment methodsMethods depend on the terminal configuration. Saved Cards appear when you pass a customer_id that has saved cards.
  5. LanguageEnglish, Bosanski, Română or Svenska. Set a default per merchant, override it per session with language, and customers can switch on the page.

Compare integrations

How the three integrations differ.

Hosted Payment Page Embedded Fields API Integration
Checkout runs on Finrelay Your page Your page
PCI DSS scope Lowest Reduced Full
Customization Logo, colours, language CSS variables Full UI control
Effort Lowest Medium Highest

Integrate in four steps

Get an access token

Use the OIDC Client Credentials flow with your client ID and secret.

Create a payment session

Send the order reference, amount in minor units, currency and your success_url, cancel_url and error_url.

Redirect the customer

Send them to the redirect_url from the response.

Confirm the result

The customer returns to one of your redirect URLs. Verify the final status before fulfilling the order.

POST /api/hosted-payment-page
curl -X POST https://api.finrelay.io/api/hosted-payment-page \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "reference": "ORDER-123456",
    "terminal_id": "TERM001",
    "description": "Order payment",
    "amount": 4990,
    "currency": "EUR",
    "transaction_type": "PURCHASE",
    "success_url": "https://shop.example/success",
    "cancel_url": "https://shop.example/cancel",
    "error_url": "https://shop.example/error"
  }'
POST /api/hosted-payment-page
const res = await fetch('https://api.finrelay.io/api/hosted-payment-page', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    reference: 'ORDER-123456',
    terminal_id: 'TERM001',
    description: 'Order payment',
    amount: 4990, // minor units
    currency: 'EUR',
    transaction_type: 'PURCHASE',
    success_url: 'https://shop.example/success',
    cancel_url: 'https://shop.example/cancel',
    error_url: 'https://shop.example/error',
  }),
});

const { redirect_url } = await res.json();

Hosted Payment Page API

The Hosted Payment Page (HPP) allows you to create secure, preconfigured payment sessions that redirect customers to a checkout interface for completing a transaction.


Authentication

All requests require a valid OIDC access token:

HTTP Header

Authorization: Bearer <access_token>
Content-Type: application/json

Obtain the token using OIDC flows (typically Client Credentials or Authorization Code flow).


Backend Setup

1) Obtain an authentication token.

Use the OIDC Client Credentials flow. See Obtain Access Token for endpoint details.

function createToken() {
  return loadJsonFile('[path]/secret.json')
    .then((secretData) => {
      const params = new URLSearchParams();
      params.append('grant_type', 'client_credentials');
      params.append('client_id', secretData.clientId);
      params.append('client_secret', secretData.clientSecret);

      return fetch(
        secretData.tokenEndpoint, // the OIDC Token URL for your environment — see Getting Started > Environments
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
          body: params.toString()
        }
      ).then(response => {
        if (!response.ok) {
          throw new Error(`Network response was not ok: ${response.status}`);
        }
        return response.json();
      })
    })
}

2) Create a session using the access_token.

Note

The example below posts to /api/sessions, which is the session-creation endpoint used by Embedded Fields and accepts a return_url plus a customer object. It is a different endpoint from /api/hosted-payment-page, documented under Endpoint below, which the rest of this page describes. Use /api/hosted-payment-page for Hosted Payment Page integrations.

function createSession(data) { // data -> result from token endpoint
  const randomString = `date-${new Date().getTime()}`;
  return loadJsonFile('[path]/secret.json')
    .then((secretData) => {
      return fetch(
        '/api/sessions',
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': `Bearer ${data?.access_token}`
          },
          body: JSON.stringify({
            "terminal_id": "DEV02",
            "reference": randomString,
            "description": "f2aed380-b8bb-481f-92be-3060cc1ed4a2",
            "currency": "EUR",
            "amount": 25600,
            "transaction_type": "PURCHASE",
            "return_url": "https://google.com/return-url",
            "customer": {
              "first_name": "John",
              "last_name": "Doe",
              "address": "Address",
              "city": "City",
              "country": "BA",
              "postal_code": "10000",
              "email": "email@email.com",
              "phone": "00000"
            },
            "country": "BA",
            "metadata": {
              "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE",
              "response_code": "APPROVED",
              "payment_provider_response_message": "approved",
              "payment_provider_response_code": "approved-code",
              "approval_code": "N/A"
            }
          })
        }
      ).then(response => {
        if (!response.ok) {
          throw new Error(`Network response was not ok: ${response.status}`);
        }
        return response.json(); // session id
      })
    })
}

Endpoint

POST /api/hosted-payment-page

Creates a hosted payment session and returns a URL for redirection.


Request Body

{
  "reference": "ORDER-123456",
  "terminal_id": "TERM001",
  "currency": "EUR",
  "amount": 100,
  "transaction_type": "PURCHASE",
  "description": "Order payment",
  "success_url": "https://merchant.example.com/success",
  "cancel_url": "https://merchant.example.com/cancel",
  "error_url": "https://merchant.example.com/error",
  "customer_id": "CUST123456",
  "customer_first_name": "John",
  "customer_last_name": "Doe",
  "customer_email": "john.doe@example.com",
  "customer_phone_number": "+1234567890",
  "customer_address": "123 Example Street",
  "customer_city": "Sampletown",
  "customer_country": "BA",
  "customer_postal_code": "12345",
  "language": "en",
  "originator_url": "https://shop.merchant.com/checkout",
  "options": {
    "number_of_instalments": 1,
    "skip_three_ds": false,
    "cvv_less": false
  },
  "tokenization": {
    "save_card_for_future_payments": true
  }
}

Required Fields

Field Type Description
reference string Unique reference for the order. Maximum 40 characters.
terminal_id string ID of the terminal used for payment
description string Payment description shown on HPP. 1–100 characters.
currency string Currency in ISO 4217 format (e.g., "EUR")
amount integer Amount in minor units (e.g., 100 = €1.00). Must be an integer — decimal or fractional values (e.g., 1.00 or "1.00") are rejected at request validation.
transaction_type string Must be "PURCHASE" or "AUTHORIZE"
success_url string Redirect URL after a successful payment
cancel_url string Redirect URL after the customer cancels the payment
error_url string Redirect URL after a failed or errored payment

Optional Fields

Field Type Description
customer_id string Payment Platform customer ID for saved cards (required to use saved payment methods)
customer_first_name string Customer's first name
customer_last_name string Customer's last name
customer_email string Email address
customer_phone_number string Phone number in E.164 format (e.g., +12125551234). Validated using Google libphonenumber isPossibleNumber — the number must be a plausible length for its country code.
customer_address string Street address
customer_city string City name
customer_country string ISO 3166-1 alpha-2 code (e.g., "BA")
customer_postal_code string ZIP/postal code
language string ISO code for the HPP's initial locale. See Supported Languages.
originator_url string URL of the website or page on which the customer started the purchase. http/https only, a # fragment is allowed. If omitted or empty, the terminal website is used. Kept when the transaction is retried or routed to another provider, and forwarded to providers that require it.
options.number_of_instalments integer Number of instalments offered for the payment, where supported
options.skip_three_ds boolean If true, skips 3-D Secure authentication for this payment
options.cvv_less boolean If true, allows payment without a CVV
tokenization.save_card_for_future_payments boolean If true, automatically saves the card for future use
tokenization.show_save_card_for_future_payments boolean If true, displays a checkbox allowing customers to opt-in to save card

Response

{
  "redirect_url": "https://api.example.com/hpp/payment/TO6FjoePBl0GoiAZI6IJeAYIuUanvAmaW1YsBpbj/GfXYVKxPXGksQhBJuyAZ",
  "session_id": "GfXYVKxPXGksQhBJuyAZ"
}
Field Type Description
redirect_url string Fully qualified URL to which the customer should be redirected for payment
session_id string Unique identifier for the hosted payment session

URL Structure

The redirect_url follows this pattern:

https://<base-url>/hpp/payment/<client-key>/<session-id>

The HPP application automatically extracts the client-key and session-id from the URL path. Simply redirect your customer to the redirect_url returned in the response.


Tokenization Notes

If tokenization.save_card_for_future_payments = true is used:

  • Tokenization must be enabled at the merchant level (contact your payment platform administrator to enable this feature).
  • If unsupported, the response will include:
{
  "status": "INVALID",
  "errors": [
    {
      "property": "tokenization",
      "message": "one of requested features not supported=[TOKENIZATION]"
    }
  ]
}

For detailed information on the two methods of saving cards, see the Saving Cards for Future Payments section below.


Saving Cards for Future Payments

The HPP supports two methods for saving payment cards for future use:

Feature show_save_card_for_future_payments save_card_for_future_payments
Displays checkbox Yes No
Customer consent Explicit (opt-in) Implicit
Use case Customer choice Subscription/recurring payments

Method 1: show_save_card_for_future_payments

This parameter displays a checkbox on the HPP allowing customers to opt-in to saving their card.

Request Body Example:

{
  "reference": "ORDER-123456",
  "terminal_id": "TERM001",
  "description": "Order payment",
  "currency": "EUR",
  "amount": 100,
  "transaction_type": "PURCHASE",
  "success_url": "https://merchant.example.com/success",
  "cancel_url": "https://merchant.example.com/cancel",
  "error_url": "https://merchant.example.com/error",
  "customer_id": "CUST123456",
  "tokenization": {
    "show_save_card_for_future_payments": true
  }
}

When show_save_card_for_future_payments is set to true, a checkbox appears on the payment page allowing the customer to choose whether to save their card for future payments.

Method 2: save_card_for_future_payments

This parameter implicitly saves the card without displaying a checkbox to the customer.

Request Body Example:

{
  "reference": "ORDER-123456",
  "terminal_id": "TERM001",
  "description": "Order payment",
  "currency": "EUR",
  "amount": 100,
  "transaction_type": "PURCHASE",
  "success_url": "https://merchant.example.com/success",
  "cancel_url": "https://merchant.example.com/cancel",
  "error_url": "https://merchant.example.com/error",
  "customer_id": "CUST123456",
  "tokenization": {
    "save_card_for_future_payments": true
  }
}

When save_card_for_future_payments is set to true, the card is automatically saved without requiring customer interaction.

Compliance Note: When using save_card_for_future_payments (implicit saving), ensure your terms of service and privacy policy inform customers that payment information will be stored for future use. Depending on your jurisdiction, this may have regulatory implications (GDPR, PCI-DSS, etc.).

Note: If both show_save_card_for_future_payments and save_card_for_future_payments are set to true, the save_card_for_future_payments behavior takes precedence and the card is saved without displaying a checkbox.

Requirements for Card Tokenization

For both methods to work, the following requirements must be met:

  1. Tokenization must be enabled at the merchant level
  2. Customer must exist: A customer with the specified customer_id must already exist in the Payment Platform before creating the HPP session (if customer_id is provided). If the customer doesn't exist, the API will return a validation error.

Assigning Saved Payment Methods to a Customer

When a card is successfully saved (via either method):

  • With customer_id: The saved payment method is automatically assigned to the specified customer_id. The customer record must already exist in the Payment Platform before creating the HPP session.
  • Without customer_id: The payment method is still saved but remains unassociated with any customer. These payment methods can be retrieved via API but cannot be associated with a customer later.

Important Notes:

  • Payment methods saved with a customer_id are linked to that customer profile
  • Payment methods saved without a customer_id remain permanently unassociated
  • To save a payment method for a specific customer, include the customer_id in the initial HPP request

Paying with Saved Cards

Customers who have saved payment methods can use them for future payments by providing their customer_id.

Using Saved Cards via HPP

When a customer_id is included in the HPP request, and that customer has saved payment methods, the HPP will display a "Saved Cards" payment option allowing the customer to select from their previously saved cards.

Request Body Example:

{
  "reference": "ORDER-789012",
  "terminal_id": "TERM001",
  "description": "Order payment",
  "currency": "EUR",
  "amount": 5000,
  "transaction_type": "PURCHASE",
  "success_url": "https://merchant.example.com/success",
  "cancel_url": "https://merchant.example.com/cancel",
  "error_url": "https://merchant.example.com/error",
  "customer_id": "CUST123456"
}

Using Saved Cards via API

Saved payment methods can also be used directly through the API by referencing the payment method token. See the API Integration documentation for details on using saved payment methods with direct API calls.

Important: Customer ID Required

It is not possible to pay with a saved card if no customer_id is provided in the request.

  • Without a customer_id, the system cannot retrieve saved payment methods
  • Always include the customer_id field when you want to enable saved card payment options

Misconfiguration Handling

If the link is invalid (e.g., no supported payment methods), the system returns a user-friendly message:

"This payment is currently unavailable. Please contact the merchant."


Redirects and Callbacks

The HPP handles redirects automatically based on the URLs configured in your session request:

  • On a successful payment, the customer is redirected to success_url.
  • If the customer cancels the payment, they are redirected to cancel_url.
  • If the payment fails or errors out, the customer is redirected to error_url.

All three URLs are required when creating the HPP session.


Best Practices

  • Use the redirect_url from the response: After creating an HPP session, redirect the customer to the redirect_url returned in the response. The HPP extracts all required parameters from the URL automatically.
  • Authenticate properly: First obtain an auth token, then create the session with the Authorization header.
  • Configure the redirect URLs: Always provide success_url, cancel_url, and error_url to handle the post-payment redirects.

Troubleshooting

  • Any issues during HPP setup are shown during the flow with descriptive errors and guidance.

Customization

The Hosted Payment Page can be customized through the Merchant Portal dashboard. Available customizations include your logo, font, colour scheme and the display language (see Supported Languages). Contact your account manager for details on available customization options.


Supported Languages

The Hosted Payment Page is available in the following languages:

Code Language Displayed as
en English English
bs Bosnian Bosanski
ro Romanian Română
sv Swedish Svenska

Setting the Default Language

The default language is configured per merchant in the Merchant Portal dashboard. Once set, every Hosted Payment Page created for that merchant opens in that language unless overridden per session.

To set it, go to Accounts → Merchants in the Merchant Portal, open the merchant, and select the Language in the Hosted Payment Page customizations section. It sits alongside the other Hosted Payment Page customizations for that merchant — logo, font family, font URL and colours.

If no language is configured, the page opens in English (en).

Setting the Language per Session

The optional language field in the session request body sets the HPP's initial locale for that session, overriding the merchant-level default. It accepts one of the language codes listed above.

Customer Language Selection

Customers can change the language themselves using the language selector on the payment page. All supported languages are offered, and each one is listed in its own language (for example Svenska for Swedish), so customers can find their language regardless of which one the page is currently displaying.

If a language that is not supported is configured, the page falls back to English rather than failing to load.

Note

If a language you need is not listed in the Language dropdown, contact your account manager to have it enabled for your account.


Sample cURL Request

curl -X POST https://api.example.com/api/hosted-payment-page \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "ORDER-123456",
    "terminal_id": "TERM001",
    "description": "Order payment",
    "currency": "EUR",
    "amount": 100,
    "transaction_type": "PURCHASE",
    "success_url": "https://merchant.example.com/success",
    "cancel_url": "https://merchant.example.com/cancel",
    "error_url": "https://merchant.example.com/error",
    "customer_id": "CUST123456",
    "tokenization": {
      "save_card_for_future_payments": true
    }
  }'

Go further

Save cards for future payments

Let customers opt in, or always save the card.

Read guide

3DS card payment

A complete recipe with challenge flow.

Open recipe

Webhooks

Get notified when a payment changes status.

View reference