Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with PayPal

PayPal has no native Bolt integration (unlike Stripe, which is first-class). To add PayPal payments, create a PayPal developer app for client ID and secret, build a Next.js API route for server-side token exchange and order creation, and use the @paypal/paypal-js SDK for the client-side checkout button. PayPal webhooks require a deployed URL — test order creation in the preview but complete end-to-end testing only after deploying to Netlify or Bolt Cloud.

What you'll learn

  • How to create a PayPal developer app and obtain sandbox credentials for testing
  • How to implement the two-step PayPal OAuth flow (token exchange + order creation) in a Next.js API route
  • How to render the PayPal Smart Payment Button using the @paypal/paypal-js SDK in React
  • Why PayPal webhooks require a deployed URL and how to test without them during development
  • How PayPal's manual setup compares to Stripe's native one-prompt integration in Bolt.new
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read30 minutesPaymentApril 2026RapidDev Engineering Team
TL;DR

PayPal has no native Bolt integration (unlike Stripe, which is first-class). To add PayPal payments, create a PayPal developer app for client ID and secret, build a Next.js API route for server-side token exchange and order creation, and use the @paypal/paypal-js SDK for the client-side checkout button. PayPal webhooks require a deployed URL — test order creation in the preview but complete end-to-end testing only after deploying to Netlify or Bolt Cloud.

Adding PayPal Payments to Bolt.new — The Manual Approach

PayPal is used by 435 million account holders worldwide and is often requested by customers who don't want to enter card details. However, Bolt.new's native payment integration is exclusive to Stripe — there is no PayPal connector in Bolt's settings panel. Implementing PayPal requires manually building both the server-side API logic and the client-side checkout UI, which takes significantly more effort than Stripe's one-prompt setup.

PayPal's payment flow has two server-side steps that must happen before the customer sees the payment interface: first, your server acquires an OAuth access token using your client ID and secret, then uses that token to create an order with the amount and currency. These steps require your PayPal secret key and must happen server-side to prevent credential exposure. A Next.js API route handles both steps, returning the PayPal order ID to the client. The client-side @paypal/paypal-js SDK then renders the familiar PayPal button and smart payment panel (PayPal balance, Venmo, credit/debit card) using that order ID.

A critical limitation to understand upfront: PayPal webhooks (the order.approved and payment.capture.completed events that confirm payment on your server) require a publicly accessible URL. Bolt's WebContainer runtime has no public URL — it runs inside a browser tab. This means you can test the order creation flow in the Bolt preview, but you cannot test the complete end-to-end payment confirmation until you deploy to Netlify or Bolt Cloud. Plan your development workflow around this: build and test the UI first, then complete end-to-end testing after your first deployment.

Integration method

Bolt Chat + API Route

PayPal integration in Bolt requires a manual two-part implementation: a server-side Next.js API route for OAuth token acquisition and order creation (keeping client ID and secret secure), and the @paypal/paypal-js SDK on the client for rendering the PayPal button and checkout UI. Unlike Stripe's one-prompt setup in Bolt, PayPal requires building each piece manually. PayPal webhooks cannot be received in Bolt's WebContainer — you must deploy to a real server before testing end-to-end payment flows.

Prerequisites

  • A PayPal developer account at developer.paypal.com (free, separate from your personal PayPal)
  • A PayPal app created in the developer dashboard with sandbox client ID and secret
  • Two PayPal sandbox test accounts (buyer and seller) created in the developer dashboard
  • A Bolt.new project using Next.js for API routes that will handle server-side PayPal logic
  • Understanding that full end-to-end payment testing requires deploying to Netlify or Bolt Cloud first

Step-by-step guide

1

Create a PayPal Developer App and Get Credentials

PayPal uses a separate developer environment at developer.paypal.com that's completely isolated from your personal or business PayPal account. You need to create a developer app to get the sandbox credentials used for testing. Log in at developer.paypal.com using your PayPal credentials. Navigate to My Apps & Credentials. Click 'Create App' under the Sandbox section. Give it a descriptive name like 'MyBoltApp-Sandbox.' After creation, you'll see your Sandbox Client ID (a long string starting with 'A...') and a Secret (click to reveal). Copy both. Also on the developer dashboard, you'll see pre-created sandbox test accounts — you need a sandbox buyer account to test the payment flow. Find the sandbox personal account email and password under Sandbox → Accounts. When you're ready for production, create a separate Live app and use those credentials instead. Never mix sandbox and live credentials.

