Integrate Worldpay (FIS) with Bolt.new by obtaining sandbox credentials from the Worldpay developer portal, then using an API route to create hosted payment page sessions or direct API payment requests. The hosted payment page redirect works from the WebContainer preview since it's an outbound redirect — but payment notification callbacks require a deployed URL. Worldpay supports 120+ currencies and 300+ payment methods, making it the choice for global enterprise payment processing.
Accept Global Payments in Your Bolt.new App with Worldpay
Worldpay (operated by FIS) is one of the largest payment processors globally, handling transactions for major retailers, airlines, and financial institutions. If your Bolt.new app needs to accept payments from customers across multiple countries — especially in the UK, Europe, and Asia-Pacific — Worldpay's 120+ currency support and 300+ payment method acceptance make it a serious enterprise option alongside Stripe and Adyen. Worldpay is an acquirer-first processor, meaning it handles the entire payment chain from card tokenization to bank settlement, rather than just acting as a gateway layer on top of other acquirers.
The recommended integration path for Bolt.new apps is the hosted payment page: your API route calls Worldpay to create a payment session, receives a redirect URL to Worldpay's hosted checkout interface, and sends the customer there. Worldpay handles all card data, 3D Secure challenges, and PCI compliance on their end. After payment, Worldpay redirects back to your site and sends a server-side notification to your callback URL with the payment result. This pattern is clean and works well with Bolt — the outbound redirect to Worldpay's hosted page works from the WebContainer preview, though the return notification callback requires a deployed URL with a public HTTPS endpoint.
Worldpay's onboarding has historically been more complex than developer-friendly processors like Stripe — getting sandbox credentials requires registering a developer account and waiting for access approval. Once you have credentials, the REST API is well-documented. Expect enterprise-style onboarding with account managers for live payment processing, as Worldpay primarily targets larger businesses. For quick prototyping or smaller projects, Stripe's native Bolt integration may be more appropriate; Worldpay shines when you specifically need its global payment method coverage or your organization already has a Worldpay account.
Integration method
Worldpay integrates with Bolt.new through an API route that creates hosted payment page sessions or processes direct payment requests using Worldpay's REST API. The hosted payment page pattern — where you redirect the customer to Worldpay's own checkout UI — is the recommended approach, as it offloads PCI compliance and the redirect itself works from the WebContainer preview. Payment notification callbacks (equivalent to webhooks) require a deployed public URL. Direct integration using Worldpay's card-capture APIs requires storing Worldpay API credentials server-side and never in client-side bundles.
Prerequisites
- A Worldpay developer account registered at developer.worldpay.com with sandbox credentials approved
- Worldpay Service Key (sk_) and Client Key (pk_) from your sandbox account dashboard
- A Bolt.new project with Next.js (API routes) or Vite with Supabase Edge Functions configured
- Supabase project for storing payment records and customer data
- A deployed URL on Netlify or Bolt Cloud for receiving Worldpay payment notification callbacks
Step-by-step guide
Register for Worldpay Sandbox and Get Credentials
Register for Worldpay Sandbox and Get Credentials
Unlike Stripe, which provides instant API keys on signup, Worldpay requires registering a developer account through their partner portal and waiting for credential provisioning. Go to developer.worldpay.com and click 'Sign Up' to create a developer account. You'll need to provide business information and a use case description. Once approved, access the sandbox environment — Worldpay calls it the 'test environment' — through the dashboard. Navigate to Settings → Account and find your Service Key (starts with `T_S_` for test service key) and Client Key (starts with `T_C_` for test client key). The Service Key is used for server-side API calls and must be kept secret — it's the equivalent of Stripe's secret key. The Client Key is safe for client-side use (for the Worldpay JavaScript SDK if you use the Direct integration). For the hosted payment page integration, you only need the Service Key server-side. Copy both keys into your Bolt project's `.env` file. Note that Worldpay sandbox credentials have a different format than live credentials: test credentials start with `T_`, while live credentials start with `L_`. Using a test key against the live endpoint (or vice versa) will result in authentication errors. The sandbox environment at `https://api.worldpay.com/v1/orders` with a test key processes payments without charging real cards.
1# .env — Worldpay credentials for Bolt.new project2# Test/Sandbox credentials (start with T_)3WORLDPAY_SERVICE_KEY=T_S_your-test-service-key4WORLDPAY_CLIENT_KEY=T_C_your-test-client-key56# Your app's callback URLs (update after deploying)7WORLDPAY_SUCCESS_URL=https://your-app.netlify.app/payment/success8WORLDPAY_FAILURE_URL=https://your-app.netlify.app/payment/failure9WORLDPAY_PENDING_URL=https://your-app.netlify.app/payment/pending10WORLDPAY_CANCEL_URL=https://your-app.netlify.app/payment/cancel1112# For Next.js: process.env.WORLDPAY_SERVICE_KEY13# For Vite: import.meta.env.VITE_WORLDPAY_CLIENT_KEY (client-side safe key only)Pro tip: Worldpay test cards: 4444 3333 2222 1111 (Visa, success), 5555 5555 5555 4444 (Mastercard, success), 4444 3333 2222 1117 (refused). Use any future expiry date and CVC 123.
Expected result: You have Worldpay sandbox credentials stored in your .env file. You can verify them by making a test API call to the Worldpay sandbox endpoint.
Create a Payment Session API Route
Create a Payment Session API Route
The core of the Worldpay integration is an API route that creates a payment order on Worldpay's servers and returns a redirect URL to the hosted payment page. This route accepts payment details from your frontend, authenticates with Worldpay using your Service Key, and receives back a payment session with a redirectURL field. The entire flow uses standard HTTPS requests — Worldpay's REST API does not use any TCP-only protocols, so it works perfectly from Bolt's Next.js API routes and from within the WebContainer during development. The Worldpay API endpoint for creating orders is `https://api.worldpay.com/v1/orders`. You POST a JSON body describing the payment: amount in minor units (pence for GBP, cents for USD), currency code, order description, and the URLs to redirect to after success/failure/cancellation. Worldpay responds with an `orderCode` (your reference), `paymentStatus`, and a `redirectURL` to Worldpay's hosted checkout. Your frontend then redirects the customer to this URL. Critically, your Service Key must only ever appear in this server-side route — never in component files, never in hooks, never in any code that ships to the browser. Use `process.env.WORLDPAY_SERVICE_KEY` in Next.js API routes where it's executed server-side only.
Create a Worldpay payment session API route at app/api/worldpay/create-payment/route.ts. It should accept a POST request with: amount (number, in pennies/cents), currency (string, e.g., 'GBP'), description (string), and orderId (string). Use the WORLDPAY_SERVICE_KEY environment variable to authenticate with Worldpay's API at https://api.worldpay.com/v1/orders. Return the orderCode and redirectURL from Worldpay's response. Handle errors gracefully and return appropriate error messages.
Paste this in Bolt.new chat
1// app/api/worldpay/create-payment/route.ts2import { NextResponse } from 'next/server';34const WORLDPAY_API_URL = 'https://api.worldpay.com/v1/orders';56export async function POST(request: Request) {7 const serviceKey = process.env.WORLDPAY_SERVICE_KEY;8 if (!serviceKey) {9 return NextResponse.json(10 { error: 'Worldpay service key not configured' },11 { status: 500 }12 );13 }1415 const { amount, currency = 'GBP', description, orderId } = await request.json();1617 const payload = {18 token: serviceKey,19 amount,20 currencyCode: currency,21 name: description,22 orderDescription: description,23 customerOrderCode: orderId,24 orderType: 'ECOM',25 is3DSOrder: true,26 successUrl: process.env.WORLDPAY_SUCCESS_URL,27 failureUrl: process.env.WORLDPAY_FAILURE_URL,28 pendingUrl: process.env.WORLDPAY_PENDING_URL,29 cancelUrl: process.env.WORLDPAY_CANCEL_URL,30 };3132 const response = await fetch(WORLDPAY_API_URL, {33 method: 'POST',34 headers: {35 'Content-Type': 'application/json',36 },37 body: JSON.stringify(payload),38 });3940 if (!response.ok) {41 const errorData = await response.json().catch(() => ({}));42 return NextResponse.json(43 { error: 'Worldpay API error', details: errorData },44 { status: response.status }45 );46 }4748 const data = await response.json();4950 return NextResponse.json({51 orderCode: data.orderCode,52 redirectUrl: data.redirectURL,53 paymentStatus: data.paymentStatus,54 });55}Pro tip: Worldpay amounts are in minor currency units — for GBP, £10.99 = 1099 pence. For USD, $10.99 = 1099 cents. Always convert your display prices to minor units before sending to the API to avoid incorrect charge amounts.
Expected result: Calling POST /api/worldpay/create-payment with amount, currency, and description returns a Worldpay orderCode and a redirectUrl pointing to Worldpay's hosted checkout page.
Build the Checkout UI and Handle Redirects
Build the Checkout UI and Handle Redirects
With the API route in place, build the frontend component that initiates the payment and redirects the customer to Worldpay's hosted page. The pattern is similar to Stripe Checkout: the user clicks 'Pay Now', your component calls your API route, receives the redirectUrl, and performs a browser redirect to Worldpay's hosted checkout UI. Worldpay takes over from there — displaying the payment form, handling card validation, performing 3D Secure challenges, and processing the payment. After the payment attempt, Worldpay redirects back to your success, failure, or pending URLs based on the outcome. The redirect-based pattern works well in Bolt's WebContainer preview because the redirect itself is just a browser navigation to a Worldpay URL — this is a client-side action, not an incoming HTTP call. You'll see Worldpay's hosted checkout page load. However, after completing the test payment, Worldpay redirects back to your configured success/failure URLs. During preview development, these redirect URLs point to your deployed app (or can be set to localhost with a tunnel), so you'll need a deployed URL to see the full post-payment flow. Create simple success and failure pages at the paths configured in your environment variables to handle the redirect.
Create a PayWithWorldpay component that shows a 'Pay Now' button. On click, call /api/worldpay/create-payment with the order total in pence, currency 'GBP', and a description. Show a loading state while creating the session. On success, redirect the browser to the redirectUrl from the response. On failure, show an error message. Also create a /payment/success page that shows a success confirmation with the orderCode from the URL query params, and a /payment/failure page that shows a failure message with a retry button.
Paste this in Bolt.new chat
1// components/PayWithWorldpay.tsx2'use client';3import { useState } from 'react';45interface PayWithWorldpayProps {6 amountInPence: number;7 currency?: string;8 description: string;9 orderId: string;10}1112export function PayWithWorldpay({13 amountInPence,14 currency = 'GBP',15 description,16 orderId,17}: PayWithWorldpayProps) {18 const [loading, setLoading] = useState(false);19 const [error, setError] = useState<string | null>(null);2021 async function handlePayment() {22 setLoading(true);23 setError(null);2425 const response = await fetch('/api/worldpay/create-payment', {26 method: 'POST',27 headers: { 'Content-Type': 'application/json' },28 body: JSON.stringify({29 amount: amountInPence,30 currency,31 description,32 orderId,33 }),34 });3536 if (!response.ok) {37 const err = await response.json();38 setError(err.error ?? 'Payment creation failed');39 setLoading(false);40 return;41 }4243 const { redirectUrl } = await response.json();44 // Redirect to Worldpay hosted payment page45 window.location.href = redirectUrl;46 }4748 return (49 <div>50 {error && (51 <p className="text-red-600 mb-3 text-sm">{error}</p>52 )}53 <button54 onClick={handlePayment}55 disabled={loading}56 className="bg-blue-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 w-full"57 >58 {loading ? 'Creating payment...' : `Pay £${(amountInPence / 100).toFixed(2)}`}59 </button>60 <p className="text-xs text-gray-500 mt-2 text-center">61 Secured by Worldpay62 </p>63 </div>64 );65}Pro tip: During WebContainer development, test the redirect to Worldpay's hosted page using the sandbox. After completing the test payment, Worldpay tries to redirect to your success URL — make sure this URL is either your deployed app or a tunneled localhost for full end-to-end testing.
Expected result: Clicking Pay Now redirects the browser to Worldpay's hosted checkout page. Completing payment with a test card redirects back to your success URL with an orderCode parameter.
Handle Payment Notifications After Deploying
Handle Payment Notifications After Deploying
Worldpay's payment notification system (equivalent to Stripe webhooks) sends a server-to-server HTTP POST to a URL you configure whenever a payment status changes. This is separate from the customer-facing redirect — the notification goes directly from Worldpay's servers to your app's endpoint, bypassing the browser entirely. During development in Bolt's WebContainer, this notification URL cannot point to your local preview because the WebContainer has no public IP address — it's a browser-local virtualized environment running inside Service Workers. Worldpay's servers cannot route HTTP requests to `localhost` or the dynamic preview URL. Deploy your app first, then configure the notification URL in your Worldpay merchant account settings to point to your deployed endpoint at `https://your-app.netlify.app/api/worldpay/notification`. The notification payload from Worldpay includes the orderCode, paymentStatus (SUCCESS, FAILED, EXPIRED), amount, currency, and card details. Your handler should update the payment record in Supabase, trigger any post-payment logic (order fulfillment, subscription activation, email confirmation), and return a `[OK]` string response — Worldpay expects this specific response body to confirm receipt.
Create a Worldpay payment notification handler at app/api/worldpay/notification/route.ts. It should receive POST requests from Worldpay with payment status updates. Parse the notification body (Worldpay sends form-encoded data or JSON depending on configuration). Extract the orderCode, paymentStatus, and amount. If paymentStatus is SUCCESS, update the corresponding order in Supabase to 'paid' status. If FAILED or EXPIRED, update to 'failed'. Return the string '[OK]' as the response body — Worldpay requires this exact string to confirm receipt.
Paste this in Bolt.new chat
1// app/api/worldpay/notification/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createClient } from '@supabase/supabase-js';45export async function POST(request: NextRequest) {6 // Worldpay sends notification as JSON or form-encoded7 const contentType = request.headers.get('content-type') ?? '';8 let notificationData: Record<string, string> = {};910 if (contentType.includes('application/json')) {11 notificationData = await request.json();12 } else {13 // Form-encoded notification14 const body = await request.text();15 const params = new URLSearchParams(body);16 params.forEach((value, key) => { notificationData[key] = value; });17 }1819 const { orderCode, paymentStatus, amount, currency } = notificationData;2021 if (!orderCode) {22 return new Response('[OK]', { headers: { 'Content-Type': 'text/plain' } });23 }2425 const supabase = createClient(26 process.env.NEXT_PUBLIC_SUPABASE_URL!,27 process.env.SUPABASE_SERVICE_ROLE_KEY!28 );2930 const status = paymentStatus === 'SUCCESS' ? 'paid'31 : paymentStatus === 'FAILED' ? 'failed'32 : 'pending';3334 await supabase35 .from('orders')36 .update({37 payment_status: status,38 worldpay_order_code: orderCode,39 updated_at: new Date().toISOString(),40 })41 .eq('id', orderCode);4243 // Worldpay REQUIRES '[OK]' response body to confirm receipt44 return new Response('[OK]', {45 status: 200,46 headers: { 'Content-Type': 'text/plain' },47 });48}Pro tip: Worldpay requires the response body to be exactly '[OK]' (with square brackets, no quotes). Any other response body causes Worldpay to retry the notification repeatedly. Do not use NextResponse.json() for this endpoint — use a plain Response with text/plain content type.
Expected result: After deploying and configuring the notification URL in Worldpay merchant settings, successful test payments trigger a POST to your notification endpoint, updating order status in Supabase.
Common use cases
Global E-Commerce Checkout
Build a checkout flow for an online store that accepts payments in multiple currencies from customers worldwide. Worldpay's hosted payment page handles currency conversion, local payment methods, and 3D Secure authentication automatically based on the customer's country and card type.
Add a checkout button to my e-commerce product page. When clicked, call my API route to create a Worldpay hosted payment session for the cart total in GBP. Redirect the user to Worldpay's hosted payment page. After payment, redirect back to /order-confirmation with the order ID. Store the payment reference and status in Supabase. Use the WORLDPAY_SERVICE_KEY and WORLDPAY_CLIENT_KEY from the .env file.
Copy this prompt to try it in Bolt.new
Multi-Currency SaaS Billing
Charge SaaS customers in their local currency — USD, EUR, GBP, JPY — without managing exchange rates yourself. Worldpay handles the currency conversion and routes to local acquirers for better authorization rates.
Create a subscription billing page that charges users in their local currency using Worldpay. Based on the user's country stored in their profile, set the payment currency (USD for US, EUR for EU, GBP for UK). Create a Worldpay payment session via the API route and display the hosted payment page in a modal. On payment success, update the user's subscription tier in Supabase to 'pro' and send a confirmation email.
Copy this prompt to try it in Bolt.new
Enterprise Invoice Payment Portal
Build a B2B payment portal where enterprise clients can pay outstanding invoices using corporate cards or bank transfers. Worldpay supports corporate card types and local bank transfer methods that aren't available on all processors.
Build an invoice payment page at /invoices/[id]/pay. Fetch the invoice details from Supabase. Create a Worldpay hosted payment session for the invoice amount and reference. Show the payment form embedded or as a redirect. After Worldpay confirms payment via callback, update the invoice status to 'paid' in Supabase and notify the account manager via Slack webhook.
Copy this prompt to try it in Bolt.new
Troubleshooting
Worldpay API returns 401 Unauthorized when creating a payment session
Cause: The Service Key in the request body is incorrect, expired, or you're mixing test keys (T_S_) with the live environment (or vice versa). Worldpay uses the token field in the JSON body for authentication, not an Authorization header.
Solution: Verify your WORLDPAY_SERVICE_KEY in .env matches exactly what's shown in the Worldpay merchant portal. Ensure you're using T_S_ (test service key) with the sandbox endpoint https://api.worldpay.com/v1/orders — the same endpoint is used for both test and live, differentiated only by the key prefix. Check for extra spaces or newlines around the key value.
1// Worldpay uses token in the body, not Authorization header2const payload = {3 token: process.env.WORLDPAY_SERVICE_KEY, // T_S_xxx for sandbox4 amount: 1099,5 currencyCode: 'GBP',6 // ...7};Payment notification URL is not receiving callbacks from Worldpay
Cause: The notification URL is set to a WebContainer preview URL or localhost, which Worldpay's servers cannot reach. Worldpay makes server-to-server HTTP calls to your notification endpoint — it cannot reach browser-local environments.
Solution: Deploy your app to Netlify or Bolt Cloud first, then update the notification URL in your Worldpay merchant account settings to point to your deployed domain: https://your-app.netlify.app/api/worldpay/notification. The WebContainer only handles outbound calls — it cannot receive inbound HTTP requests from external servers.
Worldpay redirects back with payment status 'FAILED' even for test cards
Cause: Using an incorrect test card number, or the payment session was created with an amount of 0 (Worldpay rejects zero-amount payments), or the currency code is not recognized.
Solution: Use Worldpay's official test card numbers: 4444 3333 2222 1111 for Visa success, 5555 5555 5555 4444 for Mastercard success. Ensure the amount is in minor units (pence for GBP — £1.00 = 100, not 1). Verify the currencyCode is the ISO 4217 3-letter code in uppercase (GBP, USD, EUR).
1// Correct: amount in minor units (pence/cents)2const amountInPence = Math.round(priceInPounds * 100); // £10.99 → 109934// Wrong: passing decimal amount5// amount: 10.99 // This will be interpreted incorrectlyWorldpay notification handler returns 200 but Worldpay keeps retrying the notification
Cause: The response body is not exactly '[OK]' — Worldpay checks the response body content, not just the HTTP status code. Using NextResponse.json() or any JSON response triggers retries.
Solution: Return a plain text Response with body '[OK]' exactly. Do not use NextResponse.json() for this specific endpoint. The square brackets are required — this is Worldpay's protocol for acknowledging receipt.
1// Correct: plain text '[OK]'2return new Response('[OK]', { status: 200, headers: { 'Content-Type': 'text/plain' } });34// Wrong: JSON response causes retries5// return NextResponse.json({ received: true });Best practices
- Use Worldpay's hosted payment page instead of the Direct integration — it offloads PCI DSS compliance and works with the WebContainer redirect pattern without handling raw card data
- Always store amounts in minor currency units (pence, cents) in your database — convert to display format only in the UI to avoid floating-point rounding errors
- Keep your Worldpay Service Key (T_S_ or L_S_) exclusively in server-side environment variables — never bundle it into client-side Vite VITE_* variables
- Return exactly '[OK]' (with brackets) as the response body to Worldpay payment notifications — any other response triggers repeated retry attempts
- Test all payment scenarios using Worldpay's test cards before going live: success, decline, and 3D Secure challenge cards each behave differently
- Configure both success and failure redirect URLs in your payment session creation and create corresponding pages to handle both outcomes gracefully
- Deploy to Netlify or Bolt Cloud before testing the complete payment lifecycle — Worldpay payment notifications cannot reach the WebContainer's browser-local environment
- Use idempotent order IDs (e.g., your database record ID) as the customerOrderCode to prevent duplicate orders if users click the payment button multiple times
Alternatives
Adyen is a modern full-stack payment platform with better developer documentation and a unified API; choose Adyen over Worldpay for greenfield projects without existing Worldpay contracts.
Stripe has native first-class Bolt.new integration, instant developer access, and far simpler onboarding — the default choice for new Bolt apps that don't need Worldpay's specific global coverage.
Authorize.net is a Visa-owned US-focused payment gateway that's simpler to integrate than Worldpay for US-only merchants and has a more accessible developer program.
2Checkout (now Verifone) specializes in digital goods and SaaS billing with strong international coverage, similar to Worldpay but with a more developer-accessible API.
Frequently asked questions
How do I get Worldpay sandbox credentials for testing?
Register a developer account at developer.worldpay.com and request sandbox access. Worldpay's onboarding is more involved than Stripe — you'll need to provide business details and wait for approval. Once approved, find your test Service Key (T_S_) and Client Key (T_C_) in the merchant portal dashboard under Settings → Account. Use test cards like 4444 3333 2222 1111 for successful payments in the sandbox.
Can I test Worldpay payments in Bolt's WebContainer preview?
Partially. You can test the checkout session creation and the redirect to Worldpay's hosted payment page in the WebContainer preview — both are outbound HTTP calls that work fine. After completing the test payment, Worldpay redirects back to your configured success URL. Worldpay's server-to-server payment notifications (callbacks) require a deployed public URL and cannot reach the WebContainer's browser-local environment.
Does Worldpay work with Vite projects in Bolt, or only Next.js?
Worldpay works with both project types, but the server-side pattern differs. For Next.js, use API routes (app/api/worldpay/route.ts) where the Service Key is available via process.env. For Vite projects, create a Supabase Edge Function to handle the Worldpay API calls, since Vite runs entirely client-side and cannot securely store the Service Key without a server-side component.
How does Worldpay compare to Stripe for a Bolt.new app?
Stripe has native Bolt integration (Settings → Stripe panel, auto-generated edge functions) and much simpler onboarding — start accepting payments in minutes. Worldpay requires manual API route setup and enterprise-level onboarding. Choose Worldpay when you already have a Worldpay merchant account, need specific global payment methods not available on Stripe, or when your organization has contractual requirements to use Worldpay.
What response must my Worldpay notification endpoint return?
Your notification endpoint must return the exact string '[OK]' (with square brackets) as the HTTP response body with a 200 status and text/plain content type. Worldpay checks the response body content — returning JSON or any other format causes Worldpay to consider the notification unacknowledged and retry it repeatedly. Use new Response('[OK]', { status: 200 }) not NextResponse.json().
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation