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

How to Integrate Bolt.new with SharpSpring

Integrate SharpSpring (now part of Constant Contact) with Bolt.new using their JSON-RPC style REST API. Get API credentials from SharpSpring Settings → API Settings, then use a Next.js API route to capture leads from Bolt forms, manage pipeline stages, and sync contact data. The SharpSpring API uses a unique account ID plus secret key authentication pattern — store both credentials server-side and never in client code.

What you'll learn

  • How to authenticate with SharpSpring's unique JSON-RPC style API using accountID and secretKey
  • How to capture leads from Bolt.new forms into SharpSpring using the createLeads API method
  • How to read and update lead pipeline stages in SharpSpring from a Next.js API route
  • How to build a lead management dashboard that syncs SharpSpring pipeline data
  • How to configure SharpSpring webhook notifications for lead activity on your deployed Netlify app
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate18 min read25 minutesMarketingApril 2026RapidDev Engineering Team
TL;DR

Integrate SharpSpring (now part of Constant Contact) with Bolt.new using their JSON-RPC style REST API. Get API credentials from SharpSpring Settings → API Settings, then use a Next.js API route to capture leads from Bolt forms, manage pipeline stages, and sync contact data. The SharpSpring API uses a unique account ID plus secret key authentication pattern — store both credentials server-side and never in client code.

Connect Bolt.new Forms and Dashboards to SharpSpring CRM

SharpSpring is a marketing automation platform that has found its strongest foothold among SMBs and marketing agencies — it offers the depth of enterprise platforms like Marketo and HubSpot at a significantly lower price point. In 2022, Constant Contact acquired SharpSpring and merged their platforms, giving SharpSpring users access to a broader email and digital marketing toolkit. If your clients or team is already using SharpSpring, building Bolt.new integrations that push leads and contact data directly into SharpSpring can significantly reduce manual data entry and improve lead response times.

SharpSpring's API is notable for its JSON-RPC style — unlike typical REST APIs where different endpoints represent different resources, SharpSpring uses a single endpoint and identifies the operation via a method field in the request body. This unusual pattern requires slightly different code structure than standard REST integrations, but it is well-documented and fully functional when called from a Next.js API route.

The integration covers two main flows. Capture flow: a Bolt.new form collects lead information from your landing page or app, and an API route immediately creates or updates a corresponding lead record in SharpSpring — ensuring no leads are lost and response times are minimized. Reporting flow: your Bolt app reads pipeline data from SharpSpring to display deal stages, lead counts, and conversion metrics in a custom dashboard that supplements or replaces SharpSpring's own reporting UI.

Integration method

Bolt Chat + API Route

SharpSpring's REST API uses a JSON-RPC style request format where all operations go to a single endpoint with method and params in the body. All API calls from Bolt must go through a Next.js API route using your accountID and secretKey stored in server-side environment variables. The API supports creating and updating leads, reading pipeline data, and managing contact fields. Webhook-based triggers for SharpSpring automation events require a deployed Netlify URL.

Prerequisites

  • A SharpSpring account at sharpspring.com — API access requires a SharpSpring subscription
  • SharpSpring API credentials: go to Settings → API Settings to find your Account ID and Secret Key
  • A Bolt.new account with a new Next.js project open
  • Familiarity with SharpSpring's lead fields — download the field list from SharpSpring's API documentation for your account's custom fields
  • A Netlify account for deployment if you plan to use SharpSpring webhook notifications

Step-by-step guide

1

Get SharpSpring API credentials and understand the JSON-RPC request format

SharpSpring's API has an unusual design that can be confusing if you approach it expecting a standard REST API. All requests — regardless of the operation — go to the same URL, and the operation is specified by a `method` field in the request body. This is called a JSON-RPC style API, and SharpSpring uses it consistently across all their endpoints. The API endpoint URL includes your Account ID and Secret Key as query parameters. This means the credentials are in the URL itself, not in headers: `https://api.sharpspring.com/pubapi/v1/?accountID=YOUR_ID&secretKey=YOUR_KEY`. Because the entire URL is a secret, you absolutely must call this from a server-side API route and never from client-side React code. If a user inspects your network requests, they would see the secret key in the URL. To get your credentials: in SharpSpring, go to Settings (gear icon) → API Settings. You will find your Account ID (a UUID-style string) and Secret Key. If you do not see API Settings, check with your SharpSpring administrator — some accounts require admin access to view API credentials. The request body format is always: `{ method: 'methodName', params: { objects: [...], limit: 500 }, id: 'request-id' }`. The response format is: `{ result: { data: [...], total: 100 }, error: null, id: 'request-id' }`. When there is an error, `result` is null and `error` has code and message fields. Always check for both `result === null` and `error !== null` in your response handling.

Bolt.new Prompt

Set up SharpSpring API integration. Create .env.local with SHARPSPRING_ACCOUNT_ID=your-account-id and SHARPSPRING_SECRET_KEY=your-secret-key. Create lib/sharpspring.ts with: a base apiRequest function that accepts method (string) and params (object), constructs the SharpSpring JSON-RPC URL from env vars, sends a POST with the method/params/id body, and returns the result.data array. Add TypeScript interfaces for SharpSpringLead (with all standard fields: leadID, firstName, lastName, emailAddress, companyName, phoneNumber, leadStatus, ownerID). Export helper functions createLeads(leads[]), getLeads(where?, limit?), updateLeads(leads[]), and getLeadByEmail(email) that all use apiRequest.

Paste this in Bolt.new chat

lib/sharpspring.ts
1// lib/sharpspring.ts
2export interface SharpSpringLead {
3 leadID?: string;
4 firstName: string;
5 lastName: string;
6 emailAddress: string;
7 companyName?: string;
8 phoneNumber?: string;
9 leadStatus?: string;
10 ownerID?: string;
11 description?: string;
12 [key: string]: string | undefined;
13}
14
15interface ApiResponse<T> {
16 result: { [key: string]: T[] } | null;
17 error: { code: number; message: string } | null;
18 id: string;
19}
20
21async function apiRequest<T>(method: string, params: Record<string, unknown>): Promise<T[]> {
22 const accountID = process.env.SHARPSPRING_ACCOUNT_ID;
23 const secretKey = process.env.SHARPSPRING_SECRET_KEY;
24
25 if (!accountID || !secretKey) {
26 throw new Error('SharpSpring credentials not configured');
27 }
28
29 const url = `https://api.sharpspring.com/pubapi/v1/?accountID=${accountID}&secretKey=${secretKey}`;
30 const requestId = `bolt-${Date.now()}`;
31
32 const response = await fetch(url, {
33 method: 'POST',
34 headers: { 'Content-Type': 'application/json' },
35 body: JSON.stringify({ method, params, id: requestId }),
36 });
37
38 if (!response.ok) {
39 throw new Error(`SharpSpring HTTP error: ${response.status}`);
40 }
41
42 const data: ApiResponse<T> = await response.json();
43
44 if (data.error) {
45 throw new Error(`SharpSpring API error ${data.error.code}: ${data.error.message}`);
46 }
47
48 if (!data.result) {
49 return [];
50 }
51
52 // SharpSpring wraps results in a key matching the data type (e.g., result.leads)
53 const resultKey = Object.keys(data.result)[0];
54 return (data.result[resultKey] as T[]) || [];
55}
56
57export async function createLeads(leads: Partial<SharpSpringLead>[]): Promise<SharpSpringLead[]> {
58 return apiRequest<SharpSpringLead>('createLeads', { objects: leads });
59}
60
61export async function getLeads(
62 where?: Record<string, string>,
63 limit = 100
64): Promise<SharpSpringLead[]> {
65 const params: Record<string, unknown> = { limit, offset: 0 };
66 if (where) params.where = where;
67 return apiRequest<SharpSpringLead>('getLeads', params);
68}
69
70export async function updateLeads(leads: Array<{ leadID: string } & Partial<SharpSpringLead>>): Promise<SharpSpringLead[]> {
71 return apiRequest<SharpSpringLead>('updateLeads', { objects: leads });
72}
73
74export async function getLeadByEmail(email: string): Promise<SharpSpringLead | null> {
75 const leads = await getLeads({ emailAddress: email }, 1);
76 return leads[0] || null;
77}

Pro tip: SharpSpring's API request ID field (the id property in the JSON-RPC body) is used to correlate requests and responses. Use a unique ID per request — a timestamp-based ID like bolt-${Date.now()} works well. The response will echo back the same ID, which is useful for debugging when multiple requests are in flight.

