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.

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:
- Your order management system: Your internal e-commerce platform that handles orders, inventory, fulfilment, and customer management.
- Merchant API integration: Your backend integrates with the Merchant API to create orders and receive unique
checkout_urllinks for each transaction. - 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:
- Order creation: Your server creates an order via the Merchant API: Create an order endpoint and receives a
checkout_url. - Customer checkout: You redirect the customer to the
checkout_urlor share it with them via email/SMS. - 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.
- Post-payment: Customer is redirected to a success page (default Revolut page or your custom
redirect_url). - Status notification: Your server receives webhook notifications for payment events (recommended), or polls the order status.
- Order fulfilment: Your server verifies the final payment status (
completedorauthorised) 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.
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:
- Create an order
- Get customers to the checkout URL
- Track order and payment status
- 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.
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:
| Parameter | Description |
|---|---|
amount | The payment amount in the lowest denomination |
currency | ISO 4217 currency code |
Useful optional parameters:
| Parameter | Description |
|---|---|
description | Description of the order (visible to the customer) |
customer.email | Customer email address for sending receipts |
redirect_url | Custom URL to redirect customers after successful payment |
merchant_order_data.reference | Your 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"
}
| Parameter | Description |
|---|---|
id | The order ID. Save this to track its status or retrieve order details later. |
checkout_url | The 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.
redirect_url when creating the order.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"
}
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:
- Customer completes payment successfully on the Revolut-hosted checkout page.
- Customer is automatically redirected to your specified
redirect_url. - Your redirect page should verify the payment status server-side using the Retrieve an order endpoint before displaying confirmation.
- Display the appropriate success message and next steps to the customer.
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>
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.
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:
| State | Description | Final state |
|---|---|---|
pending | Order created, awaiting payment attempt | |
processing | Payment attempt in progress | |
authorised | Payment authorised (for manual capture mode) | (fulfilment trigger for manual capture) |
completed | Payment successfully captured and settled | (fulfilment trigger) |
cancelled | Order cancelled (manually or due to auth expiry) | |
failed | Order expired without successful payment |
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
completedstate - For manual capture mode: Trigger fulfilment on
authorisedstate, then capture the payment manually
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 order →
ORDER_CANCELLEDwebhook → state becomescancelled - Automatic expiration: Order exceeds configured period (default 30 days, or custom via
expire_pending_after) →ORDER_FAILEDwebhook → state becomesfailed
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:
- Payment fails: When a payment attempt fails (declined card, insufficient funds, etc.), the Hosted Checkout Page displays an error message to the customer.
- Automatic retry option: The customer sees a Try again button on the error page.
- Retry flow: When the customer clicks Try again, the Hosted Checkout Page automatically restarts the payment flow.
- New payment object: This creates a new payment attempt, which appears as a new entry in the
order.paymentsarray. - Same order ID: All retry attempts are linked to the same order ID, maintaining transaction consistency.
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_FAILEDorORDER_PAYMENT_DECLINEDwebhook events for each failed attempt. - Multiple payments array: When retrieving the order via Retrieve an order, you'll see multiple entries in the
paymentsarray if the customer retried. - Final state: Only trigger fulfilment logic when the order reaches a final state:
completed→ORDER_COMPLETEDwebhookauthorised→ORDER_AUTHORISEDwebhook (for manual capture mode)cancelled→ORDER_CANCELLEDwebhookfailed→ORDER_FAILEDwebhook (order expires without payment)
4. Use webhooks for fulfilment
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 event | Description | When sent |
|---|---|---|
ORDER_COMPLETED | Order successfully paid and captured (trigger fulfilment) | Payment captured and settled |
ORDER_AUTHORISED | Order authorised, awaiting capture (for manual capture mode) | Payment authorised but not yet captured |
ORDER_CANCELLED | Order cancelled | Order cancelled via API |
ORDER_FAILED | Order expired without successful payment | Order expiration period elapsed (default 30 days or custom via expire_pending_after) |
ORDER_PAYMENT_DECLINED | Payment attempt declined by processor | Card declined, insufficient funds, etc. |
ORDER_PAYMENT_FAILED | Payment 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
pendingorprocessingstate beyond the expiration period - No successful payment was completed before expiration
- Order automatically transitions to
failedstate
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)
ORDER_FAILEDrepresents true abandonment via automatic expiration, whileORDER_CANCELLEDrepresents 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:
- Create order: Create an order and store the order
idin your system. - Start polling: Begin polling the order status at regular intervals (e.g., every 10-30 seconds).
- Check final states: Monitor for final states (
completed,authorised,cancelled,failed). - Trigger actions: When a final state is reached, trigger appropriate business logic.
- 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
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.
This approach can be combined with automatic expiration for redundancy. See Combining both approaches.
How it works:
- Set your detection threshold
- Monitor orders via webhooks or polling
- Call Cancel an order when threshold exceeded
- Handle
ORDER_CANCELLEDwebhook or poll forcancelledstate - Update your system (release inventory, etc.)
Webhook-based detection (recommended)
Best for: Merchants using webhooks for order status tracking
If you're using webhooks to track order status, implement a timer-based detection mechanism:
- 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 - Monitor for completion: If you receive an
ORDER_COMPLETEDorORDER_AUTHORISEDwebhook event during this period, cancel the timer - Detect abandonment: If the timer expires without receiving a completion event, mark the order as abandoned in your system
- Cancel the order: Call the Cancel an order endpoint to formally cancel the order in Revolut's system
- Listen for confirmation: Your webhook endpoint will receive an
ORDER_CANCELLEDevent confirming the cancellation - 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:
- Track order age: Store the creation timestamp for each order in your system
- Periodic polling: Set up a background job that runs periodically (e.g., every 15 minutes)
- Check order status: For each order older than your threshold, poll the Retrieve an order endpoint
- Detect abandonment: If the order is still in
pendingorprocessingstate after the threshold, consider it abandoned - Cancel the order: Call the Cancel an order endpoint
- Verify cancellation: Poll the order status again to confirm it's in
cancelledstate - Update your system: Trigger your order management logic (release inventory, update status, etc.)
Choose your cancellation timeframe
| Timeframe | Approach | Best for |
|---|---|---|
| <5 minutes | Aggressive | Extremely limited inventory (flash sales, concert tickets, limited drops) |
| 5-15 minutes | Balanced | Standard e-commerce with inventory management needs |
| >30 minutes | Lenient | Flexible inventory, digital goods, or services |
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:
-
Set automatic expiration as a safety net:
- Configure
expire_pending_afterwith a reasonable period (e.g., 24 hours:"PT24H") - Ensures orders eventually fail even if manual cancellation doesn't trigger
- Configure
-
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)
-
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_CANCELLEDwebhook →cancelledstate - If manual cancellation fails, order automatically expires after 24 hours →
ORDER_FAILEDwebhook →failedstate
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:
| Approach | Webhook | State | Trigger |
|---|---|---|---|
| Manual cancellation | ORDER_CANCELLED | cancelled | You cancelled via API, dashboard, or authorised payment expired |
| Automatic expiration | ORDER_FAILED | failed | Order exceeded expire_pending_after period |
Implementation:
- Webhooks: Listen for both
ORDER_CANCELLEDandORDER_FAILEDevents - Polling: Check for
state = 'cancelled'orstate = 'failed'
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_urlandid - Order creation works with minimal parameters (
amount,currency) - Order creation works with optional parameters (
description,customer.email,redirect_url)
Customer experience
- Customers can access the checkout page via the
checkout_url - The checkout page displays correctly on both desktop and mobile
- Customers can complete payment using test cards: Test cards for successful payments
- Payment failures are handled gracefully: Test cards for error cases
- Custom branding (logo, colours, cover image) displays correctly if configured
Payment tracking
- Your server can retrieve order status via the Retrieve an order endpoint
- The order response includes the
paymentsarray 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
pendingtocompletedafter successful payment - When a payment attempt fails and customer retries on the same
checkout_url, the order shows multiple entries in thepaymentsarray - 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_COMPLETEDevents - Webhook notifications are received for
ORDER_PAYMENT_FAILEDevents - Webhook notifications are received for
ORDER_CANCELLEDevents - Webhook notifications are received for
ORDER_FAILEDevents (if using automatic expiration) - Webhook signature verification is implemented for security
- Your backend only fulfils orders after receiving the
ORDER_COMPLETEDorORDER_AUTHORISEDwebhook - Your backend correctly handles
ORDER_CANCELLEDevents (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_CANCELLEDevents are handled appropriately (inventory released, status updated)- Cancellation threshold configured for your business needs
- Webhook-based: Timers cancelled when
ORDER_COMPLETED/ORDER_AUTHORISEDreceived - Polling-based: Background jobs correctly identify and cancel stale orders
If using automatic expiration:
- Orders created with appropriate
expire_pending_aftervalues (ISO 8601 duration format) ORDER_FAILEDevents are handled appropriately (inventory released, status updated)- Expiration period tested in Sandbox
- Expired orders correctly transition to
failedstate
If using manual capture mode:
cancel_authorised_afterconfigured if custom authorisation window needed (ISO 8601 duration format, max"P7D")- System handles
ORDER_CANCELLEDevents 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
cancelledorfailedstates
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.
Congratulations! You've successfully implemented Hosted Checkout Page via the API and are ready to accept payments.
What's next
- Learn about using webhooks - Track payment lifecycle events reliably
- Explore the full Merchant API - Discover advanced features and capabilities
- Learn about the order and payment lifecycle
- Check the Payment link guide - Learn about the dashboard method
- Learn about other payment methods - Explore Revolut Pay and other options