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

How to Integrate Bolt.new with Freshsales

Integrate Bolt.new with Freshsales by grabbing your API key from Settings → API Settings, storing it in your .env file, and calling the Freshsales CRM REST API through a Next.js API route. Build contact management, deal pipelines, and AI-powered Freddy lead score dashboards. Outbound API calls work in Bolt's WebContainer preview; Freshsales webhooks for real-time events require a deployed public URL.

What you'll learn

  • How to get a Freshsales API key from Settings → API Settings and configure authentication
  • How to create, list, and update contacts and deals via server-side API routes
  • How to fetch Freddy AI lead scores and display them in a sales dashboard
  • How to sync Bolt app form submissions directly into Freshsales as new contacts
  • How to register Freshsales webhooks after deploying for real-time pipeline events
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate19 min read30 minutesMarketingApril 2026RapidDev Engineering Team
TL;DR

Integrate Bolt.new with Freshsales by grabbing your API key from Settings → API Settings, storing it in your .env file, and calling the Freshsales CRM REST API through a Next.js API route. Build contact management, deal pipelines, and AI-powered Freddy lead score dashboards. Outbound API calls work in Bolt's WebContainer preview; Freshsales webhooks for real-time events require a deployed public URL.

Building a Freshsales-Powered Sales Dashboard in Bolt.new

Freshsales is part of the Freshworks ecosystem — the same company behind Freshdesk (support), Freshservice (IT), and Freshchat (messaging). This means contacts, companies, and deals created in Freshsales can flow automatically into other Freshworks products, making it a strong choice if your team already uses Freshdesk for customer support or Freshchat for live chat. The REST API uses API key authentication with no OAuth dance required, and the free tier supports up to 3 users — making it an ideal CRM to prototype against in a Bolt.new project.

The standout feature differentiating Freshsales from competitors like Pipedrive is Freddy AI — Freshworks' AI engine that assigns a lead score (0–100) to each contact based on behavioral signals: email opens, website visits, deal history, and demographic fit. The Freddy score is exposed directly through the Contacts API as a field on each contact object, so you can build dashboards that surface your hottest leads without any extra ML work on your side. This is meaningfully different from Pipedrive's more manual pipeline approach.

All Freshsales API calls use the base URL pattern https://your-domain.freshsales.io/api/ with an Authorization header containing Token token={your_api_key}. The API follows RESTful conventions and returns JSON. From Bolt's WebContainer, every outbound HTTPS call to the Freshsales API works exactly as expected. The only operations that require a deployed public URL are incoming webhooks — Freshsales can push deal stage changes, contact updates, and custom event triggers to your app, but your server needs to be reachable from the internet for those events to land.

Integration method

Bolt Chat + API Route

Bolt generates the Freshsales integration through conversation — describe what CRM features you need and Bolt writes the API routes and React components. Freshsales uses simple API key authentication with no OAuth flow required, making it one of the easiest CRM integrations to set up. All CRM API calls run through server-side Next.js routes to keep your Freshsales API key out of the browser bundle. Webhooks for real-time pipeline updates require a deployed public URL since Bolt's WebContainer cannot receive incoming connections.

Prerequisites

  • A Freshsales account — the free tier (3 users) is sufficient for development and prototyping
  • Your Freshsales API key from Settings → API Settings → Your API Key (shown at the bottom of the page)
  • Your Freshsales subdomain (the part before .freshsales.io in your account URL)
  • A Next.js project in Bolt.new — prompt 'Create a Next.js app' if you don't have one yet
  • For webhook testing: a deployed app on Netlify or Vercel with a publicly accessible HTTPS URL

Step-by-step guide

1

Get your Freshsales API key and configure environment variables

Log in to your Freshsales account and navigate to your profile settings. Click the avatar or user icon in the top-right corner, then select Settings. In the left sidebar, scroll down to the API Settings section (sometimes listed under 'Integrations & API' depending on your plan level). Your API key is displayed at the bottom of this page — it's a long alphanumeric string. Copy it. You'll also need your Freshsales domain, which is the subdomain portion of your account URL. For example, if you access Freshsales at https://mycompany.freshsales.io, your domain is mycompany. In Bolt.new, open your project and look for the .env or .env.local file in the file tree. If it doesn't exist, create one in the project root. Add your API key and domain as environment variables. In a Next.js project inside Bolt, use the standard process.env pattern for server-side variables — never use the NEXT_PUBLIC_ prefix for API keys, as that would expose them in the client bundle. The Freshsales API uses simple token authentication: every request must include an Authorization header with the value Token token={your_api_key}. There is no OAuth token refresh to manage, no expiry time to track, and no separate client ID/client secret pair. This makes Freshsales one of the simplest CRM APIs to integrate with from a server-side proxy pattern. Test that your credentials are working by prompting Bolt to create a simple test route that fetches your Freshsales account details.

Bolt.new Prompt

Create a Next.js app with Freshsales CRM integration. Add .env.local with FRESHSALES_API_KEY and FRESHSALES_DOMAIN (the subdomain, e.g. 'mycompany' from mycompany.freshsales.io) as placeholder variables. Create a utility file at lib/freshsales.ts that exports a freshsalesFetch helper function that prepends the base URL and adds the Authorization header to all requests.

Paste this in Bolt.new chat

.env.local
1// .env.local
2FRESHSALES_API_KEY=your_api_key_here
3FRESHSALES_DOMAIN=your_subdomain_here

Pro tip: You can verify your API key is correct by calling GET https://{domain}.freshsales.io/api/contacts — it should return a JSON array of contacts. If it returns 401, double-check that the key was copied fully (they can be 20+ characters).

Expected result: Your .env.local file contains FRESHSALES_API_KEY and FRESHSALES_DOMAIN, and a freshsalesFetch utility is ready to use in API routes.

2

Build the Freshsales API utility and contacts endpoint

With your credentials in place, create the core API utility that all Freshsales routes will use. The helper function should accept an endpoint path (e.g., /contacts), an HTTP method, and an optional request body, and return the parsed JSON response. It constructs the full URL using your FRESHSALES_DOMAIN environment variable and adds the Authorization header automatically. This keeps your API routes clean and avoids repeating authentication logic across multiple files. The contacts API is the most commonly used endpoint. GET /api/contacts returns a paginated list of contacts with all CRM fields including name, email, phone, company, lifecycle stage, and — critically — the Freddy AI lead score under the field lead_score. You can filter contacts by various parameters and sort by fields like lead_score to surface your hottest leads first. The sort parameter uses the format sort=lead_score&sort_type=desc. For creating a new contact, POST to /api/contacts with a JSON body containing a contact object. Required fields are first_name and last_name; email and phone are strongly recommended. You can pass custom field values, tag names as an array, and the owner_id to assign the contact to a specific sales rep. The API returns the newly created contact object with its assigned id, which you can use to link subsequent deal creation to this contact. Because all these API calls are outbound HTTPS requests from your server-side Next.js route, they work perfectly in Bolt's WebContainer development preview. You can create, list, and update contacts without deploying your app — the WebContainer can make any outbound HTTP call over HTTPS.

Bolt.new Prompt

Build the Freshsales utility and contacts API routes. Create lib/freshsales.ts with a freshsalesFetch helper. Create app/api/freshsales/contacts/route.ts that handles GET (list contacts sorted by Freddy lead score, returns top 50) and POST (create a new contact). For GET, return each contact's id, first_name, last_name, email, company, lead_score, and last_contacted_time.

Paste this in Bolt.new chat

lib/freshsales.ts
1// lib/freshsales.ts
2export async function freshsalesFetch(
3 endpoint: string,
4 method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
5 body?: Record<string, unknown>
6): Promise<Response> {
7 const domain = process.env.FRESHSALES_DOMAIN!;
8 const apiKey = process.env.FRESHSALES_API_KEY!;
9 const url = `https://${domain}.freshsales.io/api${endpoint}`;
10
11 return fetch(url, {
12 method,
13 headers: {
14 Authorization: `Token token=${apiKey}`,
15 'Content-Type': 'application/json',
16 },
17 ...(body ? { body: JSON.stringify(body) } : {}),
18 });
19}
20
21// app/api/freshsales/contacts/route.ts
22import { NextRequest, NextResponse } from 'next/server';
23import { freshsalesFetch } from '@/lib/freshsales';
24
25export async function GET() {
26 const response = await freshsalesFetch(
27 '/contacts?sort=lead_score&sort_type=desc&per_page=50'
28 );
29 if (!response.ok) {
30 return NextResponse.json({ error: 'Freshsales API error' }, { status: response.status });
31 }
32 const data = await response.json();
33 const contacts = (data.contacts ?? []).map((c: Record<string, unknown>) => ({
34 id: c.id,
35 firstName: c.first_name,
36 lastName: c.last_name,
37 email: c.email,
38 company: (c.company as Record<string, unknown>)?.name ?? '',
39 leadScore: c.lead_score ?? 0,
40 lastContacted: c.last_contacted_time,
41 }));
42 return NextResponse.json({ contacts, total: data.meta?.total_pages });
43}
44
45export async function POST(request: NextRequest) {
46 const body = await request.json();
47 const response = await freshsalesFetch('/contacts', 'POST', { contact: body });
48 if (!response.ok) {
49 const err = await response.json();
50 return NextResponse.json({ error: err }, { status: response.status });
51 }
52 const data = await response.json();
53 return NextResponse.json({ contact: data.contact });
54}

Pro tip: Freddy AI lead scores update asynchronously — a newly created contact will have a score of 0 until Freddy processes their behavioral signals. Scores above 70 are typically considered hot leads worth immediate follow-up.

Expected result: GET /api/freshsales/contacts returns a JSON list of contacts sorted by Freddy AI lead score, and POST /api/freshsales/contacts creates a new lead in Freshsales.

3

Create the deals pipeline API and dashboard UI

The Deals API follows the same authentication pattern as Contacts. GET /api/deals returns open deals with fields including name, amount, expected_close, deal_stage (an object with id and name), owner, and associated_contacts. You can filter by pipeline with the filter_type parameter and sort by amount, expected_close, or updated_at. Deal stages in Freshsales are configured per pipeline under Settings → Deal Management → Pipelines. Each stage has a numeric id and a string name. When you fetch deals, the deal_stage object tells you where in the funnel each deal currently sits. To move a deal to a new stage, send a PUT request to /api/deals/{id} with a body containing deal: { deal_stage_id: newStageId }. For creating deals, POST to /api/deals with a deal object containing name, amount, expected_close (YYYY-MM-DD format), deal_stage_id, and optionally contacts: [{ id: contactId }] to associate the deal with an existing contact. You can also pass owner_id to assign it to a specific sales rep. When building the dashboard UI in Bolt, describe the kanban layout you want in a Bolt chat prompt. A typical sales dashboard groups deals in columns by stage name, shows total deal value per column, and color-codes cards by expected close date (red for overdue, yellow for this month, green for future). Bolt can generate this layout as a React component using the data returned from your /api/freshsales/deals route. All data fetching happens server-side, so there are no CORS issues — the WebContainer's outbound HTTP works without restriction for these server-to-server calls.

Bolt.new Prompt

Add Freshsales deal management to my app. Create app/api/freshsales/deals/route.ts with GET (list all open deals with stage, amount, expected close, and contact names) and POST (create a new deal with name, amount, expected_close, deal_stage_id, and contact_id). Then build a DealsPipeline React component that fetches from /api/freshsales/deals and displays deals in columns grouped by stage name.

Paste this in Bolt.new chat

app/api/freshsales/deals/route.ts
1// app/api/freshsales/deals/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { freshsalesFetch } from '@/lib/freshsales';
4
5export async function GET() {
6 const response = await freshsalesFetch(
7 '/deals?filter_type=open&sort=expected_close&sort_type=asc&per_page=100'
8 );
9 if (!response.ok) {
10 return NextResponse.json({ error: 'Freshsales API error' }, { status: response.status });
11 }
12 const data = await response.json();
13 const deals = (data.deals ?? []).map((d: Record<string, unknown>) => ({
14 id: d.id,
15 name: d.name,
16 amount: d.amount,
17 expectedClose: d.expected_close,
18 stage: (d.deal_stage as Record<string, unknown>)?.name ?? 'Unknown',
19 stageId: (d.deal_stage as Record<string, unknown>)?.id,
20 ownerName: (d.owner as Record<string, unknown>)?.name ?? '',
21 contacts: (d.contacts as Array<Record<string, unknown>>)?.map((c) => c.display_name) ?? [],
22 }));
23 return NextResponse.json({ deals });
24}
25
26export async function POST(request: NextRequest) {
27 const { name, amount, expectedClose, stageId, contactId } = await request.json();
28 const body = {
29 deal: {
30 name,
31 amount,
32 expected_close: expectedClose,
33 deal_stage_id: stageId,
34 ...(contactId ? { contacts: [{ id: contactId }] } : {}),
35 },
36 };
37 const response = await freshsalesFetch('/deals', 'POST', body);
38 if (!response.ok) {
39 const err = await response.json();
40 return NextResponse.json({ error: err }, { status: response.status });
41 }
42 const data = await response.json();
43 return NextResponse.json({ deal: data.deal });
44}

Pro tip: To fetch your pipeline's stage IDs for deal creation, call GET /api/deals/filters — it returns all configured deal stages with their IDs and names. Hard-code the stage IDs you use most often in a constants file to avoid extra API calls.

Expected result: GET /api/freshsales/deals returns open deals with stage information, and the DealsPipeline component renders a kanban view grouped by deal stage.

4

Sync Bolt form submissions as Freshsales contacts

One of the most practical Freshsales integrations for Bolt apps is automatic lead capture: when a user fills out a contact form, demo request form, or waitlist signup in your Bolt app, the submission is immediately forwarded to Freshsales as a new contact and lead. This keeps the sales team working with up-to-date data without any manual CSV imports. The implementation uses a server-side POST route that receives form data from the React component, validates the required fields, and then calls the Freshsales Contacts API to create or upsert the contact. Freshsales supports upsert via a POST to /api/contacts/upsert — if a contact with the same email already exists, it updates them instead of creating a duplicate. This is important for form-heavy use cases where the same person might submit multiple times. You can enrich the contact record at creation time by passing additional fields: lifecycle_stage (one of Lead, Marketing Qualified Lead, Sales Qualified Lead, etc.), tags as an array of strings (e.g., ['bolt-landing-page', 'demo-request']), and any custom fields your sales team has configured. If you know the source (UTM parameters, referrer page), store it in a custom field so reps know where each lead came from. Test the full form-to-CRM flow in Bolt's development preview — since the form submission fires an outbound POST to Freshsales' API, it works without deployment. Check your Freshsales contacts list after submitting the form to confirm the lead was created with all the right fields populated.

Bolt.new Prompt

Create a lead capture form component and the Freshsales upsert API route. The form should have fields for first name, last name, email, phone, and company. On submit, POST to /api/freshsales/contacts/upsert which calls Freshsales' upsert endpoint to create or update the contact. Tag the contact with 'bolt-app-lead'. Show a success toast after creation and clear the form.

Paste this in Bolt.new chat

app/api/freshsales/contacts/upsert/route.ts
1// app/api/freshsales/contacts/upsert/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { freshsalesFetch } from '@/lib/freshsales';
4
5export async function POST(request: NextRequest) {
6 const { firstName, lastName, email, phone, company } = await request.json();
7
8 if (!email || !firstName) {
9 return NextResponse.json(
10 { error: 'firstName and email are required' },
11 { status: 400 }
12 );
13 }
14
15 const body = {
16 contact: {
17 first_name: firstName,
18 last_name: lastName ?? '',
19 email,
20 mobile_number: phone ?? '',
21 company: { name: company ?? '' },
22 lifecycle_stage: 1, // 1 = Lead
23 tags: ['bolt-app-lead'],
24 },
25 unique_identifier: { email },
26 };
27
28 const response = await freshsalesFetch('/contacts/upsert', 'POST', body);
29 if (!response.ok) {
30 const err = await response.json();
31 return NextResponse.json({ error: err }, { status: response.status });
32 }
33 const data = await response.json();
34 return NextResponse.json({
35 contact: data.contact,
36 wasCreated: response.status === 201,
37 });
38}

Pro tip: The upsert endpoint returns HTTP 200 if the contact was updated and 201 if it was newly created. Use this to show different success messages: 'Welcome back, we updated your info!' vs 'Thanks for reaching out!'.

Expected result: Submitting the lead capture form creates or updates a contact in Freshsales with the correct tags and lifecycle stage. You can verify by checking the Contacts list in your Freshsales dashboard.

5

Set up Freshsales webhooks after deployment

Freshsales can send real-time notifications to your app when contacts are updated, deals move between stages, tasks are completed, or custom events occur. Unlike the outbound API calls in the previous steps — which work fine in Bolt's WebContainer — webhooks are incoming HTTP requests from Freshsales' servers to your app. Bolt's WebContainer runs inside a browser tab and has no public IP address or DNS hostname, so Freshsales cannot reach it. You must deploy your app first. Deploy your app to Netlify or Vercel using Bolt's export workflow. Once you have a stable public URL (e.g., https://my-crm-app.netlify.app), create the webhook handler. In Freshsales, navigate to Settings → Integrations → Webhooks → Create Webhook. Enter your webhook URL (https://my-crm-app.netlify.app/api/freshsales/webhook), select the events to subscribe to — common choices are Contact Updated, Deal Stage Changed, and Task Completed — and click Save. Your webhook handler should validate requests are genuinely from Freshsales using the X-Freshsales-Signature header (if your plan provides it), parse the JSON payload, and respond with HTTP 200 within 10 seconds. The payload structure varies by event type: contact events include a contact object with all fields, deal events include a deal object with the old and new stage IDs. Add FRESHSALES_WEBHOOK_SECRET to your hosting platform's environment variables before enabling the webhook. Set environment variables in Netlify under Site settings → Environment variables or in Vercel under Project settings → Environment variables.

Bolt.new Prompt

Create a Freshsales webhook handler at /api/freshsales/webhook. It should handle POST requests, parse the event type from the payload, and log the event details. For deal_stage_changed events, extract the deal name, old stage, and new stage. For contact_updated events, log the contact email and what changed. Return 200 OK for all valid events.

Paste this in Bolt.new chat

app/api/freshsales/webhook/route.ts
1// app/api/freshsales/webhook/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export async function POST(request: NextRequest) {
5 let payload: Record<string, unknown>;
6
7 try {
8 payload = await request.json();
9 } catch {
10 return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
11 }
12
13 const eventType = payload.event_type as string;
14
15 if (eventType === 'deal_stage_changed') {
16 const deal = payload.deal as Record<string, unknown>;
17 console.log(`Deal stage changed: ${deal.name}`);
18 // Add your database update or notification logic here
19 }
20
21 if (eventType === 'contact_updated') {
22 const contact = payload.contact as Record<string, unknown>;
23 console.log(`Contact updated: ${contact.email}`);
24 // Add your sync or notification logic here
25 }
26
27 return NextResponse.json({ received: true });
28}

Pro tip: Freshsales webhooks do not include a built-in signature verification mechanism on all plans. As a minimum security measure, add a secret token as a query parameter in your webhook URL (e.g., /api/freshsales/webhook?secret=my_secret) and verify it on every incoming request.

Expected result: After deploying and registering the webhook in Freshsales Settings, deal stage changes and contact updates trigger POST requests to your /api/freshsales/webhook endpoint and are logged in your server console.

Common use cases

Lead capture form syncing contacts to Freshsales

Add a contact form or waitlist signup to your Bolt app that automatically creates a new lead in Freshsales when submitted. Each new contact gets tagged with a source (e.g., 'Bolt Landing Page') and routed to the appropriate sales pipeline. No manual data entry for the sales team.

Bolt.new Prompt

Create a lead capture form in Next.js with fields for first name, last name, email, phone, and company. When submitted, call /api/freshsales/contacts/create to create a new Freshsales lead with these details and the tag 'bolt-landing-page'. Show a success message after the contact is created.

Copy this prompt to try it in Bolt.new

Freddy AI lead score dashboard

Build a sales dashboard that fetches all contacts from Freshsales and sorts them by Freddy AI lead score. Sales reps can see which leads are hottest at a glance, with each card showing the lead's name, company, score, last activity date, and a direct link to the contact in Freshsales.

Bolt.new Prompt

Build a sales dashboard that fetches all Freshsales contacts from /api/freshsales/contacts and displays them sorted by Freddy AI lead score in descending order. Each contact card should show name, email, company, Freddy score as a badge (color-coded: green 70+, yellow 40-69, red below 40), and last contacted date.

Copy this prompt to try it in Bolt.new

Deal pipeline tracker with stage updates

Create a kanban-style deal pipeline view that fetches all open deals from Freshsales and groups them by pipeline stage. Sales managers can track deal value at each stage and identify bottlenecks. Deals are fetched fresh on every page load using server-side API calls.

Bolt.new Prompt

Create a deal pipeline page that fetches all open deals from Freshsales /api/freshsales/deals and displays them in a kanban board grouped by stage. Each deal card should show deal name, amount (formatted as currency), expected close date, and associated contact name. Add totals for each column.

Copy this prompt to try it in Bolt.new

Troubleshooting

API returns 401 Unauthorized on every request even with the correct key

Cause: The Authorization header format is wrong, or the API key was copied with extra whitespace. Freshsales requires the exact format Token token={api_key} — not Bearer, not Basic.

Solution: Double-check your freshsalesFetch utility uses the exact header value 'Token token=' + apiKey with no extra spaces. Copy your API key again from Settings → API Settings and paste it directly into .env.local without any trailing newline or space characters.

typescript
1// Correct Authorization header format
2Authorization: `Token token=${process.env.FRESHSALES_API_KEY}`
3
4// NOT these:
5Authorization: `Bearer ${process.env.FRESHSALES_API_KEY}`
6Authorization: `Token ${process.env.FRESHSALES_API_KEY}`

Contact creation returns 422 Unprocessable Entity with validation errors

Cause: Required fields are missing or a field value format is incorrect. The most common issue is passing company as a plain string instead of an object with a name property, or providing phone as a number instead of a string.

Solution: Ensure company is passed as { name: 'Company Name' }, not as a plain string. Phone numbers must be strings. The first_name field is required — requests with only email will fail. Check the error response body for the specific field name causing the validation error.

typescript
1// Correct contact body format
2{
3 contact: {
4 first_name: 'John', // required
5 last_name: 'Doe',
6 email: 'john@example.com',
7 mobile_number: '+15551234567', // string, not number
8 company: { name: 'Acme Inc' } // object, not string
9 }
10}

Webhook events are not arriving even though the endpoint is registered in Freshsales

Cause: The webhook is pointing to a Bolt preview URL (e.g., a *.webcontainer-api.io address) instead of a deployed production URL. Freshsales cannot reach the WebContainer because it has no public internet address.

Solution: Deploy your app to Netlify or Vercel first, then register the webhook URL using your deployed domain. The preview URL in Bolt's WebContainer is only accessible from your local browser session. Only a deployed app with a stable public HTTPS URL can receive Freshsales webhook events.

Freddy AI lead scores are 0 or null for all contacts including existing ones

Cause: Freddy AI lead scoring requires the Freshsales Growth plan or higher. The free tier (3 users) does not include Freddy AI lead scores, and the field returns null or 0 for all contacts on free accounts.

Solution: Upgrade to Freshsales Growth (paid plan) to enable Freddy AI. For development and prototyping on the free tier, you can still build the dashboard UI and mock the lead_score field with placeholder values. The rest of the Contacts API (creating, listing, updating) works on the free plan.

Best practices

  • Store FRESHSALES_API_KEY and FRESHSALES_DOMAIN as server-side environment variables — never prefix them with NEXT_PUBLIC_ or include them in client-side components, as they would be exposed in the browser bundle.
  • Use the upsert endpoint (/api/contacts/upsert with unique_identifier: { email }) instead of plain POST /contacts when capturing leads from forms — this prevents duplicate contacts when the same person submits multiple times.
  • Add pagination handling to your contacts and deals list routes — Freshsales returns 25 results per page by default, and the meta.total_pages field tells you how many pages exist. Use per_page=100 to reduce the number of requests for dashboards.
  • Cache frequently-read data like deal stage lists and pipeline configurations client-side or in a server-side cache — these change rarely and making live API calls on every page load is unnecessary.
  • Test all outbound API calls (create, list, update) in Bolt's WebContainer preview — they work perfectly since Freshsales API calls are outbound HTTPS requests. Only register webhooks after deploying to a public URL.
  • Handle the Freshsales rate limit (1,000 requests per hour per API key) gracefully — add error handling for 429 responses and display a user-friendly message rather than an unhandled exception.
  • Use Freshsales tags consistently across your integration to identify the source of contacts — tag leads from different forms differently (e.g., 'bolt-contact-form', 'bolt-demo-request') so the sales team can filter and prioritize by source.

Alternatives

Frequently asked questions

Does Bolt.new have a native Freshsales integration?

No. Freshsales is not one of Bolt's native connectors. You build the integration manually using Next.js API routes and the Freshsales REST API. Bolt's AI can generate most of the boilerplate code when you describe what CRM features you need, but you must obtain your own Freshsales API key and manage credentials in your .env file.

Can I use Freshsales in Bolt's development preview without deploying?

Yes, for all outbound API operations. Creating contacts, listing deals, updating records, and reading Freddy AI lead scores all work from Bolt's WebContainer since they are outbound HTTPS requests from your server-side routes. The only exception is Freshsales webhooks — Freshsales needs to send HTTP events to your server, which requires a deployed public URL. For most dashboard and form-capture use cases, you can fully develop and test in the Bolt preview.

What is Freddy AI and how do I display lead scores?

Freddy AI is Freshworks' built-in machine learning engine that analyzes behavioral signals — email engagement, website visits, deal history, and demographic fit — to assign each contact a score from 0 to 100. The score is available as the lead_score field on every contact object returned by the Contacts API. It requires a paid Freshsales Growth plan or higher; the free tier always returns 0. Display scores with color-coded badges in your dashboard to help sales reps prioritize outreach.

How do I avoid creating duplicate contacts in Freshsales from Bolt form submissions?

Use the Freshsales upsert endpoint POST /api/contacts/upsert instead of the plain POST /contacts endpoint. Include a unique_identifier: { email } field in the request body. If a contact with that email already exists, Freshsales updates the existing record; if it doesn't exist, it creates a new one. The response status code tells you which happened: 200 means updated, 201 means created.

Is the Freshsales free tier sufficient for building a Bolt integration?

Yes for most development purposes. The free tier supports 3 users and provides access to the full Contacts, Deals, and Accounts APIs without paying. The main limitations are that Freddy AI lead scoring requires a paid plan, and some advanced features like automation rules and AI-powered deal insights are paid-only. For building and testing the integration, the free tier is more than adequate.

Can I integrate Freshsales with other Freshworks products through the same API key?

Each Freshworks product (Freshsales, Freshdesk, Freshservice, Freshchat) has its own separate API with its own endpoint domain and API key. For example, Freshdesk uses your-domain.freshdesk.com/api/v2/ with a different API key. You cannot use your Freshsales API key to call the Freshdesk API. However, if your Freshworks account has cross-product integrations configured (like syncing Freshdesk tickets to Freshsales contacts), those sync automatically in the background without any additional API work from your side.

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.