Expected result: A lib/sharpspring.ts helper with typed functions for creating, reading, and updating leads using SharpSpring's JSON-RPC API, with credentials in .env.local.

2

Build the lead capture API route

The lead capture route is the most common and immediately valuable SharpSpring integration. When a user submits a contact form, request a demo, or sign up for a trial in your Bolt app, this route captures their information and creates a corresponding lead record in SharpSpring. This eliminates manual data entry and ensures your sales team sees new leads instantly without checking multiple systems. SharpSpring field names follow camelCase conventions. The standard fields are: `firstName`, `lastName`, `emailAddress`, `phoneNumber`, `companyName`, `leadStatus`, `description`, and `ownerID`. Custom fields created in your SharpSpring account have their own field names visible in SharpSpring's field manager. Look up your account's custom field names in SharpSpring under Settings → Lead Fields. For the `leadStatus` field, use SharpSpring's predefined stage names exactly as configured in your account — these vary by SharpSpring configuration. Common defaults include 'Open - Not Contacted', 'Working - Contacted', and 'Closed - Converted'. Mismatching the status name means the lead may not appear in expected pipeline views. The create-or-update pattern (check if the email already exists, then create if new or update if existing) prevents duplicate lead records when the same person submits a form multiple times. Use `getLeadByEmail` to check for an existing record before calling `createLeads`.

Bolt.new Prompt

Create a SharpSpring lead capture API route at app/api/sharpspring/leads/route.ts using functions from lib/sharpspring.ts. Accept POST with: firstName (required), lastName (required), email (required, validate format), company (optional), phone (optional), message (optional), source (optional, for tracking which page). Check if a lead with that email already exists using getLeadByEmail. If yes, update the existing lead. If no, create a new lead. Map message to the description field and source to a custom field if SHARPSPRING_SOURCE_FIELD env var is set. Return 201 for new leads, 200 for updates. Also create a ContactForm React component with fields for first name, last name, email, company, phone, and message, with Zod validation and loading/success/error states.

Paste this in Bolt.new chat

app/api/sharpspring/leads/route.ts
1// app/api/sharpspring/leads/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { createLeads, updateLeads, getLeadByEmail } from '@/lib/sharpspring';
4
5const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
7export async function POST(request: NextRequest) {
8 const body = await request.json();
9 const { firstName, lastName, email, company, phone, message, source } = body;
10
11 // Validation
12 if (!firstName?.trim()) {
13 return NextResponse.json({ error: 'First name is required' }, { status: 400 });
14 }
15 if (!lastName?.trim()) {
16 return NextResponse.json({ error: 'Last name is required' }, { status: 400 });
17 }
18 if (!email || !emailRegex.test(email)) {
19 return NextResponse.json({ error: 'Valid email is required' }, { status: 400 });
20 }
21
22 try {
23 const existingLead = await getLeadByEmail(email);
24
25 const leadData: Record<string, string> = {
26 firstName: firstName.trim(),
27 lastName: lastName.trim(),
28 emailAddress: email.toLowerCase().trim(),
29 };
30
31 if (company) leadData.companyName = company.trim();
32 if (phone) leadData.phoneNumber = phone.trim();
33 if (message) leadData.description = message.trim();
34
35 // Add source tracking if custom field is configured
36 const sourceField = process.env.SHARPSPRING_SOURCE_FIELD;
37 if (source && sourceField) {
38 leadData[sourceField] = source;
39 }
40
41 if (existingLead?.leadID) {
42 await updateLeads([{ leadID: existingLead.leadID, ...leadData }]);
43 return NextResponse.json({ success: true, action: 'updated', leadID: existingLead.leadID });
44 } else {
45 leadData.leadStatus = 'Open - Not Contacted';
46 const created = await createLeads([leadData]);
47 const newLeadId = (created[0] as Record<string, string>)?.id;
48 return NextResponse.json({ success: true, action: 'created', leadID: newLeadId }, { status: 201 });
49 }
50 } catch (error) {
51 console.error('SharpSpring lead capture error:', error);
52 return NextResponse.json(
53 { error: 'Failed to save lead information' },
54 { status: 500 }
55 );
56 }
57}

Pro tip: The leadStatus value must exactly match the stage names configured in your SharpSpring account. If 'Open - Not Contacted' is not your account's default first stage name, new leads may not appear in your pipeline view. Check your SharpSpring admin under Settings → Stages to find the exact stage names for your account.

Expected result: A lead capture API route that creates or updates SharpSpring lead records from Bolt.new form submissions, with duplicate prevention via email lookup.

3

Fetch pipeline data for a sales dashboard

A custom pipeline dashboard in Bolt.new can combine SharpSpring lead data with other data sources — Supabase user data, Stripe subscription metrics, or custom application events — in a single view that SharpSpring's own reporting does not provide. Even without combining data sources, a Bolt-built dashboard can present SharpSpring pipeline data in a layout tailored to your team's daily review process. SharpSpring's `getLeads` API returns leads with their current pipeline status and associated fields. For a pipeline funnel, group leads by `leadStatus` and count them at each stage. For opportunity-based pipelines (where deals have monetary values), SharpSpring's `getOpportunities` API provides deal stage and value data. Pagination is important for accounts with large numbers of leads. SharpSpring's API supports `limit` and `offset` parameters in the params object. For a summary dashboard showing counts per stage, you can use a single API call with a high limit. For detailed lead lists, implement pagination with a next/previous interface. Caching the SharpSpring pipeline data in Supabase is strongly recommended for team-facing dashboards that multiple people access throughout the day. A sync job that pulls fresh data from SharpSpring every 15-30 minutes is better than hitting the SharpSpring API on every page load from every user.

Bolt.new Prompt

Create a SharpSpring pipeline summary API route at app/api/sharpspring/pipeline/route.ts. Fetch all leads using getLeads from lib/sharpspring.ts with limit 500. Group leads by leadStatus field and count them. Also group by ownerID. Return: stages (array of {name, count}), byOwner (array of {ownerID, count}), totalLeads. Cache the result in Supabase table sharpspring_pipeline_cache (id, data jsonb, synced_at timestamp) and use cached data if it is less than 30 minutes old. Create a PipelineSummary React component showing a horizontal bar chart (Recharts) of leads by stage and a table of leads by owner. Add a Refresh button that forces a fresh SharpSpring API call.

Paste this in Bolt.new chat

app/api/sharpspring/pipeline/route.ts
1// app/api/sharpspring/pipeline/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { getLeads } from '@/lib/sharpspring';
4import { createClient } from '@supabase/supabase-js';
5
6const CACHE_TTL_MINUTES = 30;
7
8interface PipelineData {
9 stages: Array<{ name: string; count: number }>;
10 byOwner: Array<{ ownerID: string; count: number }>;
11 totalLeads: number;
12 syncedAt: string;
13}
14
15async function fetchFromSharpSpring(): Promise<PipelineData> {
16 const leads = await getLeads(undefined, 500);
17
18 const stageCounts = new Map<string, number>();
19 const ownerCounts = new Map<string, number>();
20
21 for (const lead of leads) {
22 const stage = lead.leadStatus || 'Unknown';
23 stageCounts.set(stage, (stageCounts.get(stage) || 0) + 1);
24
25 if (lead.ownerID) {
26 ownerCounts.set(lead.ownerID, (ownerCounts.get(lead.ownerID) || 0) + 1);
27 }
28 }
29
30 return {
31 stages: Array.from(stageCounts.entries()).map(([name, count]) => ({ name, count })),
32 byOwner: Array.from(ownerCounts.entries()).map(([ownerID, count]) => ({ ownerID, count })),
33 totalLeads: leads.length,
34 syncedAt: new Date().toISOString(),
35 };
36}
37
38export async function GET(request: NextRequest) {
39 const { searchParams } = new URL(request.url);
40 const forceRefresh = searchParams.get('refresh') === 'true';
41
42 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
43 const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
44 const supabase = supabaseUrl && serviceKey ? createClient(supabaseUrl, serviceKey) : null;
45
46 // Try cache first
47 if (supabase && !forceRefresh) {
48 const { data: cached } = await supabase
49 .from('sharpspring_pipeline_cache')
50 .select('data, synced_at')
51 .order('synced_at', { ascending: false })
52 .limit(1)
53 .single();
54
55 if (cached) {
56 const ageMinutes = (Date.now() - new Date(cached.synced_at).getTime()) / 60000;
57 if (ageMinutes < CACHE_TTL_MINUTES) {
58 return NextResponse.json({ ...cached.data, fromCache: true });
59 }
60 }
61 }
62
63 try {
64 const data = await fetchFromSharpSpring();
65
66 if (supabase) {
67 await supabase.from('sharpspring_pipeline_cache').insert({ data, synced_at: data.syncedAt });
68 }
69
70 return NextResponse.json(data);
71 } catch (error) {
72 return NextResponse.json(
73 { error: (error as Error).message },
74 { status: 500 }
75 );
76 }
77}

Pro tip: SharpSpring's getLeads API returns up to 500 records per call. For accounts with more than 500 leads, implement pagination by incrementing the offset parameter in additional calls until you have fetched all records. Use the total count returned by the API to know when you have retrieved everything.

Expected result: A pipeline summary API route with Supabase caching that returns lead counts by stage and owner, ready for dashboard visualizations.

4

Deploy to Netlify and configure SharpSpring webhook notifications

SharpSpring API calls work in Bolt.new's WebContainer during development since they are standard outbound HTTP requests. You can build and test all lead capture and pipeline reading features in the Bolt preview before deploying. Deployment to Netlify is required for two reasons: your .env.local credentials are not included in the deployed build, and any SharpSpring webhook features (receiving notifications when lead status changes) require a publicly accessible URL that Bolt's WebContainer cannot provide. After deploying to Netlify via Bolt.new's deploy button, go to the Netlify dashboard → Site Configuration → Environment Variables and add: SHARPSPRING_ACCOUNT_ID, SHARPSPRING_SECRET_KEY, and if you use Supabase caching: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY. Trigger a redeploy. For SharpSpring webhook notifications: in SharpSpring, go to Settings → API Settings → Webhooks. Enter your Netlify URL (https://your-app.netlify.app/api/sharpspring/webhook) and select the events to subscribe to — lead status changes, new form submissions, or lead score changes. SharpSpring sends a POST request to your webhook URL when these events occur. Always return a 200 response from your webhook handler quickly to acknowledge receipt. SharpSpring retries webhooks that receive error responses, which can cause duplicate processing. Log errors internally but return 200 to prevent retries.

Bolt.new Prompt

Create a SharpSpring webhook handler at app/api/sharpspring/webhook/route.ts. Accept POST from SharpSpring. Parse the payload and extract event type and lead data. Handle lead status change events by updating the corresponding record in a Supabase leads_sync table if present. Log the event type and affected lead email for debugging. Return 200 immediately for all events. Also add netlify.toml with Next.js build config. Add a comment noting that webhooks require a deployed public URL and cannot be received in Bolt's WebContainer preview.

Paste this in Bolt.new chat

app/api/sharpspring/webhook/route.ts
1// app/api/sharpspring/webhook/route.ts
2// NOTE: SharpSpring webhooks require a public URL.
3// This endpoint cannot receive events in Bolt's WebContainer preview.
4// Deploy to Netlify first, then register your URL in SharpSpring Settings → API Settings → Webhooks.
5import { NextRequest, NextResponse } from 'next/server';
6
7interface SharpSpringWebhookPayload {
8 event: string;
9 lead?: {
10 leadID: string;
11 emailAddress: string;
12 leadStatus: string;
13 firstName: string;
14 lastName: string;
15 };
16}
17
18export async function POST(request: NextRequest) {
19 let payload: SharpSpringWebhookPayload;
20
21 try {
22 payload = await request.json();
23 } catch {
24 return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
25 }
26
27 const { event, lead } = payload;
28
29 console.log(`SharpSpring webhook: ${event}`, {
30 leadID: lead?.leadID,
31 email: lead?.emailAddress,
32 status: lead?.leadStatus,
33 });
34
35 switch (event) {
36 case 'lead.status_changed':
37 // TODO: Sync updated status to your Supabase database
38 console.log(`Lead ${lead?.emailAddress} status changed to: ${lead?.leadStatus}`);
39 break;
40
41 case 'lead.created':
42 console.log(`New SharpSpring lead: ${lead?.firstName} ${lead?.lastName} <${lead?.emailAddress}>`);
43 break;
44
45 case 'lead.score_changed':
46 console.log(`Lead score changed for: ${lead?.emailAddress}`);
47 break;
48
49 default:
50 console.log(`Unhandled SharpSpring event: ${event}`);
51 }
52
53 // Always return 200 — SharpSpring retries on non-200
54 return NextResponse.json({ received: true });
55}

Pro tip: SharpSpring's webhook payload format should be verified against their current API documentation — the exact structure and event type names may vary based on your SharpSpring version and configuration. Test the webhook by using SharpSpring's webhook test tool or by manually changing a lead's status after setting up the webhook endpoint.

Expected result: A deployed Netlify app with SharpSpring lead capture and pipeline reading working in production, with a webhook handler for lead event notifications.

Common use cases

Lead Capture Form to SharpSpring CRM

When a visitor fills out a contact or demo request form on your Bolt.new landing page or marketing site, automatically create a lead record in SharpSpring with their contact information and assign them to the appropriate lead owner and pipeline stage. Eliminates manual lead entry and ensures instant CRM visibility.

Bolt.new Prompt

Create a demo request form that sends leads to SharpSpring. Build an API route at app/api/sharpspring/leads/route.ts that accepts POST with firstName, lastName, email, company, phone, and message fields. Use SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY env vars to call SharpSpring's JSON-RPC API at https://api.sharpspring.com/pubapi/v1/?accountID={id}&secretKey={key} with method createLeads. Pass the lead data in the objects array with appropriate SharpSpring field names. Create a DemoRequestForm React component with a clean multi-field form, validation, and success message showing 'We will be in touch within 1 business day'. Return 400 for validation errors, 500 for API failures.

Copy this prompt to try it in Bolt.new

Pipeline Stage Dashboard

Build an internal sales dashboard that fetches current lead pipeline data from SharpSpring and displays deal counts and values by stage. Useful for sales managers who want a quick overview without navigating SharpSpring's interface, or for combining SharpSpring pipeline data with metrics from other sources.

Bolt.new Prompt

Create a SharpSpring pipeline dashboard. Build an API route at app/api/sharpspring/pipeline/route.ts that calls SharpSpring's getLeads and getOpportunities methods using SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY. Group leads by their lead status field and opportunities by stage. Return summary data with counts and deal values per stage. Create a PipelineDashboard React component displaying a horizontal funnel visualization using Recharts, showing the count and total value of leads at each stage. Add a refresh button and display last synced timestamp.

Copy this prompt to try it in Bolt.new

Contact Update from In-App Actions

When a user takes a key action in your app — completing onboarding, upgrading their subscription, or canceling — automatically update their SharpSpring contact record with the new status and trigger relevant SharpSpring automation rules. Keeps your CRM data in sync with actual user behavior.

Bolt.new Prompt

Create a SharpSpring contact sync utility. Build a lib/sharpspring.ts helper with functions: updateLead(email, fields) that calls SharpSpring's updateLeads method to change field values, and createOrUpdateLead(contactData) that first searches for an existing lead by email using getLeads, then creates if not found or updates if found. Call updateLead from my Supabase user lifecycle hooks: when a user upgrades their plan, update their SharpSpring leadStatus field to 'Customer'; when they cancel, update to 'Churned'. Store the SharpSpring lead ID in Supabase users table for efficient future updates.

Copy this prompt to try it in Bolt.new

Troubleshooting

SharpSpring API returns error code 301 — 'Method not found'

Cause: The method name in the JSON-RPC request body does not match an available API method. SharpSpring method names are case-sensitive and must exactly match the documented names (e.g., 'createLeads' not 'CreateLeads' or 'create_leads').

Solution: Verify the exact method name in SharpSpring's API documentation. Common methods: createLeads, getLeads, updateLeads, createOpportunities, getOpportunities, updateOpportunities, getLeadFields. Check that the method name is spelled correctly and matches the documented capitalization exactly.

typescript
1// Correct method names
2body: JSON.stringify({
3 method: 'createLeads', // correct
4 params: { objects: [leadData] },
5 id: requestId,
6})
7// Not: 'CreateLeads', 'create_leads', or 'CreateLead'

SharpSpring API returns error code 102 — 'Authentication failed'

Cause: The accountID or secretKey is incorrect, or the URL-encoded credentials are malformed. The credentials are query parameters in the URL, not headers, and must be exact matches to your SharpSpring account's values.

Solution: Verify both SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY match exactly what is shown in SharpSpring Settings → API Settings. Check there are no extra spaces or newlines in the environment variable values. Confirm the URL format is correct: https://api.sharpspring.com/pubapi/v1/?accountID={id}&secretKey={key}.

Lead status is not updating or new leads do not appear in the SharpSpring pipeline

Cause: The leadStatus value being sent does not match the exact stage names configured in your SharpSpring account. SharpSpring's stage names are configurable and may differ from the default examples in documentation.

Solution: Fetch your account's actual stage names from the SharpSpring API using the getLeadFields method or by checking Settings → Stages in the SharpSpring dashboard. Replace the hardcoded 'Open - Not Contacted' status with the exact stage name from your account. Stage names are case-sensitive.

typescript
1// Fetch valid lead status values from SharpSpring
2const fields = await apiRequest('getLeadFields', {});
3// Look for the 'leadStatus' field and its available options

SharpSpring API calls work in Bolt.new preview but the API key is undefined after deploying to Netlify

Cause: .env.local variables are never deployed — they only exist in the local Bolt development environment. Netlify has its own environment variable storage that must be configured separately.

Solution: In the Netlify dashboard → Site Configuration → Environment Variables, add SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY with their values. Click Trigger Deploy → Deploy site to redeploy with the new variables. These server-side variables are never exposed to the browser, so do not add NEXT_PUBLIC_ prefix.

Best practices

  • Never expose your SharpSpring API credentials in client-side code — the accountID and secretKey appear in the API URL itself, which must only be constructed server-side in Next.js API routes
  • Use the create-or-update pattern for lead capture to prevent duplicate records — always check for an existing lead by email before creating a new one
  • Map lead source information (which page, which campaign, which product) to SharpSpring custom fields at capture time — this data is far harder to reconstruct after the fact
  • Implement a Supabase cache layer for pipeline data reads to avoid hitting SharpSpring's API on every page load when multiple team members use the dashboard
  • Use SharpSpring's exact stage name strings from your account configuration — hardcoding default stage names that may not match your setup causes silent failures where leads are created but do not appear in the expected pipeline view
  • Always return HTTP 200 from SharpSpring webhook handlers regardless of processing outcome — SharpSpring retries on non-200 responses, causing duplicate event processing
  • Test API calls in Bolt.new's WebContainer preview before deploying — outbound SharpSpring API calls work in the preview, allowing you to validate the JSON-RPC request format before going to production
  • Add request ID logging to your API routes to correlate SharpSpring API calls with specific form submissions for debugging

Alternatives

Frequently asked questions

Can SharpSpring API calls work in Bolt.new's WebContainer during development?

Yes. SharpSpring API calls are standard outbound HTTP POST requests from your Next.js API routes. These work in Bolt's WebContainer preview without any issues. You can build and test all lead capture and pipeline read features in the Bolt preview. The only SharpSpring feature that requires deployment is incoming webhook delivery, which needs a publicly accessible URL.

Why does SharpSpring use a JSON-RPC style API instead of standard REST?

SharpSpring's API was designed before REST APIs became the universal standard for B2B SaaS platforms. The JSON-RPC pattern uses a single endpoint for all operations, with the operation name in the request body. This is functionally equivalent to REST — all operations are available, the format is consistent, and authentication works the same way. It just requires learning a slightly different request structure compared to typical REST APIs.

How do I find the correct field names for SharpSpring custom fields?

Call the SharpSpring getLeadFields API method with empty params to retrieve all available lead fields for your account, including custom fields created by your admin. The response includes each field's systemName (the API field name to use in create/update calls), label (the display name), and dataType. Custom fields typically have system names like customField_abc123 that you must use exactly in API calls.

What is the difference between SharpSpring leads and opportunities in the API?

SharpSpring uses a two-object model: Leads (contacts, either individuals or companies) and Opportunities (deals linked to leads, with stages and monetary values). The leads API manages contact information and lead status. The opportunities API manages the sales pipeline with stages and deal values. For lead capture forms, you typically only interact with the leads API. For a full sales pipeline dashboard, you use both.

Is SharpSpring still available as a standalone product after the Constant Contact acquisition?

Yes. SharpSpring continues to operate as a distinct product with its own branding, pricing, and feature set at sharpspring.com. The Constant Contact acquisition in 2022 added marketing email capabilities to SharpSpring's existing CRM and marketing automation platform. Existing SharpSpring customers can continue using it as before, and the API remains the same.

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.