Accept payments via Hosted Checkout Page - API

Welcome to the API integration guide for Hosted Checkout Pages! This guide walks you through creating payment links programmatically using the Merchant API, implementing order management, and handling the complete payment lifecycle—from order creation to fulfilment.

This guide covers everything you need for a production-ready integration, including the recommended 3-layered architecture, webhook implementation, abandoned order handling, and best practices for order fulfilment. The solution is web-based and works seamlessly on both desktop and mobile browsers, making it a versatile option for accepting payments without requiring native app integration.

Hosted Checkout Page - Checkout page

Tip

Prefer a visual interface? Check out the Payment link guide for creating payment requests directly from the Revolut Business dashboard without coding.

How it works

Implementing Hosted Checkout Page via the API requires the following components:

  1. Your order management system: Your internal e-commerce platform that handles orders, inventory, fulfilment, and customer management.
  2. Merchant API integration: Your backend integrates with the Merchant API to create orders and receive unique checkout_url links for each transaction.
  3. Status tracking mechanism: A webhook server (recommended) or polling mechanism that monitors order status changes in the Merchant API and triggers appropriate actions in your order management system.

This architecture ensures clean separation of concerns: Revolut handles the checkout experience and payment processing, while you maintain full control over your business logic, inventory, and fulfilment workflows.

Payment flow

The typical payment flow involves these steps:

  1. Order creation: Your server creates an order via the Merchant API: Create an order endpoint and receives a checkout_url.
  2. Customer checkout: You redirect the customer to the checkout_url or share it with them via email/SMS.
  3. Payment processing: Customer completes payment on the Revolut-hosted checkout page using their preferred payment method. If payment fails, the Hosted Checkout Page automatically handles retries.
  4. Post-payment: Customer is redirected to a success page (default Revolut page or your custom redirect_url).
  5. Status notification: Your server receives webhook notifications for payment events (recommended), or polls the order status.
  6. Order fulfilment: Your server verifies the final payment status (completed or authorised) and fulfils the order through your order management system.

Customise your checkout page

You can customise the branding of your Hosted Checkout Pages (logo, cover image, button colours, website) through the Revolut Business dashboard. This customisation applies to all checkout pages created via both Payment link and API methods.

Info

For detailed instructions on customising your checkout page, see the Payment link guide: Customise your checkout page.

Implementation overview

Check the following high-level overview on how to implement Hosted Checkout Page via the API:

  1. Create an order
  2. Get customers to the checkout URL
  3. Track order and payment status
  4. Use webhooks for fulfilment

Before you begin

Before you start this tutorial, ensure you have completed the following steps:

Implement Hosted Checkout Page

This section walks you through the API implementation step by step.

1. Create an order

To create a Hosted Checkout Page, you need to create an order using the Merchant API: Create an order endpoint. This endpoint accepts order details and returns an order object containing the checkout_url and id.

Caution

Order creation must always be performed on your server. This is a critical security requirement because the request requires your Secret API Key, which must never be exposed in client-side code (frontend).

Required parameters:

ParameterDescription
amountThe payment amount in the lowest denomination
currencyISO 4217 currency code

Useful optional parameters:

ParameterDescription
descriptionDescription of the order (visible to the customer)
customer.emailCustomer email address for sending receipts
redirect_urlCustom URL to redirect customers after successful payment
merchant_order_data.referenceYour internal order reference/ID for correlating Revolut orders with your system

Example response:

The API returns a JSON object representing the created order. The most important field for this integration is checkout_url.

{
"id": "6516e61c-d279-a454-a837-bc52ce55ed49",
"token": "0adc0e3c-ab44-4f33-bcc0-534ded7354ce",
"type": "payment",
"state": "pending",
"created_at": "2023-09-29T14:58:36.079398Z",
"updated_at": "2023-09-29T14:58:36.079398Z",
"amount": 1000,
"currency": "GBP",
"outstanding_amount": 1000,
"capture_mode": "automatic",
"checkout_url": "https://checkout.revolut.com/payment-link/0adc0e3c-ab44-4f33-bcc0-534ded7354ce",
"enforce_challenge": "automatic"
}
ParameterDescription
idThe order ID. Save this to track its status or retrieve order details later.
checkout_urlThe unique URL for this payment. This is where your customer can complete the payment.

Custom post-payment redirection (optional)

By default, customers are redirected to Revolut's confirmation page after a successful payment. To provide a branded post-payment experience and handle the flow on your own website, you can specify a custom redirect_url when creating the order.

Info

The redirect_url is only triggered after successful payments. When payment attempts fail, customers remain on the checkout page, allowing them to retry with a different payment method.

When to use custom redirection:

  • You want to show a branded success page
  • You need to display order-specific information after payment
  • You want to guide customers to the next step in your workflow (e.g., download page, account dashboard)

Example payload with custom redirect:

{
"amount": 1000,
"currency": "GBP",
"description": "Invoice #12345",
"redirect_url": "https://example.com/payment-confirmation"
}
Note

Custom redirection is only supported via the API. Payment links generated via the GUI always use Revolut's self-hosted confirmation pages.

How it works:

  1. Customer completes payment successfully on the Revolut-hosted checkout page.
  2. Customer is automatically redirected to your specified redirect_url.
  3. Your redirect page should verify the payment status server-side using the Retrieve an order endpoint before displaying confirmation.
  4. Display the appropriate success message and next steps to the customer.
Warning

Never rely solely on the redirect for critical business logic like order fulfilment. Always verify payment status server-side and use webhooks to trigger fulfilment workflows.

2. Get customers to the checkout URL

Once you have the checkout_url, you need to direct your customers to it. There are two main approaches depending on your use case:

Use this approach when: Customers are already on your website during the checkout process (e.g., e-commerce checkout flows).

After creating the order on your server, redirect the customer to the checkout_url using your frontend logic. Here are some common approaches:

Web (JavaScript):

After calling your backend API to create the order, use the returned checkout_url to redirect:

// Fetch checkout URL from your backend
const response = await fetch('/api/create-order', { method: 'POST' });
const order = await response.json();

// Redirect to checkout URL
window.location.href = order.checkout_url;

// Or open in new tab
window.open(order.checkout_url, '_blank');

HTML:

You can populate the checkout URL dynamically using server-side rendering or JavaScript:

<!-- Option 1: Server-side rendering (e.g., in your template) -->
<a href="{{ checkout_url }}">Pay Now</a>

<!-- Option 2: Set href dynamically with JavaScript -->
<a id="checkout-link" href="#">Pay Now</a>
<script>
// After getting checkout_url from your backend
document.getElementById('checkout-link').href = checkoutUrl;
</script>

<!-- Option 3: Button with JavaScript -->
<button id="pay-button">Proceed to Payment</button>
<script>
document.getElementById('pay-button').addEventListener('click', async () => {
const response = await fetch('/api/create-order', { method: 'POST' });
const order = await response.json();
window.location.href = order.checkout_url;
});
</script>
Tip

These are example approaches to help you get started. You can implement the redirect using any method that works with your technology stack and framework. The key is to ensure customers are directed to the checkout_url returned by the API.

The customer will be taken to the Revolut-hosted checkout page, complete payment, and then be redirected back to your site (if you've configured a redirect_url).

3. Track order and payment status

Understanding the status of your orders and payments is crucial for implementing proper business logic. The Merchant API provides detailed information about both order-level and payment-level statuses.

Understanding orders and payments

An order represents a customer's purchase intent and contains one or more payment attempts. A single order can have multiple payment objects if the customer's initial payment attempt fails, and they retry.

Info

For more information about the order and payment lifecycle in the Merchant API, see: Order and payment lifecycle.

Retrieve order status

Use the Retrieve an order endpoint to check the overall order status and corresponding payment attempts:

{
"id": "6516e61c-d279-a454-a837-bc52ce55ed49",
"state": "completed",
"amount": 1000,
"currency": "GBP",
"outstanding_amount": 0,
"payments": [
{
"id": "8f7d3c2b-1e4a-4b9c-8d6e-2f5a7c9e1b3d",
"state": "completed",
"amount": 1000,
...
}
],
...
}

Order states:

StateDescriptionFinal state
pendingOrder created, awaiting payment attempt
processingPayment attempt in progress
authorisedPayment authorised (for manual capture mode) (fulfilment trigger for manual capture)
completedPayment successfully captured and settled (fulfilment trigger)
cancelledOrder cancelled (manually or due to auth expiry)
failedOrder expired without successful payment
Caution

Only trigger fulfilment and business logic when orders reach final states. Do not fulfil orders in pending or processing states as these are transient and may change.

  • For automatic capture mode: Trigger fulfilment on completed state
  • For manual capture mode: Trigger fulfilment on authorised state, then capture the payment manually
Important distinction

Receiving ORDER_PAYMENT_FAILED or ORDER_PAYMENT_DECLINED webhooks does not mean the order has reached a final unsuccessful state. These events indicate individual payment attempt failures, but the customer can retry payment on the same order.

An order only reaches a final unsuccessful state through:

  • Manual cancellation: You cancel via Cancel an orderORDER_CANCELLED webhook → state becomes cancelled
  • Automatic expiration: Order exceeds configured period (default 30 days, or custom via expire_pending_after) → ORDER_FAILED webhook → state becomes failed

Final successful states: completed, authorised Final unsuccessful states: cancelled, failed

Retrieve payment details

For more granular information about specific payment attempts, extract the payment ID from the order response and use the Retrieve payment details endpoint:

{
"id": "8f7d3c2b-1e4a-4b9c-8d6e-2f5a7c9e1b3d",
"order_id": "6516e61c-d279-a454-a837-bc52ce55ed49",
"state": "completed",
"amount": 1000,
"currency": "GBP",
"created_at": "2023-09-29T14:58:36.079398Z",
"updated_at": "2023-09-29T15:02:18.123456Z",
...
}

Payment details provide specific information about why a payment attempt succeeded or failed, which is useful for troubleshooting and customer support.

Automatic error handling

The Hosted Checkout Page automatically handles payment errors and retries, requiring no additional implementation from merchants.

How it works:

  1. Payment fails: When a payment attempt fails (declined card, insufficient funds, etc.), the Hosted Checkout Page displays an error message to the customer.
  2. Automatic retry option: The customer sees a Try again button on the error page.
  3. Retry flow: When the customer clicks Try again, the Hosted Checkout Page automatically restarts the payment flow.
  4. New payment object: This creates a new payment attempt, which appears as a new entry in the order.payments array.
  5. Same order ID: All retry attempts are linked to the same order ID, maintaining transaction consistency.
Info

The checkout_url remains valid and can be reused for multiple payment attempts. Each attempt creates a new payment object in the payments array, allowing you to track the full payment history for a single order.

For merchants:

You don't need to implement any retry logic. Simply monitor the order status through webhooks or polling:

  • Failed attempts: You'll receive ORDER_PAYMENT_FAILED or ORDER_PAYMENT_DECLINED webhook events for each failed attempt.
  • Multiple payments array: When retrieving the order via Retrieve an order, you'll see multiple entries in the payments array if the customer retried.
  • Final state: Only trigger fulfilment logic when the order reaches a final state:
    • completedORDER_COMPLETED webhook
    • authorisedORDER_AUTHORISED webhook (for manual capture mode)
    • cancelledORDER_CANCELLED webhook
    • failedORDER_FAILED webhook (order expires without payment)

4. Use webhooks for fulfilment

Note

Webhooks are the recommended method for tracking payment status in real-time and should be used for all fulfilment logic. While polling is possible, it introduces delays and risks missing critical status changes.

Webhooks provide immediate notifications for order and payment events, eliminating the need for polling and ensuring you never miss a status change. They are essential for:

  • Order fulfilment: Trigger fulfilment workflows immediately upon successful payment
  • Inventory management: Update stock levels in real-time
  • Customer notifications: Send confirmation emails/SMS instantly
  • Abandoned order handling: Receive notifications when orders are cancelled
  • Financial reconciliation: Track all transactions accurately

Relevant webhooks for Hosted Checkout Page

Webhook eventDescriptionWhen sent
ORDER_COMPLETEDOrder successfully paid and captured (trigger fulfilment)Payment captured and settled
ORDER_AUTHORISEDOrder authorised, awaiting capture (for manual capture mode)Payment authorised but not yet captured
ORDER_CANCELLEDOrder cancelledOrder cancelled via API
ORDER_FAILEDOrder expired without successful paymentOrder expiration period elapsed (default 30 days or custom via expire_pending_after)
ORDER_PAYMENT_DECLINEDPayment attempt declined by processorCard declined, insufficient funds, etc.
ORDER_PAYMENT_FAILEDPayment attempt failed (technical or processing error)Technical failure, processing error

Understanding ORDER_CANCELLED

The ORDER_CANCELLED webhook is triggered when:

  • You cancel an order via the Cancel an order endpoint (e.g., due to abandonment detection)
  • An authorised payment expires without being captured (in manual capture mode)
    • Default: Authorised orders automatically cancel after 7 days
    • Custom: Set cancel_authorised_after (ISO 8601 duration format, max 7 days) when creating the order
  • The order is manually cancelled through the Revolut Business dashboard

When you receive this event, your system should:

  • Release any reserved inventory
  • Update your order management system to mark the order as cancelled
  • Trigger any cleanup or notification workflows
  • Ensure fulfilment logic does not process this order

Understanding ORDER_FAILED

The ORDER_FAILED webhook is triggered when an order expires without successful payment. By default, orders expire after 30 days, but you can customise this using the expire_pending_after parameter when creating an order.

When it's triggered:

  • Order remains in pending or processing state beyond the expiration period
  • No successful payment was completed before expiration
  • Order automatically transitions to failed state

Controlling expiration:

Set a custom expiration period when creating an order using expire_pending_after (ISO 8601 duration format):

{
"amount": 1000,
"currency": "GBP",
"expire_pending_after": "PT30M" // 30 minutes (ISO 8601 Duration)
}

Common duration examples:

  • "PT5M" = 5 minutes
  • "PT15M" = 15 minutes
  • "PT1H" = 1 hour
  • "PT24H" = 24 hours
  • "P7D" = 7 days

When you receive this event, your system should:

  • Recognise the order has definitively failed and cannot be completed
  • Release any reserved inventory
  • Update your order management system to mark the order as expired/failed
  • Consider this as an alternative to manual cancellation for abandoned orders (see Handling abandoned orders)
Info
  • ORDER_FAILED represents true abandonment via automatic expiration, while ORDER_CANCELLED represents active intervention (manual cancellation via API or dashboard, or authorised payment expiry).
  • For detailed webhook implementation instructions, see: Use webhooks to track the payment lifecycle.

Alternative: Polling for order status

If you cannot implement webhooks, you can use polling as an alternative, though it's less efficient and introduces delays. With polling, your system periodically checks order status using the Retrieve an order endpoint.

Polling workflow:

  1. Create order: Create an order and store the order id in your system.
  2. Start polling: Begin polling the order status at regular intervals (e.g., every 10-30 seconds).
  3. Check final states: Monitor for final states (completed, authorised, cancelled, failed).
  4. Trigger actions: When a final state is reached, trigger appropriate business logic.
  5. Stop polling: Once a final state is reached, stop polling for that order.

Polling limitations:

  • Delayed notifications: Status changes are only detected during polling intervals
  • Increased API calls: Requires more API requests, especially for high-volume merchants
  • Risk of missing updates: If polling stops prematurely, you may miss state changes
  • Resource intensive: Requires maintaining active polling processes for all orders
Caution

For production implementations, webhooks are strongly recommended over polling. Polling should only be used as a temporary solution or for low-volume scenarios where webhooks cannot be implemented.

Handling abandoned orders

When a customer abandons checkout without completing payment, you need to decide how to manage these orders. Revolut provides two complementary approaches that can be used independently or together, depending on your business needs.

Why abandoned order handling matters

Abandoned orders can:

  • Lock inventory in your system unnecessarily
  • Create confusion in order management and reporting
  • Prevent accurate revenue forecasting
  • Lead to operational inefficiencies

Implementing proper abandonment detection helps you maintain accurate order states and clean up stale orders that will never be completed.

Approaches to handling abandoned orders

You can use one or both of these approaches based on your business requirements:

Best for: Tight inventory control, active order lifecycle management, or when orders sync with your OMS

With manual cancellation, you detect abandoned orders based on your business rules and actively cancel them.

Info

This approach can be combined with automatic expiration for redundancy. See Combining both approaches.

How it works:

  1. Set your detection threshold
  2. Monitor orders via webhooks or polling
  3. Call Cancel an order when threshold exceeded
  4. Handle ORDER_CANCELLED webhook or poll for cancelled state
  5. Update your system (release inventory, etc.)

Best for: Merchants using webhooks for order status tracking

If you're using webhooks to track order status, implement a timer-based detection mechanism:

  1. Start timer on order creation: When you create an order via the API and share the checkout_url, start a timer based on your chosen threshold
  2. Monitor for completion: If you receive an ORDER_COMPLETED or ORDER_AUTHORISED webhook event during this period, cancel the timer
  3. Detect abandonment: If the timer expires without receiving a completion event, mark the order as abandoned in your system
  4. Cancel the order: Call the Cancel an order endpoint to formally cancel the order in Revolut's system
  5. Listen for confirmation: Your webhook endpoint will receive an ORDER_CANCELLED event confirming the cancellation
  6. Update your system: Trigger your order management logic (release inventory, update status, etc.)

Polling-based detection

Best for: Merchants using polling instead of webhooks

If you're polling order status instead of using webhooks, check for abandoned orders periodically:

  1. Track order age: Store the creation timestamp for each order in your system
  2. Periodic polling: Set up a background job that runs periodically (e.g., every 15 minutes)
  3. Check order status: For each order older than your threshold, poll the Retrieve an order endpoint
  4. Detect abandonment: If the order is still in pending or processing state after the threshold, consider it abandoned
  5. Cancel the order: Call the Cancel an order endpoint
  6. Verify cancellation: Poll the order status again to confirm it's in cancelled state
  7. Update your system: Trigger your order management logic (release inventory, update status, etc.)

Choose your cancellation timeframe

TimeframeApproachBest for
<5 minutesAggressiveExtremely limited inventory (flash sales, concert tickets, limited drops)
5-15 minutesBalancedStandard e-commerce with inventory management needs
>30 minutesLenientFlexible inventory, digital goods, or services
Tip

Select your threshold based on your constraints, but also decide what actions to take:

  • Cancel immediately and release inventory?
  • Send reminder emails before cancelling?
  • Mark as abandoned but keep order active?
  • Different handling by order value or customer segment?

Manual cancellation gives you full control over these decisions.

When to use manual cancellation:

  • Tight inventory control required
  • Orders revoked/cancelled in your OMS need to sync to Revolut
  • Want to send reminders before cancellation
  • Need different handling for customer segments
  • Require audit trails of cancellation triggers

Combining both approaches

For the most robust implementation, use both approaches together. This provides multiple layers of protection and ensures orders are cleaned up efficiently.

How it works:

  1. Set automatic expiration as a safety net:

    • Configure expire_pending_after with a reasonable period (e.g., 24 hours: "PT24H")
    • Ensures orders eventually fail even if manual cancellation doesn't trigger
  2. Implement manual cancellation for faster cleanup:

    • Active detection with shorter threshold (e.g., 15 minutes)
    • Immediate inventory release and order cleanup
    • Allows for business-specific actions (reminders, notifications)
  3. Benefits of using both:

    • Redundancy: Manual cancellation cleanup happens quickly; automatic expiration catches edge cases
    • Flexibility: Different thresholds for different urgency levels
    • Reliability: Orders always reach final states, even if manual process fails
    • Audit trail: Distinguish between actively cancelled orders vs truly abandoned orders

Example scenario:

// Create order with 24-hour expiration as safety net
{
"amount": 1000,
"currency": "GBP",
"expire_pending_after": "PT24H" // Automatic expiration after 24 hours
}

Then implement manual cancellation logic to cancel after 15 minutes of inactivity. Result:

  • Orders typically cancelled manually after 15 minutes → ORDER_CANCELLED webhook → cancelled state
  • If manual cancellation fails, order automatically expires after 24 hours → ORDER_FAILED webhook → failed state
Tip

Using both approaches together is the best practice for production environments. It combines the control of manual cancellation with the reliability of automatic expiration.

Handling ORDER_CANCELLED and ORDER_FAILED events

Whether using manual cancellation, automatic expiration, or both, handle both final unsuccessful states properly.

Common actions for both events:

  • Release inventory
  • Update order management system
  • Customer communication (optional)
  • Cleanup temporary data/sessions
  • Update analytics/reporting

Event-specific handling:

ApproachWebhookStateTrigger
Manual cancellationORDER_CANCELLEDcancelledYou cancelled via API, dashboard, or authorised payment expired
Automatic expirationORDER_FAILEDfailedOrder exceeded expire_pending_after period

Implementation:

  • Webhooks: Listen for both ORDER_CANCELLED and ORDER_FAILED events
  • Polling: Check for state = 'cancelled' or state = 'failed'
Caution

Never fulfil orders in cancelled or failed states. These are final unsuccessful states.

Only fulfil on completed (automatic capture) or authorised (manual capture) — the final successful states.

Implementation checklist

Before deploying your implementation to your production environment, complete the checklist below to see if everything works as expected, using the Merchant API's Sandbox environment. To test in Sandbox environment, set the base URL of your API calls to: https://sandbox-merchant.revolut.com/.

Order creation

  • Your server successfully creates orders via the API
  • The API returns a valid checkout_url and id
  • Order creation works with minimal parameters (amount, currency)
  • Order creation works with optional parameters (description, customer.email, redirect_url)

Customer experience

Payment tracking

  • Your server can retrieve order status via the Retrieve an order endpoint
  • The order response includes the payments array showing all payment attempts
  • Your server can retrieve individual payment details via the Retrieve payment details endpoint using a payment ID from the order
  • Order state changes from pending to completed after successful payment
  • When a payment attempt fails and customer retries on the same checkout_url, the order shows multiple entries in the payments array
  • Your implementation correctly handles failed payment attempts using one of the two approaches (retry same order or create new order)

Custom redirect (if implemented)

  • Customers are redirected to your custom URL after successful payment completion
  • Your redirect page verifies payment status server-side before showing confirmation
  • Failed payment attempts keep customers on the checkout page (no redirect occurs)
  • Your redirect page handles the order ID from the URL parameters

Webhooks

  • Webhook endpoint is set up
  • Webhook notifications are received for ORDER_COMPLETED events
  • Webhook notifications are received for ORDER_PAYMENT_FAILED events
  • Webhook notifications are received for ORDER_CANCELLED events
  • Webhook notifications are received for ORDER_FAILED events (if using automatic expiration)
  • Webhook signature verification is implemented for security
  • Your backend only fulfils orders after receiving the ORDER_COMPLETED or ORDER_AUTHORISED webhook
  • Your backend correctly handles ORDER_CANCELLED events (releases inventory, updates order status)

Abandoned order handling

If using manual cancellation:

  • Abandonment detection logic is implemented (webhook-based timer or polling-based)
  • Abandoned orders are cancelled via Cancel an order after your threshold
  • ORDER_CANCELLED events are handled appropriately (inventory released, status updated)
  • Cancellation threshold configured for your business needs
  • Webhook-based: Timers cancelled when ORDER_COMPLETED/ORDER_AUTHORISED received
  • Polling-based: Background jobs correctly identify and cancel stale orders

If using automatic expiration:

  • Orders created with appropriate expire_pending_after values (ISO 8601 duration format)
  • ORDER_FAILED events are handled appropriately (inventory released, status updated)
  • Expiration period tested in Sandbox
  • Expired orders correctly transition to failed state

If using manual capture mode:

  • cancel_authorised_after configured if custom authorisation window needed (ISO 8601 duration format, max "P7D")
  • System handles ORDER_CANCELLED events for expired authorisations

Common (both approaches):

  • Inventory released when orders reach final unsuccessful states
  • Order management system updated with correct final states
  • Analytics/reporting reflect cancelled/failed orders
  • No fulfilment triggered for cancelled or failed states

Once your implementation passes all checks in the Sandbox environment, test it in production with small amounts before going live. After verifying everything works correctly in both environments, you can confidently deploy to production.

These checks only cover the implementation path described in this tutorial. If your application handles more features of the Merchant API, see the Merchant API: Implementation checklists.

Success

Congratulations! You've successfully implemented Hosted Checkout Page via the API and are ready to accept payments.

What's next

Was this page helpful?