Integrate Braintree (by PayPal) with Bolt.new using an API route to generate client tokens server-side and the Braintree Drop-in UI client-side. The Drop-in UI handles credit cards, PayPal, Venmo, and Apple Pay in a single embeddable component that works in Bolt's WebContainer preview. Deploy to Netlify or Bolt Cloud before testing Braintree webhooks — payment event notifications require a public URL that the WebContainer cannot provide.
Add PayPal, Venmo, and Card Payments to Your Bolt.new App with Braintree
Braintree, owned by PayPal, is the payment processor that powers many large e-commerce sites and on-demand platforms. Its key differentiator is natively combining credit and debit card processing, PayPal, Venmo (US), Apple Pay, and Google Pay into a single integration — you don't need to set up separate provider accounts for each payment method. If your audience includes customers who prefer PayPal or Venmo over entering card details, Braintree is the straightforward way to add those options alongside standard card processing.
The Braintree Drop-in UI is the fastest integration path and the recommended starting point for Bolt.new apps. It's an embeddable JavaScript component that renders a complete payment form — card fields, PayPal button, Venmo button — styled to match common UI conventions. You don't build the payment form; Braintree renders it. The Drop-in handles card tokenization, PayPal OAuth flows, and 3D Secure challenges internally, passing only a payment method nonce to your server. This nonce is a one-time-use token representing the payment method — your server exchanges it for an actual transaction using your Braintree private key.
The architectural pattern is critical to understand: the client token flow separates the two halves of the integration. Your server generates a client token using your Braintree private credentials (which stays server-side), the Drop-in UI initializes using this token, and after the customer selects a payment method, the Drop-in produces a nonce that your server uses to create the transaction. This two-step flow ensures payment method nonces are short-lived and that your private API key never touches client code. Braintree webhooks for subscription events and settlement notifications require a deployed public URL — the WebContainer cannot receive incoming HTTP connections.
Integration method
Braintree integrates with Bolt.new through two layers: a server-side API route that generates client tokens (using your Braintree private key, which must never reach the browser) and the Braintree Drop-in UI JavaScript library on the client (which handles all payment method collection and card tokenization). The client token flow is the key architectural pattern — the server generates a short-lived token, the Drop-in UI uses it to initialize, and after the customer enters payment details, the Drop-in sends a payment method nonce back to your server for transaction completion. Payment webhooks require a deployed URL and cannot be received in the WebContainer.
Prerequisites
- A Braintree sandbox account at sandbox.braintreegateway.com (free, instant registration)
- Braintree merchant ID, public key, and private key from the sandbox Control Panel
- A Bolt.new project with Next.js for API routes (or Vite with Supabase Edge Functions)
- braintree npm package installed (npm install braintree) — pure JavaScript, WebContainer compatible
- A deployed URL on Netlify or Bolt Cloud for testing Braintree webhooks and subscription events
Step-by-step guide
Create a Braintree Sandbox Account and Get Credentials
Create a Braintree Sandbox Account and Get Credentials
Braintree provides instant sandbox access — no business verification or waiting period required. Go to sandbox.braintreegateway.com and click 'Sign Up for Free'. Create your account using an email address and password. Once registered, you're immediately in the Braintree Control Panel for the sandbox environment. To find your API credentials, navigate to Account → My User → API Keys, Tokenization Keys, Encryption Keys. Click 'View' next to your API keys to reveal the three values you need: Merchant ID (a short alphanumeric string that identifies your account), Public Key (safe for server-side use to authenticate read operations), and Private Key (must be kept secret — used to create transactions, generate client tokens, and modify vaults). Copy all three values into your Bolt project's `.env` file. There's also a fourth credential called the Tokenization Key — this can be used client-side in some integration patterns, but for the Drop-in UI with proper server-side token generation, you'll use the server credentials. Never put your Private Key in any client-side variable — in Bolt, that means never using `VITE_BRAINTREE_PRIVATE_KEY` as a prefix, and in Next.js, never accessing `process.env.BRAINTREE_PRIVATE_KEY` from a component or client-side hook. The sandbox environment is completely isolated from production — test transactions never charge real cards, and the sandbox has its own set of test card numbers and PayPal test accounts.
1# .env — Braintree sandbox credentials2# Find in Braintree Control Panel → Account → API Keys3BRAINTREE_MERCHANT_ID=your_merchant_id4BRAINTREE_PUBLIC_KEY=your_public_key5BRAINTREE_PRIVATE_KEY=your_private_key6BRAINTREE_ENVIRONMENT=sandbox78# For Next.js server-side: process.env.BRAINTREE_PRIVATE_KEY9# For production, change BRAINTREE_ENVIRONMENT to 'production'10# and replace all three keys with production credentialsPro tip: Braintree sandbox test cards: 4111111111111111 (Visa, success), 5431111111111111 (Mastercard, success), 4000111111111115 (Visa, processor declined). For sandbox PayPal testing, use the PayPal sandbox buyer accounts in your Braintree Control Panel under Sandbox → PayPal Sandbox Accounts.
Expected result: You have Braintree sandbox credentials in your .env file. Navigate to the Braintree Control Panel and verify the merchant ID matches what's in your .env.
Create the Client Token Generation API Route
Create the Client Token Generation API Route
The client token is the bridge between your server's Braintree credentials and the Drop-in UI on the client. Every time the checkout page loads, your server generates a fresh client token by authenticating with Braintree using your merchant ID, public key, and private key. The client token is a short-lived authorization token that the Drop-in UI uses to securely communicate with Braintree's client-side systems. This token can optionally be tied to a specific customer ID from Braintree's Vault — if you store customer payment methods for recurring billing, passing the customer ID generates a token that shows the customer's saved payment methods in the Drop-in. For one-time checkout without saved methods, generate a token without a customer ID. Install the `braintree` npm package (`npm install braintree`). This is a pure JavaScript package with no native module dependencies, making it fully compatible with Bolt's WebContainer for server-side use in Next.js API routes. The braintree package communicates with Braintree's servers over HTTPS — no TCP-only protocols — so it works seamlessly from within the WebContainer environment. The API route should generate a client token and return it to the frontend. The frontend drops this token into the Drop-in UI initialization call.
Create a Braintree client token API route at app/api/braintree/client-token/route.ts. Use the braintree npm package to initialize a gateway with the BRAINTREE_MERCHANT_ID, BRAINTREE_PUBLIC_KEY, BRAINTREE_PRIVATE_KEY, and BRAINTREE_ENVIRONMENT environment variables. The route should generate a client token using gateway.clientToken.generate({}) and return { clientToken: token } as JSON. Handle errors by returning a 500 response with the error message.
Paste this in Bolt.new chat
1// app/api/braintree/client-token/route.ts2import { NextResponse } from 'next/server';3import braintree from 'braintree';45function getGateway() {6 const environment = process.env.BRAINTREE_ENVIRONMENT === 'production'7 ? braintree.Environment.Production8 : braintree.Environment.Sandbox;910 return new braintree.BraintreeGateway({11 environment,12 merchantId: process.env.BRAINTREE_MERCHANT_ID!,13 publicKey: process.env.BRAINTREE_PUBLIC_KEY!,14 privateKey: process.env.BRAINTREE_PRIVATE_KEY!,15 });16}1718export async function POST(request: Request) {19 const gateway = getGateway();2021 // Optional: pass customerId to show saved payment methods22 const body = await request.json().catch(() => ({}));23 const generateParams = body.customerId24 ? { customerId: body.customerId }25 : {};2627 const response = await gateway.clientToken.generate(generateParams);2829 if (!response.success) {30 return NextResponse.json(31 { error: 'Failed to generate client token' },32 { status: 500 }33 );34 }3536 return NextResponse.json({ clientToken: response.clientToken });37}Pro tip: The braintree Node.js SDK is pure JavaScript and installs without native module compilation — it works correctly in Bolt's WebContainer. Run: npm install braintree @types/braintree
Expected result: Calling POST /api/braintree/client-token returns a JSON object with a clientToken string that can be passed to the Drop-in UI initialization.
Integrate Braintree Drop-in UI in a React Component
Integrate Braintree Drop-in UI in a React Component
The Braintree Drop-in UI is a prebuilt, embeddable payment form that renders inside a div in your React component. It loads from Braintree's CDN and handles all payment method selection, card field rendering, PayPal OAuth flow, and tokenization. To use it in a React component, fetch the client token from your API route, then initialize the Drop-in using `braintree.dropin.create()` from the `braintree-web-drop-in` package. The Drop-in renders into a target container div by ID. When the customer fills in payment details and clicks your submit button, call `dropinInstance.requestPaymentMethod()` to get the payment method nonce. This nonce is a short-lived, single-use token — send it to your server-side checkout API route along with the order amount. The Drop-in UI handles the visual presentation, card validation, PayPal popup flow, and 3D Secure challenges internally. Since the Drop-in loads from Braintree's CDN and communicates with Braintree's servers directly over HTTPS, it works completely within Bolt's WebContainer preview — this is fully client-side functionality with no incoming connection requirements. Install the package with `npm install braintree-web-drop-in`. The Drop-in can be configured to show or hide specific payment methods based on what's enabled in your Braintree Control Panel.
Create a BraintreeCheckout React component. On mount, fetch a client token from /api/braintree/client-token using POST. Initialize the Braintree Drop-in UI from the braintree-web-drop-in package inside a div with id='dropin-container'. Add a 'Pay Now' button that calls dropinInstance.requestPaymentMethod() to get the payment nonce, then sends it to /api/braintree/checkout with the order amount. Show loading states, disable the button while processing, and display success or error messages. On success, redirect to /order-success.
Paste this in Bolt.new chat
1// components/BraintreeCheckout.tsx2'use client';3import { useEffect, useRef, useState } from 'react';4import type { Dropin } from 'braintree-web-drop-in';56interface BraintreeCheckoutProps {7 amount: string; // e.g. '29.99'8 onSuccess: (transactionId: string) => void;9}1011export function BraintreeCheckout({ amount, onSuccess }: BraintreeCheckoutProps) {12 const dropinInstanceRef = useRef<Dropin | null>(null);13 const [loading, setLoading] = useState(true);14 const [processing, setProcessing] = useState(false);15 const [error, setError] = useState<string | null>(null);1617 useEffect(() => {18 let mounted = true;1920 async function initDropin() {21 // Fetch client token from server22 const tokenRes = await fetch('/api/braintree/client-token', {23 method: 'POST',24 headers: { 'Content-Type': 'application/json' },25 body: JSON.stringify({}),26 });27 const { clientToken } = await tokenRes.json();2829 // Dynamically import to avoid SSR issues30 const dropin = await import('braintree-web-drop-in');3132 if (!mounted) return;3334 const instance = await dropin.default.create({35 authorization: clientToken,36 container: '#dropin-container',37 paypal: { flow: 'checkout', amount, currency: 'USD' },38 });3940 dropinInstanceRef.current = instance;41 setLoading(false);42 }4344 initDropin().catch((err) => {45 setError('Failed to load payment form');46 setLoading(false);47 });4849 return () => { mounted = false; };50 }, [amount]);5152 async function handleSubmit() {53 if (!dropinInstanceRef.current) return;54 setProcessing(true);55 setError(null);5657 const { nonce } = await dropinInstanceRef.current.requestPaymentMethod();5859 const checkoutRes = await fetch('/api/braintree/checkout', {60 method: 'POST',61 headers: { 'Content-Type': 'application/json' },62 body: JSON.stringify({ nonce, amount }),63 });6465 const result = await checkoutRes.json();6667 if (result.success) {68 onSuccess(result.transactionId);69 } else {70 setError(result.error ?? 'Payment failed');71 setProcessing(false);72 }73 }7475 return (76 <div className="max-w-md mx-auto">77 {loading && <p className="text-gray-500 text-center py-8">Loading payment form...</p>}78 <div id="dropin-container" />79 {error && <p className="text-red-600 text-sm mt-2">{error}</p>}80 {!loading && (81 <button82 onClick={handleSubmit}83 disabled={processing}84 className="w-full mt-4 bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50"85 >86 {processing ? 'Processing...' : `Pay $${amount}`}87 </button>88 )}89 </div>90 );91}Pro tip: Use dynamic import for braintree-web-drop-in to avoid SSR errors in Next.js. The Drop-in UI accesses browser APIs on initialization and will throw if imported at the module level in a server component context.
Expected result: The checkout page renders the Braintree Drop-in UI with card fields and PayPal button. Filling in test card details and clicking Pay Now triggers the payment flow.
Process Transactions Server-Side and Handle Webhooks After Deploying
Process Transactions Server-Side and Handle Webhooks After Deploying
When the Drop-in UI returns a payment method nonce, your server-side checkout API route uses this nonce to create an actual Braintree transaction. The nonce is single-use and short-lived — process it immediately when received. Create the transaction using `gateway.transaction.sale()` with the nonce, amount, and any additional options like storing the payment method in the Vault for future use. Braintree responds with a transaction object containing the transaction ID, status (authorized, submitted_for_settlement, settled, declined), and payment method details. Store the transaction ID in your database for order tracking and potential refunds. For recurring billing and subscription events, Braintree sends webhook notifications to a URL you configure in the Control Panel. These webhooks notify you of subscription renewals, payment failures, disbursements, and disputes. Since Braintree webhooks are server-to-server HTTP calls from Braintree's infrastructure to your app, they cannot reach Bolt's WebContainer preview — the WebContainer is a browser-local environment with no publicly routable address. Deploy to Netlify or Bolt Cloud first, then configure the webhook URL in Braintree Control Panel → Settings → Webhooks → Create New Webhook. Set the destination URL to your deployed endpoint and select the event types you want to receive.
Create a Braintree transaction processing API route at app/api/braintree/checkout/route.ts that accepts a POST request with nonce (string) and amount (string). Use the braintree gateway to call gateway.transaction.sale() with the nonce, amount, and options.submitForSettlement: true. Return { success: true, transactionId } on success or { success: false, error: message } on failure. Also create a webhook handler at app/api/braintree/webhook/route.ts that parses Braintree webhook notifications and handles subscription_charged_successfully and subscription_went_past_due events.
Paste this in Bolt.new chat
1// app/api/braintree/checkout/route.ts2import { NextResponse } from 'next/server';3import braintree from 'braintree';45const gateway = new braintree.BraintreeGateway({6 environment: process.env.BRAINTREE_ENVIRONMENT === 'production'7 ? braintree.Environment.Production8 : braintree.Environment.Sandbox,9 merchantId: process.env.BRAINTREE_MERCHANT_ID!,10 publicKey: process.env.BRAINTREE_PUBLIC_KEY!,11 privateKey: process.env.BRAINTREE_PRIVATE_KEY!,12});1314export async function POST(request: Request) {15 const { nonce, amount } = await request.json();1617 const result = await gateway.transaction.sale({18 amount,19 paymentMethodNonce: nonce,20 options: {21 submitForSettlement: true,22 },23 });2425 if (result.success) {26 return NextResponse.json({27 success: true,28 transactionId: result.transaction.id,29 status: result.transaction.status,30 });31 }3233 // Extract validation errors34 const errorMessage = result.errors?.deepErrors()?.[0]?.message35 ?? result.message36 ?? 'Transaction failed';3738 return NextResponse.json({ success: false, error: errorMessage }, { status: 400 });39}Pro tip: Always set submitForSettlement: true unless you want to manually settle transactions later. Transactions that are only authorized (not submitted for settlement) will expire and not capture funds.
Expected result: Submitting a payment method nonce to /api/braintree/checkout creates a Braintree transaction and returns a transaction ID. For webhooks, deploy first and configure the notification URL in the Braintree Control Panel.
Common use cases
Multi-Method Checkout with PayPal and Cards
Let customers choose between paying with a credit card, PayPal account, or Venmo on the same checkout page. The Braintree Drop-in UI renders all enabled payment methods automatically based on what the customer has available. No separate PayPal integration needed.
Add a checkout page using Braintree Drop-in UI that accepts credit cards and PayPal. Create an API route at /api/braintree/client-token to generate a client token. Create another route at /api/braintree/checkout to process the payment nonce. The checkout page should show the Drop-in UI, an order summary with total, and a Pay button. On success, redirect to /order-success and save the transaction ID to Supabase.
Copy this prompt to try it in Bolt.new
Subscription Billing with Braintree Plans
Set up recurring billing plans in the Braintree Control Panel and let users subscribe with any supported payment method. Braintree manages the recurring charge schedule, retry logic for failed payments, and subscription status webhooks.
Create a subscription signup flow using Braintree. Show pricing cards for Monthly ($19) and Annual ($149) plans. When a user selects a plan and enters payment details via Braintree Drop-in UI, create a Braintree subscription using the plan ID from the Control Panel and the payment method nonce. Store the subscription ID in Supabase against the user's profile. Handle the subscription webhook to update subscription status on renewal and cancellation.
Copy this prompt to try it in Bolt.new
Marketplace Split Payments
Build a marketplace where sellers list services and Braintree's submerchant accounts handle payment routing. Payments from buyers split automatically between your platform fee and the seller's account.
Add a marketplace payment system where buyers pay service providers using Braintree. Create a checkout flow that collects payment via Drop-in UI, then processes a split transaction: 85% goes to the service provider's Braintree submerchant account (stored in Supabase as their merchant_account_id) and 15% stays as our platform fee. Show the fee breakdown before payment. Store transaction details in Supabase.
Copy this prompt to try it in Bolt.new
Troubleshooting
Drop-in UI shows 'Unable to initialize PayPal' or PayPal button doesn't appear
Cause: PayPal on Braintree requires the PayPal sandbox account to be configured in the Braintree Control Panel, and the Drop-in must be initialized with paypal: { flow: 'checkout', amount, currency } configuration.
Solution: In Braintree Control Panel → Sandbox → PayPal Sandbox Accounts, verify a PayPal sandbox account is linked. Ensure the paypal configuration object in dropin.create() includes flow ('checkout' for one-time or 'vault' for saving), amount, and currency. The amount must be a string, not a number.
1// Correct Drop-in configuration with PayPal2await dropin.create({3 authorization: clientToken,4 container: '#dropin-container',5 paypal: {6 flow: 'checkout', // or 'vault' for saving payment method7 amount: '29.99', // must be a string8 currency: 'USD',9 },10});gateway.transaction.sale() returns 'Authentication Invalid' or 401 error
Cause: The Braintree gateway is initialized with incorrect credentials, or the environment doesn't match — using sandbox credentials against the production environment or vice versa.
Solution: Verify your .env values match exactly what's shown in the Braintree Control Panel under Account → API Keys. Confirm BRAINTREE_ENVIRONMENT is set to 'sandbox' for test keys and 'production' for live keys. The sandbox Control Panel is at sandbox.braintreegateway.com and production at braintreegateway.com — they have different credential sets.
Braintree webhooks are not being received after deploying
Cause: The webhook destination URL in Braintree Control Panel is misconfigured, or Braintree cannot verify the endpoint (it sends a verification challenge on first activation).
Solution: In Braintree Control Panel → Settings → Webhooks, verify the destination URL matches your deployed endpoint exactly, including the /api/braintree/webhook path. Use the 'Verify' button in the Braintree Control Panel — it sends a test notification to confirm the URL is reachable. Braintree webhooks cannot reach the WebContainer preview — only deployed Netlify or Bolt Cloud URLs work.
braintree-web-drop-in throws 'window is not defined' during Next.js build
Cause: The Drop-in UI package accesses browser APIs that aren't available during server-side rendering. Importing it at the module level in a component that Next.js renders on the server causes build failures.
Solution: Use dynamic import inside a useEffect hook or with Next.js dynamic() to ensure the Drop-in only loads in the browser. Add 'use client' to the component and import braintree-web-drop-in inside the useEffect where it executes only client-side.
1// Use dynamic import inside useEffect, not at module level2const dropin = await import('braintree-web-drop-in');3const instance = await dropin.default.create({ ... });Best practices
- Always generate a fresh client token on each checkout page load — client tokens are short-lived and should not be cached or reused across sessions
- Set submitForSettlement: true in transaction.sale() unless you have a specific reason to authorize and capture separately — uncaptured authorizations expire and don't collect funds
- Store the Braintree transaction ID alongside your order record in Supabase for refunds, dispute management, and audit trails
- Use the Braintree Vault for repeat customers — store payment methods with storeInVaultOnSuccess: true and display saved methods via the Drop-in using a customer ID client token
- Never put your Braintree private key in any VITE_ prefixed variable or client-side code — only access it from Next.js API routes or Supabase Edge Functions
- Test all Braintree processor response codes in sandbox using their test card numbers (processor declined, card expired, gateway rejected) before going live
- Deploy before testing webhooks and subscription event handling — Braintree cannot send server-to-server notifications to Bolt's WebContainer browser-local environment
- Use Braintree's test PayPal sandbox accounts (available in the Control Panel) to test PayPal checkout flows end-to-end in the sandbox environment
Alternatives
Stripe has native first-class Bolt.new integration and broader developer tooling — the better default choice unless PayPal and Venmo acceptance is a specific business requirement.
Direct PayPal integration is simpler for PayPal-only flows, but Braintree supports PayPal alongside cards and Venmo in one unified SDK making it more versatile.
Adyen provides enterprise-grade payment infrastructure with advanced fraud tools and direct acquiring — suitable for high-volume platforms that need more control than Braintree.
Square combines in-person POS and online payments in one ecosystem — better for businesses with physical locations, while Braintree focuses on online and mobile payments.
Frequently asked questions
How does Braintree differ from Stripe for a Bolt.new integration?
Stripe has a native Bolt.new integration (Settings → Stripe panel, auto-generated edge functions) while Braintree requires manual API route setup. Braintree's key advantage is natively supporting PayPal and Venmo alongside cards in a single Drop-in UI — same parent company (PayPal) means tighter integration. Choose Braintree when PayPal and Venmo acceptance is a business requirement; choose Stripe for the easiest Bolt.new setup path.
Can I test the Braintree Drop-in UI in Bolt's WebContainer preview?
Yes, the Drop-in UI loads from Braintree's CDN and communicates with Braintree over HTTPS — both fully supported by the WebContainer's outbound networking. You can test card entry, PayPal flows, and transaction processing in the preview. However, Braintree webhooks (subscription events, settlement notifications) require a deployed public URL and cannot be received in the preview.
What is a payment method nonce and why does it matter?
A payment method nonce is a short-lived, single-use token that represents a customer's payment details — the Drop-in UI creates it after the customer enters their card or selects PayPal. Your server exchanges this nonce for an actual transaction using your private API key. The nonce pattern keeps raw card data off your servers entirely (the Drop-in sends card data directly to Braintree, never through your app) while giving your server a secure handle to complete the payment.
Does the braintree npm package work in Bolt's WebContainer?
Yes. The braintree Node.js SDK is pure JavaScript with no native module dependencies (no node-gyp, no C++ addons) and communicates with Braintree's servers over HTTPS. It installs and runs correctly in Bolt's WebContainer for use in Next.js API routes. Run npm install braintree in your Bolt project terminal to add it.
How do I switch Braintree from sandbox to production?
Change BRAINTREE_ENVIRONMENT from 'sandbox' to 'production' in your deployment environment variables, and replace BRAINTREE_MERCHANT_ID, BRAINTREE_PUBLIC_KEY, and BRAINTREE_PRIVATE_KEY with your production credentials from the Braintree production Control Panel (braintreegateway.com). Set these in Netlify's environment variables (not in .env which is development-only) and redeploy. Register new webhook URLs in the production Braintree Control Panel as well.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation