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

How to Integrate Bolt.new with Pardot

Pardot (Salesforce Marketing Cloud Account Engagement) integrates with Bolt.new through a Next.js API route using Salesforce OAuth 2.0. Set up a Salesforce Connected App to get credentials, deploy your app to handle the OAuth callback (cannot be done in the WebContainer preview), store the access token in .env, and call Pardot's API to manage prospects, list memberships, and marketing automation for B2B campaigns.

What you'll learn

  • How to set up a Salesforce Connected App for Pardot API access and configure OAuth 2.0 with the correct scopes
  • How to handle the Salesforce OAuth callback on your deployed site and exchange the authorization code for an access token
  • How to manage Pardot prospects (create, read, update) via API routes in a Bolt.new Next.js app
  • How to add and remove prospects from Pardot lists to trigger marketing automation sequences
  • How to build a prospect scoring dashboard that surfaces Pardot lead scores and campaign engagement data in React
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate17 min read35 minutesMarketingApril 2026RapidDev Engineering Team
TL;DR

Pardot (Salesforce Marketing Cloud Account Engagement) integrates with Bolt.new through a Next.js API route using Salesforce OAuth 2.0. Set up a Salesforce Connected App to get credentials, deploy your app to handle the OAuth callback (cannot be done in the WebContainer preview), store the access token in .env, and call Pardot's API to manage prospects, list memberships, and marketing automation for B2B campaigns.

Pardot B2B Marketing Automation Integration for Bolt.new Apps

Pardot, now officially named Salesforce Marketing Cloud Account Engagement, is the B2B marketing automation layer of the Salesforce ecosystem. While Salesforce CRM manages deals and contacts from the sales side, Pardot handles the marketing side: tracking prospect behavior on websites, scoring leads based on engagement, sending automated email nurture sequences, and feeding qualified leads to the sales team in Salesforce. The two platforms share a unified data model — a Pardot prospect and a Salesforce contact are synchronized records, which makes Pardot the natural choice for marketing automation in organizations already running Salesforce.

For Bolt.new developers, Pardot integration typically arises in two scenarios. First, an organization wants a custom lead capture form on a Bolt-built website that feeds directly into Pardot rather than using Pardot's native form builder — this gives full control over the form design while still triggering Pardot's automation sequences. Second, a team wants an internal dashboard that surfaces Pardot's prospect scoring and campaign engagement data alongside other business metrics from Salesforce, giving sales managers a unified view without needing Pardot licenses for everyone.

Pardot's authentication is handled entirely through Salesforce — you need a Salesforce Connected App (configured in Salesforce Setup) rather than a separate Pardot developer registration. This is both a strength (unified auth across Salesforce and Pardot) and a complication (OAuth 2.0 with redirect URIs that require a deployed site). The Pardot API uses version 5 (current) and identifies your specific Pardot instance via a Business Unit ID (formerly called the Pardot account ID). All API requests include both the Salesforce Bearer token in the Authorization header and the Business Unit ID in a separate Pardot-Business-Unit-Id header.

Integration method

Bolt Chat + API Route

Pardot uses Salesforce OAuth 2.0 for authentication — you create a Salesforce Connected App to get client credentials, complete the OAuth flow on your deployed site (not in Bolt's WebContainer preview), and use the resulting Salesforce access token alongside your Pardot Business Unit ID to call the Pardot API. All API requests go through Next.js server-side routes that keep credentials out of the browser.

Prerequisites

  • A Salesforce account with Pardot (Marketing Cloud Account Engagement) enabled — requires both a Salesforce license and a Pardot add-on; no free tier exists
  • A Salesforce Connected App created in Salesforce Setup → App Manager with OAuth 2.0 configured and the correct scopes (pardot_api, api, refresh_token)
  • Your Pardot Business Unit ID found in Pardot Settings → Account Settings (a numeric ID starting with '0Uv')
  • A deployed Bolt.new app on Netlify or Bolt Cloud to use as the OAuth redirect URI — Salesforce OAuth cannot be completed in Bolt's WebContainer preview
  • Salesforce administrator access to set up the Connected App and grant OAuth permissions to the integration user

Step-by-step guide

1

Create a Salesforce Connected App for Pardot API access

Pardot authentication goes through Salesforce — you do not register a separate Pardot developer application. Instead, you create a Salesforce Connected App that is granted Pardot API access. This approach gives you a single OAuth flow that authenticates with both Salesforce and Pardot simultaneously. To create a Connected App, log into Salesforce and go to Setup (the gear icon in the top-right, then 'Setup'). In the Quick Find search box, type 'App Manager' and click it. Click 'New Connected App' in the top-right. Fill in the required fields: Connected App Name (e.g., 'My Bolt App'), API Name (auto-populated), and Contact Email. In the API section, check 'Enable OAuth Settings.' The Callback URL (redirect URI) must be a stable, publicly accessible URL — your deployed Netlify or Bolt Cloud site followed by your callback path: for example, https://your-app.netlify.app/api/auth/callback/salesforce. This cannot be the Bolt WebContainer preview URL because the preview uses dynamic StackBlitz-generated URLs that change between sessions. For OAuth scopes, select: 'Access Pardot services (pardot_api)', 'Access and manage your data (api)', and 'Perform requests at any time (refresh_token, offline_access).' The pardot_api scope is essential — without it, your Salesforce token will not have access to Pardot data. Save the Connected App. Salesforce takes a few minutes to provision it. Then go back to the app and click 'Manage Consumer Details' to reveal the Consumer Key (this is your client ID) and Consumer Secret. Copy both values. Store them in .env as SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET. Also find your Pardot Business Unit ID: in your Pardot account, go to Settings → Account Settings. The Pardot Business Unit ID starts with '0Uv' and is a Salesforce ID format. Store it in .env as PARDOT_BUSINESS_UNIT_ID. This ID is required in the Pardot-Business-Unit-Id header on every Pardot API request.

Bolt.new Prompt

Add Pardot and Salesforce OAuth credentials to .env. Create the file with: SALESFORCE_CLIENT_ID=your_connected_app_consumer_key, SALESFORCE_CLIENT_SECRET=your_connected_app_consumer_secret, PARDOT_BUSINESS_UNIT_ID=0Uv..., SALESFORCE_INSTANCE_URL=https://yourorg.my.salesforce.com, and NEXT_PUBLIC_APP_URL=https://your-app.netlify.app. Add comments explaining where to find each value.

Paste this in Bolt.new chat

.env
1# .env
2# Salesforce Connected App credentials for Pardot API access
3# Get from Salesforce Setup App Manager your app Manage Consumer Details
4SALESFORCE_CLIENT_ID=your_consumer_key_here
5SALESFORCE_CLIENT_SECRET=your_consumer_secret_here
6
7# Pardot Business Unit ID found in Pardot Settings Account Settings
8# Format: 0Uv followed by alphanumeric characters
9PARDOT_BUSINESS_UNIT_ID=0Uv000000000000AAA
10
11# Your Salesforce org's instance URL (from your Salesforce login URL)
12SALESFORCE_INSTANCE_URL=https://yourorg.my.salesforce.com
13
14# Access token (populated after completing OAuth flow)
15# Server-side only never NEXT_PUBLIC_
16PARDOT_ACCESS_TOKEN=your_access_token_here
17
18# Your deployed app URL used for OAuth redirect URI
19NEXT_PUBLIC_APP_URL=https://your-app.netlify.app

Pro tip: In Salesforce, it takes 2-10 minutes after saving a new Connected App for the OAuth flow to work. If you get a 'consumer_key not found' error immediately after creating the app, wait a few minutes and try again.

Expected result: A Salesforce Connected App is configured with the pardot_api scope and a deployed redirect URI. Client ID and secret are in .env alongside the Pardot Business Unit ID.

2

Handle Salesforce OAuth 2.0 and build the Pardot API service

With the Connected App configured, build the OAuth callback handler and Pardot API service. The OAuth flow uses Salesforce's token endpoint, and the resulting access token works for both Salesforce and Pardot API calls. The Salesforce authorization URL is https://login.salesforce.com/services/oauth2/authorize. The token endpoint is https://login.salesforce.com/services/oauth2/token. After authorization, the callback URL receives a code parameter that you exchange for tokens. Salesforce returns an access_token, refresh_token, instance_url (the specific Salesforce server for your org), and id (identity URL). For Pardot API calls, you need two headers on every request: Authorization: Bearer {access_token} (the Salesforce token) and Pardot-Business-Unit-Id: {business_unit_id}. The Pardot API v5 base URL is https://pi.pardot.com/api/v5. All endpoints follow RESTful patterns: GET /objects/prospects for listing, POST /objects/prospects for creating, GET /objects/prospects/{id} for retrieving a specific prospect. Build a pardotFetch utility function that automatically adds both required headers and handles errors. Wrap common operations in typed helper functions: getProspects, createProspect, updateProspect, addToList, and removeFromList. This abstraction makes the API routes clean and the business logic reusable across multiple API endpoints. A critical note about Bolt's development environment: outbound HTTPS calls to pi.pardot.com and login.salesforce.com work in the WebContainer preview for API operations (creating prospects, fetching data). The only operation that requires the deployed site is the OAuth authorization callback — after you have the access token, you can continue developing and testing in the Bolt preview.

Bolt.new Prompt

Create a Pardot API integration for this Next.js app. (1) Create lib/pardot.ts with a pardotFetch(endpoint, options) utility using PARDOT_ACCESS_TOKEN (Bearer) and PARDOT_BUSINESS_UNIT_ID (Pardot-Business-Unit-Id header). Use https://pi.pardot.com/api/v5 as base URL. Export: getProspects(limit, offset), createProspect(fields), updateProspect(id, fields), getProspect(id), addToList(prospectId, listId), and removeFromList(prospectId, listId). (2) Create app/api/pardot/prospects/route.ts with GET (list prospects with optional search) and POST (create prospect, then add to PARDOT_DEFAULT_LIST_ID if set). Include TypeScript types for Pardot prospect fields.

Paste this in Bolt.new chat

lib/pardot.ts
1// lib/pardot.ts
2const PARDOT_BASE = 'https://pi.pardot.com/api/v5';
3
4async function pardotFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
5 const token = process.env.PARDOT_ACCESS_TOKEN;
6 const buId = process.env.PARDOT_BUSINESS_UNIT_ID;
7
8 if (!token) throw new Error('PARDOT_ACCESS_TOKEN not configured');
9 if (!buId) throw new Error('PARDOT_BUSINESS_UNIT_ID not configured');
10
11 const res = await fetch(`${PARDOT_BASE}${endpoint}`, {
12 ...options,
13 headers: {
14 Authorization: `Bearer ${token}`,
15 'Pardot-Business-Unit-Id': buId,
16 'Content-Type': 'application/json',
17 ...options.headers,
18 },
19 });
20
21 if (!res.ok) {
22 const body = await res.json().catch(() => ({ message: res.statusText }));
23 throw Object.assign(new Error(body.message ?? 'Pardot API error'), {
24 status: res.status,
25 body,
26 });
27 }
28
29 return res.json();
30}
31
32export interface PardotProspect {
33 firstName?: string;
34 lastName?: string;
35 email: string;
36 company?: string;
37 jobTitle?: string;
38 phone?: string;
39 score?: number;
40 grade?: string;
41 lastActivityAt?: string;
42}
43
44export async function createProspect(fields: PardotProspect) {
45 return pardotFetch<{ id: string; email: string }>('/objects/prospects', {
46 method: 'POST',
47 body: JSON.stringify(fields),
48 });
49}
50
51export async function getProspects(limit = 25, offset = 0) {
52 return pardotFetch<{ values: PardotProspect[]; nextPageToken?: string }>(
53 `/objects/prospects?fields=id,firstName,lastName,email,company,score,grade,lastActivityAt&limit=${limit}&offset=${offset}`
54 );
55}
56
57export async function addToList(prospectId: string, listId: string) {
58 return pardotFetch('/objects/list-memberships', {
59 method: 'POST',
60 body: JSON.stringify({ listId, prospectId }),
61 });
62}

Pro tip: Pardot v5 API returns paginated results with a nextPageToken field rather than numeric offset pagination. Store the token and pass it as a query parameter for subsequent requests when building infinite scroll or load-more pagination.

Expected result: lib/pardot.ts provides authenticated access to the Pardot v5 API with both required headers automatically applied. The /api/pardot/prospects route creates and retrieves prospects. Prospect creation from the Bolt preview creates real records in your Pardot instance.

3

Build the OAuth callback and token refresh

For production apps or multi-user scenarios, implement the full Salesforce OAuth 2.0 authorization code flow. The callback route handles the redirect from Salesforce after user authorization and exchanges the code for tokens. The Salesforce OAuth flow starts by redirecting users to https://login.salesforce.com/services/oauth2/authorize with your client ID, redirect URI, and the response_type=code parameter. Salesforce authenticates the user (or uses an existing session) and redirects back to your callback URL with a code parameter. Your callback route makes a server-side POST request to https://login.salesforce.com/services/oauth2/token to exchange the code for tokens. The response includes access_token (valid for the current session), refresh_token (long-lived, for obtaining new access tokens), instance_url (your org's specific Salesforce server URL), and issued_at (timestamp for token age tracking). Salesforce access tokens expire after a configurable period (typically 2 hours for Connected Apps with default settings, or longer if configured for offline access). Use the refresh_token with grant_type=refresh_token to obtain a new access token without re-authorization. Implement this refresh automatically in your pardotFetch function by catching 401 responses, refreshing the token, and retrying the original request. The redirect URI in the token exchange request must exactly match the URI registered in the Connected App — even a trailing slash difference will cause a 'redirect_uri_mismatch' error from Salesforce. Since the OAuth callback runs on your deployed Netlify or Bolt Cloud site, this URL must be your deployed domain. The WebContainer preview cannot serve as the redirect URI.

Bolt.new Prompt

Create Salesforce OAuth routes for Pardot access. (1) app/api/auth/salesforce/route.ts redirects to https://login.salesforce.com/services/oauth2/authorize with SALESFORCE_CLIENT_ID, redirect_uri of {NEXT_PUBLIC_APP_URL}/api/auth/callback/salesforce, response_type=code, and scope='pardot_api api refresh_token'. (2) app/api/auth/callback/salesforce/route.ts exchanges the code by POSTing to https://login.salesforce.com/services/oauth2/token with grant_type=authorization_code, code, redirect_uri, client_id, client_secret. Log the access_token (truncated) and return redirect to /dashboard. (3) app/api/auth/salesforce/refresh/route.ts accepts a POST with refresh_token and returns a fresh access_token.

Paste this in Bolt.new chat

app/api/auth/callback/salesforce/route.ts
1// app/api/auth/callback/salesforce/route.ts
2import { NextResponse } from 'next/server';
3
4export async function GET(request: Request) {
5 const { searchParams } = new URL(request.url);
6 const code = searchParams.get('code');
7 if (!code) {
8 return NextResponse.json({ error: 'Missing authorization code' }, { status: 400 });
9 }
10
11 const appUrl = process.env.NEXT_PUBLIC_APP_URL!;
12 const tokenRes = await fetch(
13 'https://login.salesforce.com/services/oauth2/token',
14 {
15 method: 'POST',
16 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
17 body: new URLSearchParams({
18 grant_type: 'authorization_code',
19 code,
20 redirect_uri: `${appUrl}/api/auth/callback/salesforce`,
21 client_id: process.env.SALESFORCE_CLIENT_ID!,
22 client_secret: process.env.SALESFORCE_CLIENT_SECRET!,
23 }),
24 }
25 );
26
27 if (!tokenRes.ok) {
28 const err = await tokenRes.json();
29 return NextResponse.json({ error: err }, { status: 400 });
30 }
31
32 const tokens = await tokenRes.json();
33 // Store tokens securely in your database or session
34 // For single-user: copy access_token to PARDOT_ACCESS_TOKEN in .env
35 console.log({
36 access_token_preview: tokens.access_token?.substring(0, 20) + '...',
37 instance_url: tokens.instance_url,
38 has_refresh_token: !!tokens.refresh_token,
39 });
40
41 return NextResponse.redirect(new URL('/dashboard', request.url));
42}

Pro tip: Save the instance_url from the OAuth token response as SALESFORCE_INSTANCE_URL in .env. This is your org's specific Salesforce server URL (e.g., https://yourorg.my.salesforce.com) and is needed for Salesforce REST API calls — using login.salesforce.com for API calls after authentication will fail.

Expected result: The Salesforce OAuth callback exchanges the authorization code for a Pardot-compatible access token. The flow completes on the deployed site. The access_token is logged (truncated) for manual storage in .env during setup.

4

Build the prospect scoring dashboard

A prospect scoring dashboard is one of the highest-value Pardot integrations — it surfaces lead scores and engagement data for sales team members who may not have individual Pardot licenses, reducing software seat costs while keeping everyone informed on lead quality. Pardot's prospect data includes a score (numeric, cumulative points from all activities), a grade (A+ to F, based on profile fit with your ideal customer profile), and detailed activity history. The score goes up with positive behaviors (opening emails, visiting pricing pages, downloading content) and can decrease with negative signals. The grade is configured in Pardot based on demographic criteria you set (company size, industry, job title, geography). For the dashboard, fetch prospects ordered by score descending using GET /objects/prospects?fields=id,firstName,lastName,email,company,score,grade,lastActivityAt&orderBy=score%20DESC. Display score with color coding: green for 75 and above (hot lead), yellow for 50-74 (warm lead), red for below 50 (cold lead). This visual system helps sales reps prioritize immediately without needing to understand the numerical scoring model. Add prospect filtering by company name or email for sales reps who want to look up specific contacts. Include a 'Last Activity' column showing the time since the prospect's last tracked engagement — a high-scoring prospect who was active yesterday is much higher priority than one who has not engaged in six months despite a high cumulative score. For the chart component, a scatter plot of score vs activity recency provides a useful prioritization quadrant: high score + recent activity (top priority), high score + stale activity (reconnect candidates), low score + recent activity (nurture candidates), low score + stale activity (deprioritize).

Bolt.new Prompt

Build a Pardot prospect scoring dashboard at app/dashboard/prospects/page.tsx. Fetch data from /api/pardot/prospects. Display a table with columns: Name (linked to a detail panel), Company, Email, Lead Score (colored chip: green 75+, yellow 50-74, red <50), Grade (A+/A/B/C/D/F), Last Activity (relative time). Add a search input filtering by company or email client-side. Add a Recharts ScatterChart with Score on X axis and DaysSinceLastActivity on Y axis, with color-coded dots. Show total counts at the top: total prospects, hot leads (75+), warm leads (50-74). Add a CSV export button.

Paste this in Bolt.new chat

app/api/pardot/prospects/route.ts
1// app/api/pardot/prospects/route.ts
2import { NextResponse } from 'next/server';
3import { getProspects } from '@/lib/pardot';
4
5export async function GET(request: Request) {
6 const { searchParams } = new URL(request.url);
7 const limit = parseInt(searchParams.get('limit') ?? '50');
8 const offset = parseInt(searchParams.get('offset') ?? '0');
9
10 try {
11 const data = await getProspects(limit, offset);
12
13 const prospects = (data.values ?? []).map((p) => ({
14 id: (p as Record<string, unknown>).id,
15 name: `${p.firstName ?? ''} ${p.lastName ?? ''}`.trim(),
16 email: p.email,
17 company: p.company ?? '',
18 score: p.score ?? 0,
19 grade: p.grade ?? 'F',
20 lastActivityAt: p.lastActivityAt ?? null,
21 }));
22
23 return NextResponse.json({
24 prospects,
25 hasMore: !!data.nextPageToken,
26 });
27 } catch (error) {
28 const message = error instanceof Error ? error.message : 'Unknown error';
29 return NextResponse.json({ error: message }, { status: 500 });
30 }
31}

Pro tip: Pardot's prospect score is cumulative and can be very high for long-tenured prospects. Consider normalizing scores for the color-coding thresholds based on your account's actual score distribution — if your average score is 200, the 75+ green threshold may not be meaningful for your team.

Expected result: The prospect dashboard displays Pardot leads sorted by score with color-coded priority indicators and a scatter chart showing score vs activity recency. Sales reps can search by company or email and export the list as CSV.

Common use cases

Custom Lead Capture Form Feeding Pardot

A branded contact form on a Bolt-built website that creates Pardot prospects when submitted, triggering lead nurture automation sequences configured in Pardot. The form captures B2B lead fields (company, job title, phone) and adds the prospect to a specific Pardot list to trigger the appropriate nurture campaign.

Bolt.new Prompt

Create a lead capture form for a B2B website that creates prospects in Pardot. Build a form with firstName, lastName, email, company, jobTitle, and phone fields. On submit, POST to /api/pardot/prospects which calls Pardot's v5 API at https://pi.pardot.com/api/v5/objects/prospects with PARDOT_ACCESS_TOKEN as Bearer token and PARDOT_BUSINESS_UNIT_ID in the Pardot-Business-Unit-Id header. Map form fields to Pardot properties. After creating the prospect, add them to list PARDOT_NURTURE_LIST_ID by POSTing to /api/v5/objects/list-memberships. Show a success state. Use TypeScript and Tailwind CSS.

Copy this prompt to try it in Bolt.new

Prospect Lead Score Dashboard

An internal sales dashboard showing Pardot prospects with their lead scores, campaign engagement history, and page visit data. Sales development representatives can prioritize outreach based on lead scores without needing individual Pardot licenses, viewing all the relevant prospect activity data in a custom interface alongside their Salesforce pipeline.

Bolt.new Prompt

Build a Pardot prospect scoring dashboard. Create /api/pardot/prospects that GETs https://pi.pardot.com/api/v5/objects/prospects?fields=id,firstName,lastName,email,company,score,grade,lastActivityAt,campaignId with PARDOT_ACCESS_TOKEN (Bearer) and PARDOT_BUSINESS_UNIT_ID header. Sort by score descending. In React, display a table with columns: Name, Company, Email, Lead Score (color-coded: green 75+, yellow 50-74, red under 50), Grade, and Last Activity date. Add a search filter by company or email. Show an 'Export CSV' button. Fetch fresh data every 5 minutes.

Copy this prompt to try it in Bolt.new

Marketing Campaign Performance Report

A campaign analytics report showing Pardot email campaign metrics — sent counts, open rates, click rates, and conversions — for a specified date range. The report combines Pardot campaign data with Salesforce opportunity data to show marketing's contribution to revenue, displayed in a React dashboard without requiring Pardot or Salesforce licenses for every viewer.

Bolt.new Prompt

Create a Pardot campaign analytics report. (1) /api/pardot/campaigns GETs https://pi.pardot.com/api/v5/objects/campaigns with Bearer PARDOT_ACCESS_TOKEN and Pardot-Business-Unit-Id header. Return id, name, cost, and createdAt for each campaign. (2) /api/pardot/campaigns/[id]/emails GETs email send stats for that campaign including sentAt, opens, clicks, and bounces. (3) Build a React page showing a sortable campaign table with name, cost, emails sent, and open rate. Add a Recharts LineChart showing open rate trends over time. Calculate cost-per-lead for each campaign.

Copy this prompt to try it in Bolt.new

Troubleshooting

Pardot API returns 401 Unauthorized despite having a Salesforce access token

Cause: Either the PARDOT_BUSINESS_UNIT_ID header is missing from the request, or the Salesforce Connected App does not have the pardot_api OAuth scope. Both issues cause 401 errors on Pardot endpoints even when the token is valid for general Salesforce API calls.

Solution: Verify that every request to pi.pardot.com includes both Authorization: Bearer {token} AND Pardot-Business-Unit-Id: {business_unit_id}. Check the Connected App's OAuth scopes in Salesforce Setup → App Manager — 'Access Pardot services (pardot_api)' must be selected. If the scope was added after initial setup, the existing tokens may not include it — re-authorize to get a new token with the correct scopes.

typescript
1// Both headers are required for every Pardot API request
2const res = await fetch(`https://pi.pardot.com/api/v5${endpoint}`, {
3 headers: {
4 Authorization: `Bearer ${process.env.PARDOT_ACCESS_TOKEN}`,
5 'Pardot-Business-Unit-Id': process.env.PARDOT_BUSINESS_UNIT_ID!,
6 'Content-Type': 'application/json',
7 },
8});

Salesforce OAuth returns 'redirect_uri_mismatch' error during authorization

Cause: The redirect URI in your authorization request does not exactly match the Callback URL registered in the Salesforce Connected App. This includes differences in protocol (http vs https), domain, path, and trailing slashes.

Solution: Open Salesforce Setup → App Manager → your Connected App → Manage → edit the OAuth settings. Verify the Callback URL exactly matches the URL your code sends in the redirect_uri parameter. Remember that the OAuth callback must be your deployed site URL (Netlify or Bolt Cloud), not the Bolt WebContainer preview URL.

Pardot API calls work in development but stop working after a few hours in production

Cause: Salesforce access tokens expire. By default, Connected App tokens expire after 2 hours. Without a token refresh mechanism, the API calls will return 401 after expiration.

Solution: Implement token refresh using the refresh_token. When a Pardot API call returns 401, make a POST to https://login.salesforce.com/services/oauth2/token with grant_type=refresh_token and your refresh_token. Update the stored access token with the new value. For serverless deployments, store the refresh token in an environment variable and update it programmatically through your hosting platform's API.

typescript
1// Auto-refresh token on 401 response
2async function pardotFetchWithRefresh<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
3 try {
4 return await pardotFetch<T>(endpoint, options);
5 } catch (error: unknown) {
6 if ((error as { status?: number }).status === 401) {
7 // Refresh the token
8 const refreshRes = await fetch('https://login.salesforce.com/services/oauth2/token', {
9 method: 'POST',
10 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
11 body: new URLSearchParams({
12 grant_type: 'refresh_token',
13 refresh_token: process.env.SALESFORCE_REFRESH_TOKEN!,
14 client_id: process.env.SALESFORCE_CLIENT_ID!,
15 client_secret: process.env.SALESFORCE_CLIENT_SECRET!,
16 }),
17 });
18 const { access_token } = await refreshRes.json();
19 // Update token in environment (store in DB for serverless)
20 process.env.PARDOT_ACCESS_TOKEN = access_token;
21 // Retry the original request
22 return pardotFetch<T>(endpoint, options);
23 }
24 throw error;
25 }
26}

Best practices

  • Store all Salesforce and Pardot credentials (client ID, client secret, access token, refresh token, Business Unit ID) in server-side .env with no NEXT_PUBLIC_ prefix — they give access to your entire Pardot database and Salesforce org
  • Complete the Salesforce OAuth flow on your deployed site (Netlify or Bolt Cloud), not in Bolt's WebContainer preview — the preview uses dynamic StackBlitz URLs that cannot be registered as Connected App redirect URIs
  • Use a dedicated Salesforce integration user (not an admin account) for API calls to limit blast radius and improve audit trail clarity when reviewing API activity in Salesforce Setup
  • Include both required headers on every Pardot API request: Authorization: Bearer {token} and Pardot-Business-Unit-Id: {id} — missing either header causes 401 errors that look identical to authentication failures
  • Implement Salesforce access token refresh logic for production — tokens expire after hours, and refresh logic prevents your integration from silently breaking overnight
  • Cache Pardot prospect and campaign data responses for 2-5 minutes in API routes — lead scores update asynchronously and do not change in real time, so caching reduces API load without affecting data freshness
  • Use Pardot's list membership feature (rather than custom fields) to trigger automation sequences — adding a prospect to a specific list is the standard way to enroll them in a Pardot Engagement Studio program

Alternatives

Frequently asked questions

Does Pardot work with Bolt.new?

Yes. Pardot's v5 REST API uses HTTPS requests that work in Bolt's WebContainer via Next.js API routes. The key constraint is that Salesforce OAuth 2.0 requires a stable redirect URI for the authorization callback, which means the initial OAuth setup must be completed on your deployed site (Netlify or Bolt Cloud). Once you have an access token stored in .env, all Pardot API operations work in both development and production.

How do I connect Bolt.new to Pardot?

Create a Salesforce Connected App with the pardot_api OAuth scope. Deploy your app to Netlify or Bolt Cloud and register the deployed URL as the redirect URI. Complete the Salesforce OAuth flow to get an access token. Store the token as PARDOT_ACCESS_TOKEN in .env alongside your PARDOT_BUSINESS_UNIT_ID. Create Next.js API routes that include both Authorization: Bearer {token} and Pardot-Business-Unit-Id headers on every request to https://pi.pardot.com/api/v5.

Is there a free tier for Pardot?

No. Pardot requires both an active Salesforce license and a Pardot (Marketing Cloud Account Engagement) add-on subscription — there is no free tier or trial that includes API access. Pricing is enterprise-level. If you need B2B marketing automation with API access at a lower cost, consider HubSpot Marketing Hub (free tier available) or Mailchimp (free up to 500 contacts).

Why does the Salesforce OAuth callback fail in Bolt's WebContainer preview?

Bolt's WebContainer runs in a browser sandbox with dynamic session-specific URLs generated by StackBlitz (e.g., https://[hash].local.webcontainer-api.io/). These URLs change between sessions and cannot be registered as stable redirect URIs in the Salesforce Connected App settings. Deploy to Netlify or Bolt Cloud, register that stable URL, and complete OAuth there. After getting the access token, you can continue developing in the preview.

What is the Pardot Business Unit ID and where do I find it?

The Pardot Business Unit ID is a unique identifier for your Pardot instance within Salesforce. It starts with '0Uv' and follows the Salesforce ID format. Find it in your Pardot account under Settings → Account Settings → Business Unit ID. It is required in the Pardot-Business-Unit-Id HTTP header on every API request — without it, Pardot returns 401 even with a valid Salesforce access token.

How do I handle Salesforce access token expiration in a Bolt.new app?

Salesforce access tokens expire after a configurable period (typically 2 hours). Implement token refresh by catching 401 errors in your API route and making a POST to https://login.salesforce.com/services/oauth2/token with grant_type=refresh_token and your stored refresh_token. Update the token in your environment configuration and retry the original request. For serverless deployments on Netlify, store tokens in a database (Supabase) rather than in process.env, which does not persist between function invocations.

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.