Pro tip: The sandbox client ID is safe to expose to the browser (it's used to initialize the PayPal JS SDK on the client side). Only the sandbox secret must be kept server-side.

Expected result: You have your PayPal Sandbox Client ID and Sandbox Secret. You have the email and password for a sandbox buyer account for testing. All three are saved somewhere safe.

2

Install PayPal Dependencies and Configure Environment Variables

You'll need two PayPal packages: @paypal/paypal-js (or @paypal/react-paypal-js for React integration) for the client-side payment button UI, and no additional package for server-side API calls since you'll use the native fetch API for PayPal's REST API. The @paypal/react-paypal-js package provides the PayPalScriptProvider context and PayPalButtons component, which handle the complex PayPal SDK lifecycle management automatically. Store your credentials in .env — the client ID uses NEXT_PUBLIC_ prefix because it's needed by the client-side PayPal button script, but the secret uses no prefix because it's only read by your API routes. This is one of the rare cases where a 'public' variable is intentionally exposed — PayPal client IDs are designed to be visible in client code.

Bolt.new Prompt

Install @paypal/react-paypal-js. Create a .env file with NEXT_PUBLIC_PAYPAL_CLIENT_ID (my sandbox client ID) and PAYPAL_CLIENT_SECRET (my sandbox secret). Create a .env.example file documenting these variables. Also create a .env.production.example with placeholder values for the live PayPal credentials.

Paste this in Bolt.new chat

.env
1# .env sandbox for development
2NEXT_PUBLIC_PAYPAL_CLIENT_ID=your-sandbox-client-id-here
3PAYPAL_CLIENT_SECRET=your-sandbox-secret-here
4PAYPAL_API_URL=https://api-m.sandbox.paypal.com
5
6# For production, change to:
7# NEXT_PUBLIC_PAYPAL_CLIENT_ID=your-live-client-id
8# PAYPAL_CLIENT_SECRET=your-live-secret
9# PAYPAL_API_URL=https://api-m.paypal.com

Pro tip: The PAYPAL_API_URL environment variable makes switching between sandbox and production as simple as changing one URL — no code changes needed.

Expected result: @paypal/react-paypal-js is in package.json. The .env file has your sandbox client ID and secret. The dev server restarts successfully.

3

Build the Server-Side API Routes

PayPal's server-side flow has two distinct steps that each require a separate API route. The first route creates a PayPal order: it acquires an OAuth access token using your client ID and secret (a credentials-based POST request), then uses that token to create an order with the amount, currency, and intent. The route returns the PayPal order ID to the client. The second route captures the order after the customer approves it in the PayPal interface: the client sends the order ID back to this route, which makes a PayPal API call to capture the funds. The capture response contains the transaction details (amount, payer info, transaction ID) that you should save to your database. Both routes require the PAYPAL_CLIENT_SECRET which never leaves the server.

Bolt.new Prompt

Create two Next.js API routes for PayPal. First, /api/paypal/create-order (POST): accepts an amount and currency in the request body. Gets a PayPal OAuth token by POSTing to PAYPAL_API_URL/v1/oauth2/token with client credentials. Then creates a PayPal order at PAYPAL_API_URL/v2/checkout/orders with CAPTURE intent. Returns the order ID. Second, /api/paypal/capture-order (POST): accepts an orderID. Calls the PayPal capture endpoint at PAYPAL_API_URL/v2/checkout/orders/{orderID}/capture. Returns the capture response. Include error handling for failed auth and failed captures.

Paste this in Bolt.new chat

app/api/paypal/create-order/route.ts
1// app/api/paypal/create-order/route.ts
2import { NextResponse } from 'next/server';
3
4async function getPayPalAccessToken(): Promise<string> {
5 const auth = Buffer.from(
6 `${process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
7 ).toString('base64');
8
9 const response = await fetch(
10 `${process.env.PAYPAL_API_URL}/v1/oauth2/token`,
11 {
12 method: 'POST',
13 headers: {
14 'Authorization': `Basic ${auth}`,
15 'Content-Type': 'application/x-www-form-urlencoded',
16 },
17 body: 'grant_type=client_credentials',
18 }
19 );
20 const data = await response.json();
21 return data.access_token;
22}
23
24export async function POST(request: Request) {
25 try {
26 const { amount, currency = 'USD' } = await request.json();
27 const accessToken = await getPayPalAccessToken();
28
29 const response = await fetch(
30 `${process.env.PAYPAL_API_URL}/v2/checkout/orders`,
31 {
32 method: 'POST',
33 headers: {
34 'Authorization': `Bearer ${accessToken}`,
35 'Content-Type': 'application/json',
36 },
37 body: JSON.stringify({
38 intent: 'CAPTURE',
39 purchase_units: [{
40 amount: {
41 currency_code: currency,
42 value: amount.toFixed(2),
43 },
44 }],
45 }),
46 }
47 );
48
49 const order = await response.json();
50 return NextResponse.json({ id: order.id });
51 } catch (error) {
52 return NextResponse.json({ error: 'Failed to create order' }, { status: 500 });
53 }
54}

Pro tip: Cache the PayPal access token (it's valid for 9 hours) rather than requesting a new one on every order creation. Store it in memory with an expiry timestamp to avoid unnecessary OAuth calls.

Expected result: POST to /api/paypal/create-order with {amount: 29.99} returns a JSON object with an order id starting with a PayPal order ID format. The /api/paypal/capture-order route is also created and ready.

4

Add the PayPal Button Component to Your UI

The @paypal/react-paypal-js library handles the complex PayPal JavaScript SDK lifecycle — loading the external PayPal script, initializing it with your client ID, and rendering the Smart Payment Button. You wrap your payment page with the PayPalScriptProvider context component (providing your client ID from the NEXT_PUBLIC_ environment variable), then use the PayPalButtons component where you want the button to appear. The PayPalButtons component takes two key callback props: createOrder (which calls your /api/paypal/create-order route and returns the order ID) and onApprove (which calls your /api/paypal/capture-order route after the customer approves payment in the PayPal popup). The PayPal button handles all the popup/modal logic internally — you just need to handle the success and error callbacks.

Bolt.new Prompt

Create a PayPalCheckout component that uses PayPalScriptProvider from @paypal/react-paypal-js to wrap PayPalButtons. The component accepts an amount prop (number) and an onSuccess callback prop. The createOrder function should POST to /api/paypal/create-order with the amount. The onApprove function should POST the orderID to /api/paypal/capture-order and call onSuccess with the capture details on completion. Handle errors with a visible error message. Use NEXT_PUBLIC_PAYPAL_CLIENT_ID as the client ID.

Paste this in Bolt.new chat

components/PayPalCheckout.tsx
1'use client';
2import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
3import { useState } from 'react';
4
5interface PayPalCheckoutProps {
6 amount: number;
7 currency?: string;
8 onSuccess?: (details: Record<string, unknown>) => void;
9}
10
11export function PayPalCheckout({
12 amount,
13 currency = 'USD',
14 onSuccess,
15}: PayPalCheckoutProps) {
16 const [error, setError] = useState<string | null>(null);
17
18 return (
19 <PayPalScriptProvider
20 options={{
21 clientId: process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID!,
22 currency,
23 }}
24 >
25 <div className="max-w-sm">
26 <PayPalButtons
27 createOrder={async () => {
28 setError(null);
29 const response = await fetch('/api/paypal/create-order', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ amount, currency }),
33 });
34 const order = await response.json();
35 if (!response.ok) throw new Error(order.error);
36 return order.id;
37 }}
38 onApprove={async (data) => {
39 const response = await fetch('/api/paypal/capture-order', {
40 method: 'POST',
41 headers: { 'Content-Type': 'application/json' },
42 body: JSON.stringify({ orderID: data.orderID }),
43 });
44 const details = await response.json();
45 if (!response.ok) throw new Error('Capture failed');
46 onSuccess?.(details);
47 }}
48 onError={(err) => {
49 setError('Payment failed. Please try again.');
50 console.error('PayPal error:', err);
51 }}
52 />
53 {error && (
54 <p className="text-red-600 text-sm mt-2">{error}</p>
55 )}
56 </div>
57 </PayPalScriptProvider>
58 );
59}

Expected result: The PayPal Smart Payment Button renders on your page showing PayPal, Venmo, and Debit/Credit Card options. Clicking it opens the PayPal sandbox popup. Using your sandbox buyer account credentials, you can complete a test payment.

5

Deploy and Register Webhooks for Payment Confirmation

During development in Bolt's WebContainer, you can test the complete payment flow using the PayPal popup — the createOrder and captureOrder steps work in the preview since they make outbound HTTP calls to PayPal's sandbox. However, PayPal also sends webhook events (order.approved, payment.capture.completed) to confirm payments asynchronously on your server. These webhooks require a publicly accessible URL that PayPal can call. Bolt's WebContainer has no public URL, so webhook registration must wait until after deployment. Deploy to Netlify or Bolt Cloud first. After deployment, go to developer.paypal.com → your app → Webhooks → Add Webhook. Enter your deployed URL (e.g., https://your-app.netlify.app/api/paypal/webhook) and select the events you need. Create the webhook handler API route and verify signatures using PayPal's webhook verification API. Set your production PayPal credentials (live client ID and secret) in your hosting platform's environment variables.

Bolt.new Prompt

Create a webhook handler at /api/paypal/webhook that receives PayPal webhook events. It should verify the webhook signature using PayPal's verification API (POST to PAYPAL_API_URL/v1/notifications/verify-webhook-signature). For payment.capture.completed events, extract the order ID and amount and update the order status in the database to 'paid'. Return 200 for all valid webhooks.

Paste this in Bolt.new chat

Pro tip: Compare PayPal's manual setup (these 5 steps) to Stripe's integration: in Bolt, you can set up Stripe with a single chat prompt 'Add Stripe payments' and Bolt handles everything automatically. For new projects without existing PayPal requirements, Stripe is significantly easier.

Expected result: Your deployed app processes real PayPal payments in the PayPal sandbox. The webhook endpoint at /api/paypal/webhook receives payment confirmation events from PayPal. Order status updates correctly in your database when payments complete.

Common use cases

Single Product Checkout Page

Add a PayPal payment option to a product page for one-time purchases. Customers click the PayPal button, log into their PayPal account, and complete payment without entering card details. Your server captures the payment and updates the order status in your database.

Bolt.new Prompt

Add a PayPal checkout button to my product page. The product costs $29.99 USD. Create an API route at /api/paypal/create-order that gets a PayPal OAuth token using PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET from environment variables, then creates a PayPal order for $29.99. Create another route at /api/paypal/capture-order that captures the approved order. Use the @paypal/paypal-js package to render the PayPal button on the product page. Use PayPal sandbox credentials for now.

Copy this prompt to try it in Bolt.new

Shopping Cart with Variable Amounts

Implement PayPal checkout for a shopping cart where the total varies based on items. The client sends the cart total to the create-order API route, which creates a PayPal order with the exact amount. This handles discounts, shipping calculations, and tax correctly.

Bolt.new Prompt

Add PayPal checkout to my shopping cart. When the user clicks 'Pay with PayPal', call /api/paypal/create-order with the cart items array and total amount. The API route should create a PayPal order with the total amount and return the order ID. Use PayPalButtons from @paypal/react-paypal-js to show the checkout button. On approval, call /api/paypal/capture-order to complete the payment, then show a success page and clear the cart.

Copy this prompt to try it in Bolt.new

Donation Button

Add a flexible donation feature where donors can choose their own amount. PayPal's checkout flow handles the amount collection on the PayPal side, or you can let users input the amount on your page before initiating the PayPal flow.

Bolt.new Prompt

Create a donation page with PayPal. Show preset donation amounts ($5, $10, $25, $50) and a custom amount input. When the user selects an amount and clicks 'Donate via PayPal', create a PayPal order for that amount via /api/paypal/create-order, then show the PayPal button panel. After successful payment, show a thank-you message with the donation amount and a transaction ID.

Copy this prompt to try it in Bolt.new

Troubleshooting

PayPal button does not render — blank space where button should appear

Cause: The NEXT_PUBLIC_PAYPAL_CLIENT_ID is missing or incorrect, or PayPalScriptProvider failed to load the external PayPal SDK script.

Solution: Check the browser console for script loading errors. Verify NEXT_PUBLIC_PAYPAL_CLIENT_ID is set in .env and has the NEXT_PUBLIC_ prefix (required for Next.js client-side access). The client ID should start with 'A' for sandbox or 'A' for live. Ensure you're using the correct environment (sandbox vs live) — sandbox client IDs only work with the sandbox API URL.

API route returns 401: 'Authentication failed' when calling PayPal token endpoint

Cause: The Base64 encoding of clientId:secret is incorrect, or the PAYPAL_CLIENT_SECRET environment variable is missing.

Solution: Verify your credentials are copied correctly without extra spaces. The Authorization header for PayPal OAuth uses Basic authentication with Base64-encoded 'clientId:secret'. Check that PAYPAL_CLIENT_SECRET is in your .env without NEXT_PUBLIC_ prefix. Confirm you're using sandbox credentials with the sandbox URL (api-m.sandbox.paypal.com).

typescript
1// Verify the auth header construction:
2const auth = Buffer.from(
3 `${process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
4).toString('base64');
5console.log('Auth header:', `Basic ${auth}`.substring(0, 20) + '...'); // debug only

PayPal webhooks not arriving after deployment

Cause: The webhook URL is not registered in the PayPal developer dashboard, or the deployed URL is incorrect.

Solution: Go to developer.paypal.com → Apps & Credentials → your app → Webhooks → Add Webhook. Enter your exact deployed URL including the path (e.g., https://your-app.netlify.app/api/paypal/webhook). Select the payment.capture.completed and checkout.order.approved event types. Test the webhook using PayPal's webhook simulator in the developer dashboard.

Payment works in sandbox but fails with 'INSTRUMENT_DECLINED' in production

Cause: The buyer's PayPal account has restrictions, the payment method was declined, or you're using sandbox credentials in production.

Solution: Verify you've switched to live credentials (live client ID and secret) in your production environment variables. INSTRUMENT_DECLINED is a buyer-side error — the buyer's bank or PayPal account declined the payment. This is not a code error. Check PayPal's error response body for the decline_code which indicates the specific reason.

Best practices

  • Always compare PayPal's manual setup effort against Stripe's native one-prompt Bolt integration — choose PayPal only when your users specifically require it or when PayPal's buyer protection is a selling point
  • Never store your PayPal Client Secret in client-side code or NEXT_PUBLIC_ environment variables — it must only be read by server-side API routes
  • Cache PayPal access tokens (valid for 9 hours) to avoid requesting a new OAuth token on every payment — store the token and expiry in memory or Redis
  • Always capture orders immediately after approval rather than using the AUTHORIZE intent for delayed capture, unless you have a specific delayed-charge use case
  • Test with multiple PayPal sandbox accounts (different countries, currencies) to catch international payment issues before going live
  • Implement idempotency in your order creation — if a network error causes the user to retry, don't create duplicate orders in your database
  • Verify webhook signatures using PayPal's verification API rather than trusting webhook bodies without validation
  • Switch your PAYPAL_API_URL from api-m.sandbox.paypal.com to api-m.paypal.com using an environment variable, not by changing code, when going from testing to production

Alternatives

Frequently asked questions

Does Bolt.new have a native PayPal integration?

No. Bolt.new's native payment integration is exclusive to Stripe. PayPal must be implemented manually using PayPal's REST APIs through a Next.js API route and the @paypal/react-paypal-js client library. The manual setup takes approximately 30 minutes and involves more steps than Stripe's one-prompt setup.

Can I test PayPal payments in the Bolt.new preview?

Partially. You can test the order creation flow (creating a PayPal order and seeing the PayPal checkout popup) in the Bolt preview since these are outbound HTTP calls. However, PayPal webhook events (confirming payment completion) require a publicly accessible URL that Bolt's WebContainer cannot provide. Complete end-to-end payment testing requires deploying to Netlify or Bolt Cloud first.

How do I switch from PayPal sandbox to live payments?

Create a separate Live app in the PayPal developer dashboard and note the live client ID and secret. Update your environment variables in your hosting platform (Netlify/Vercel) to use live credentials and change PAYPAL_API_URL to https://api-m.paypal.com. Register your production webhook endpoint using the live app's webhook settings. Your code requires no changes — all credentials are read from environment variables.

Why is Stripe recommended over PayPal for Bolt.new projects?

Stripe is Bolt's only native payment integration and can be set up with a single chat prompt like 'Add Stripe checkout to my app' — Bolt auto-generates all the code, edge functions, and webhook handling. PayPal requires manually building the OAuth flow, order creation routes, checkout component, and webhook handler. For new projects without existing PayPal requirements, Stripe's developer experience is significantly better.

Can I offer both PayPal and Stripe on the same checkout page?

Yes. You can show both payment options by implementing Stripe's checkout for card payments (using Bolt's native integration) and adding the PayPal button component alongside it. Both APIs route through separate API routes and operate independently. Many e-commerce apps offer PayPal as an additional checkout method alongside card payments to maximize conversion by letting customers choose their preferred method.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.