To use Freshsales with V0, generate your CRM dashboard UI in V0, then create a Next.js API route at app/api/freshsales/route.ts that calls the Freshsales REST API using your Freshsales API key stored in Vercel environment variables. Freshsales returns leads, contacts, deals, and accounts data — your API route proxies these calls server-side so your API key stays secure.
Building a Custom Sales CRM Dashboard with Freshsales and V0
Freshsales is part of the Freshworks suite, which makes it an attractive choice for teams already using Freshdesk or Freshservice — the unified data model across products enables cross-functional dashboards that pull support ticket data alongside sales pipeline information. For V0 developers, Freshsales's REST API provides full CRUD access to all CRM objects: leads, contacts, accounts, deals, notes, tasks, and appointments. This enables a wide range of custom sales tools: deal pipeline visualizations, lead routing dashboards, custom reporting interfaces, and sales performance trackers built with V0's polished UI generation.
Freshsales's pricing and positioning differentiates it from HubSpot in important ways. While HubSpot has expanded into a full marketing, sales, and service platform with corresponding complexity and cost, Freshsales stays focused on pure sales CRM with a simpler pricing structure — making it common in mid-market companies and real estate teams. The most frequently searched use case for Freshsales integration is real estate CRM customization, where agents need deal pipeline views and automated lead assignment that Freshsales's standard interface does not provide out of the box.
The integration architecture follows V0's standard pattern for external CRM APIs: V0 generates the React components (kanban boards, data tables, contact cards), and a Next.js API route handles all Freshsales communication. Your Freshsales domain and API key live in Vercel environment variables. One Freshsales-specific note: the API base URL includes your Freshsales domain (e.g., yourcompany.myfreshworks.com), so you need to store this domain alongside the API key.
Integration method
V0 generates your CRM dashboard UI — lead cards, deal pipeline boards, contact tables, activity timelines — while a Next.js API route handles all Freshsales API calls server-side, keeping your API key out of the browser. The API route fetches Freshsales data and proxies it back to the V0-generated React components.
Prerequisites
- A V0 account with a Next.js project generated at v0.dev
- A Freshsales account at freshworks.com/crm/sales (free trial available, no credit card required)
- Your Freshsales API key from Freshsales → Admin Settings → API Settings → Your API Key
- Your Freshsales domain (the part of your Freshsales URL before .myfreshworks.com, e.g., 'yourcompany')
- A Vercel account with your V0 project deployed via GitHub
Step-by-step guide
Generate Your CRM Dashboard UI in V0
Generate Your CRM Dashboard UI in V0
Start by prompting V0 to generate the frontend components for your Freshsales integration. V0 produces high-quality data-dense CRM interfaces: kanban pipeline boards, sortable data tables, contact cards, deal detail panels, and activity timelines. Describing the Freshsales data structure in your V0 prompt helps it generate components with the correct field bindings. Key Freshsales data shapes to reference in your V0 prompts: Leads have first_name, last_name, email, mobile_number, lead_source, lead_stage, owner_id, and lead_score. Deals have name, deal_stage_id, amount, expected_close, deal_type, and associated_contacts. Contacts have first_name, last_name, email, mobile_number, company, and job_title. Including these field names helps V0 generate TypeScript interfaces and component props that match what your API route will return. For CRM dashboards specifically, V0 excels at generating kanban boards for deal pipelines, but the drag-and-drop functionality requires a library like @hello-pangea/dnd (the maintained fork of react-beautiful-dnd) or @dnd-kit/core. Ask V0 to use one of these libraries rather than implementing drag-and-drop from scratch. V0 knows both libraries and can generate the correct drag-and-drop setup. One important V0-specific consideration for CRM dashboards: V0 may generate the pipeline board with hardcoded stage names and counts. Make sure to ask V0 to load both the deals and the stage configuration dynamically from your API routes, since Freshsales stage names are configurable per account and may not match generic labels like 'New' or 'In Progress'.
Create a sales deals table with columns: Deal Name, Contact, Company, Deal Value (formatted as $X,XXX), Stage badge (colored by stage), Expected Close Date, and an 'Open' button. Add sorting on Deal Value and Expected Close Date columns. Include a search input that filters the visible rows. Load deals from /api/freshsales/deals. Show a skeleton table while loading.
Paste this in V0 chat
Pro tip: Ask V0 to use Tailwind's color utilities to color-code deal stage badges consistently — for example, blue for New, yellow for Negotiating, green for Won, and red for Lost.
Expected result: V0 generates a CRM deals table with sorting, filtering, and loading states. Fetch calls are wired to /api/freshsales/deals with the correct request structure.
Create the Freshsales Deals API Route
Create the Freshsales Deals API Route
Create a server-side API route that fetches deal data from Freshsales. In your V0 project, create app/api/freshsales/route.ts. Freshsales's API uses your API key in a Authorization header and your account domain as part of the base URL. Freshsales authentication uses the pattern: Authorization: Token token=YOUR_API_KEY. Note the specific format — 'Token token=' followed by your API key. This is different from the standard Bearer token format used by most APIs. Getting this header exactly right is critical; the wrong format returns a 401 even with a valid key. The Freshsales API base URL is https://YOUR_DOMAIN.myfreshworks.com/crm/sales/api. Your domain is the subdomain part of your Freshsales URL. For example, if you access Freshsales at https://mycompany.myfreshworks.com, your domain is 'mycompany'. Store this as FRESHSALES_DOMAIN in Vercel environment variables. Freshsales uses a filter-based search API for listing records. To fetch all deals, you use the POST /deals/filter endpoint with a filter body, or GET /deals for paginated lists. The filter endpoint is more powerful for custom queries — you can filter by stage, owner, date range, or any custom field. For a simple deals list, GET /deals?page=1&per_page=50 is the simplest approach. Freshsales API pagination uses page and per_page query parameters. The maximum per_page is typically 100 for most endpoints. The response includes a meta object with total_pages and total_count, making it straightforward to implement pagination in your UI.
Add a Next.js API route at app/api/freshsales/route.ts that accepts GET requests. Fetch deals from the Freshsales API at https://FRESHSALES_DOMAIN.myfreshworks.com/crm/sales/api/deals using Authorization: Token token=FRESHSALES_API_KEY from environment variables. Include page and per_page query parameters from the request. Return the deals array and meta pagination data as JSON.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23const FRESHSALES_DOMAIN = process.env.FRESHSALES_DOMAIN;4const FRESHSALES_API_KEY = process.env.FRESHSALES_API_KEY;56interface FreshsalesDeal {7 id: number;8 name: string;9 amount: number;10 deal_stage_id: number;11 expected_close: string | null;12 created_at: string;13 updated_at: string;14 owner_id: number;15}1617interface FreshsalesResponse {18 deals: FreshsalesDeal[];19 meta: {20 total_pages: number;21 total_count: number;22 };23}2425export async function GET(request: NextRequest) {26 try {27 if (!FRESHSALES_DOMAIN || !FRESHSALES_API_KEY) {28 return NextResponse.json(29 { error: 'Freshsales configuration missing' },30 { status: 500 }31 );32 }3334 const { searchParams } = new URL(request.url);35 const page = searchParams.get('page') || '1';36 const perPage = searchParams.get('per_page') || '50';3738 const url = new URL(39 `https://${FRESHSALES_DOMAIN}.myfreshworks.com/crm/sales/api/deals`40 );41 url.searchParams.set('page', page);42 url.searchParams.set('per_page', perPage);43 url.searchParams.set('include', 'owner,contact,deal_stage');4445 const response = await fetch(url.toString(), {46 headers: {47 Authorization: `Token token=${FRESHSALES_API_KEY}`,48 'Content-Type': 'application/json',49 },50 });5152 if (!response.ok) {53 const errorText = await response.text();54 console.error('Freshsales API error:', errorText);55 return NextResponse.json(56 { error: `Freshsales API error: ${response.status}` },57 { status: response.status }58 );59 }6061 const data: FreshsalesResponse = await response.json();6263 return NextResponse.json({64 deals: data.deals || [],65 meta: data.meta,66 });67 } catch (error) {68 console.error('Freshsales route error:', error);69 return NextResponse.json(70 { error: 'Failed to fetch Freshsales deals' },71 { status: 500 }72 );73 }74}Pro tip: Freshsales's include parameter is powerful — adding include=owner,contact,deal_stage to deal requests returns the related objects inline, avoiding multiple API calls to fetch owner names and stage labels separately.
Expected result: The API route returns a paginated list of Freshsales deals with associated owner and stage data. The response includes a meta object with total deal count for pagination.
Add Create and Update Routes for CRM Records
Add Create and Update Routes for CRM Records
Extend your integration with routes for creating and updating CRM records. A read-only CRM dashboard has limited value — the real utility comes from being able to create leads from web forms, update deal stages, log activities, and assign records to team members directly from your V0 app. For creating leads (common for contact forms and landing pages built with V0), create app/api/freshsales/leads/route.ts with a POST handler. Freshsales's lead creation endpoint is POST /leads with a JSON body containing the lead's details wrapped in a lead object. Required fields are first_name and last_name or email — at least one contact identifier is needed. For updating deal stages (for the kanban pipeline drag-and-drop), create a PUT handler for individual deals. Freshsales updates use PUT /deals/{id} with a deal object in the body containing only the fields you want to change. You do not need to send the full deal object — partial updates work correctly. A common integration pattern for real estate use cases: embed a Freshsales lead capture form on a V0 landing page. When the form is submitted, the Next.js API route creates a lead in Freshsales with the form data, sends a confirmation email via an email service, and redirects the user to a thank-you page. V0 can generate the form component with the correct fetch call structure. Freshsales webhooks let you receive real-time notifications when CRM records change — when a new lead comes in, when a deal closes, or when a contact is updated. For webhook integration, create a route at app/api/freshsales/webhook/route.ts that receives POST events from Freshsales and updates your app's state or database accordingly.
Add a 'New Lead' modal to the CRM dashboard. The modal form has fields for first name, last name, email, phone, company, and lead source (dropdown with options: Website, Referral, Cold Call, Social Media). On submit, POST to /api/freshsales/leads with the form data. Show a success message and close the modal after the lead is created. Use react-hook-form for form validation.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23const FRESHSALES_DOMAIN = process.env.FRESHSALES_DOMAIN;4const FRESHSALES_API_KEY = process.env.FRESHSALES_API_KEY;56export async function POST(request: NextRequest) {7 try {8 const body = await request.json();9 const { firstName, lastName, email, phone, company, leadSource } = body;1011 if (!email && !firstName) {12 return NextResponse.json(13 { error: 'At least one of email or first name is required' },14 { status: 400 }15 );16 }1718 const leadData = {19 lead: {20 first_name: firstName || '',21 last_name: lastName || '',22 email: email || '',23 mobile_number: phone || '',24 company: { name: company || '' },25 lead_source_id: null, // Map leadSource to your Freshsales source IDs26 custom_field: {27 lead_source_label: leadSource || '',28 },29 },30 };3132 const response = await fetch(33 `https://${FRESHSALES_DOMAIN}.myfreshworks.com/crm/sales/api/leads`,34 {35 method: 'POST',36 headers: {37 Authorization: `Token token=${FRESHSALES_API_KEY}`,38 'Content-Type': 'application/json',39 },40 body: JSON.stringify(leadData),41 }42 );4344 if (!response.ok) {45 const errorData = await response.text();46 console.error('Freshsales create lead error:', errorData);47 return NextResponse.json(48 { error: 'Failed to create lead in Freshsales' },49 { status: response.status }50 );51 }5253 const newLead = await response.json();54 return NextResponse.json({ lead: newLead.lead }, { status: 201 });55 } catch (error) {56 console.error('Freshsales leads POST error:', error);57 return NextResponse.json(58 { error: 'Internal server error' },59 { status: 500 }60 );61 }62}Pro tip: Freshsales lead sources are configured in your account settings and have specific IDs. Fetch your available lead sources from /crm/sales/api/selector/lead_sources to map source names to their IDs before creating leads.
Expected result: The leads POST route successfully creates a new lead in Freshsales. The new lead appears in your Freshsales account under Leads within seconds of creation.
Add Freshsales Credentials to Vercel
Add Freshsales Credentials to Vercel
Your Freshsales API routes require two environment variables: your API key and your Freshsales domain. Both must be available in Vercel's environment for your serverless functions to authenticate with Freshsales. Go to Vercel Dashboard → your project → Settings → Environment Variables and add: FRESHSALES_API_KEY: Your Freshsales API key. To find it, log into Freshsales and go to Admin Settings (gear icon) → API Settings. Your API key is shown on that page. It is a long alphanumeric string. Do not add any prefix — this is a server-only secret that must never reach the browser. It grants full API access to your Freshsales account data. FRESHSALES_DOMAIN: Just the subdomain portion of your Freshsales URL. If you access Freshsales at https://mycompany.myfreshworks.com, enter only mycompany (without https://, without .myfreshworks.com). This is combined with the Freshsales base URL in your API route code. Set both variables for Production, Preview, and Development environments. Click Save after adding each. For local development, add both to your .env.local file. After saving, push a commit to trigger a Vercel redeployment. The new environment variables are not available in previously built functions — a fresh deployment is required. Once deployed, verify the integration by loading your CRM dashboard and checking that deals appear.
Pro tip: Freshsales API keys do not expire by default, but rotate them if you suspect unauthorized access. You can regenerate your API key in Freshsales Admin Settings → API Settings — note that this immediately invalidates the old key.
Expected result: Vercel Dashboard shows FRESHSALES_API_KEY and FRESHSALES_DOMAIN saved. After redeployment, your API routes successfully connect to Freshsales and return real CRM data.
Test Your CRM Dashboard and Set Up Webhooks
Test Your CRM Dashboard and Set Up Webhooks
Test the full Freshsales integration by navigating to your deployed V0 app and verifying that CRM data loads and mutations work correctly. Freshsales test data: in your Freshsales account, create a few test leads, deals, and contacts if your account is empty. Freshsales's free trial comes pre-populated with sample data, so you may already have records to work with. Navigate to the deals page in your V0 app. The deals table should populate with your Freshsales deals within 1-2 seconds. Check the Vercel function logs (Vercel Dashboard → project → Functions) to verify the Freshsales API is responding with 200 status codes. If you see 401 errors, your API key or domain is incorrect. If you see empty data with 200 responses, verify you have active deals in Freshsales. Test lead creation by submitting the New Lead form. After submission, immediately check Freshsales → Leads to confirm the record was created with the correct data. If the lead appears but some fields are wrong, adjust the field mapping in your API route's lead object structure. For real-time updates — receiving notifications when a deal is won or a lead is assigned — configure Freshsales webhooks in Admin Settings → Integrations → Webhooks. Add your Vercel deployment URL as the webhook endpoint (e.g., https://your-app.vercel.app/api/freshsales/webhook). Select the events you want (deal updated, lead created, contact updated). Your webhook route will receive POST requests with event payloads whenever these events occur in Freshsales. For complex Freshsales integrations with custom fields, multi-stage pipeline automation, and real-time sync between Freshsales and other data sources, RapidDev's team can help design and build the full integration architecture for your V0 app.
Add a deals summary bar above the pipeline table showing three metrics: Total Pipeline Value (sum of all open deal amounts), Average Deal Size, and Deals Closing This Month (count of deals with expected_close in the current month). Calculate these from the deals data already loaded from /api/freshsales/deals.
Paste this in V0 chat
Pro tip: Freshsales has a bulk update API endpoint at /deals/bulk_update that lets you update multiple deal stages in a single request — useful for kanban board operations where a user moves several deals at once.
Expected result: The CRM dashboard displays real Freshsales data, lead creation works end-to-end, and webhooks (if configured) deliver real-time notifications to your Vercel deployment.
Common use cases
Visual Sales Pipeline Dashboard
A sales manager uses V0 to build a kanban-style deal pipeline that shows all active deals organized by stage (New, In Progress, Negotiation, Won, Lost). Each deal card shows the deal name, value, associated contact, and days in current stage. Dragging deals between columns calls the Freshsales API to update the deal stage. The dashboard gives a real-time visual overview of the entire sales pipeline.
Create a kanban board sales pipeline with columns for each deal stage: New Lead, Contacted, Proposal Sent, Negotiating, and Closed Won. Each card shows the deal name, deal value in dollars, associated company name, and a colored badge for days in stage (green <7, yellow 7-14, red >14). Load deals from /api/freshsales/deals and group them by stage. Make the cards draggable between columns.
Copy this prompt to try it in V0
Lead Management and Assignment Dashboard
A real estate team needs a custom lead assignment interface not available in Freshsales's standard UI. The dashboard shows all unassigned leads sorted by source and creation date, with one-click assignment to team members. Assigning a lead calls the Freshsales API to update the lead's owner. This is the most searched Freshsales use case for real estate teams.
Build a lead assignment dashboard for a real estate team. Show unassigned leads in a sortable table with columns: lead name, source (e.g., Zillow, Referral, Website), phone, email, lead score, and creation date. Add an 'Assign To' dropdown per row with team member names and an 'Assign' button that POSTs to /api/freshsales/leads/assign. Show a success toast after assignment.
Copy this prompt to try it in V0
Contact Activity Timeline
A sales rep wants a unified view of all interactions with a contact — emails sent, calls made, notes added, deals associated, and support tickets opened. The V0 app fetches the contact's activities from Freshsales and renders them as a chronological timeline with icons for each activity type and quick links to open the original record in Freshsales.
Create a contact detail page with an activity timeline. Show the contact's name, company, email, phone, and lead score at the top. Below, display a vertical timeline of activities — each item has an icon (email, phone, note, deal), a description, and a timestamp. Load the contact and activities from /api/freshsales/contacts/[id]. Include a 'View in Freshsales' button that opens the contact in a new tab.
Copy this prompt to try it in V0
Troubleshooting
Freshsales API returns 401 Unauthorized
Cause: The Authorization header format is incorrect. Freshsales requires the specific format 'Token token=YOUR_API_KEY' — not 'Bearer YOUR_API_KEY' or just 'Token YOUR_API_KEY'. The exact string 'Token token=' is required.
Solution: Verify your Authorization header is exactly: `Authorization: 'Token token=' + process.env.FRESHSALES_API_KEY` — note there is no space between 'Token' and 'token='. Also check that FRESHSALES_API_KEY is correctly set in Vercel environment variables and that you have redeployed after adding it.
1// WRONG2Authorization: `Bearer ${process.env.FRESHSALES_API_KEY}`34// CORRECT5Authorization: `Token token=${process.env.FRESHSALES_API_KEY}`Freshsales API returns 404 Not Found for all endpoints
Cause: The FRESHSALES_DOMAIN environment variable contains the full URL instead of just the subdomain, resulting in a malformed API URL like https://https://mycompany.myfreshworks.com.myfreshworks.com.
Solution: Set FRESHSALES_DOMAIN to just the subdomain portion — for example, 'mycompany' not 'https://mycompany.myfreshworks.com'. Your API route builds the full URL by combining the domain with the Freshsales base URL pattern.
1// FRESHSALES_DOMAIN should be: mycompany2// NOT: https://mycompany.myfreshworks.com34const url = `https://${process.env.FRESHSALES_DOMAIN}.myfreshworks.com/crm/sales/api/deals`;Deals list returns empty array but the account has active deals
Cause: The Freshsales filter or pagination is excluding the deals. By default, some Freshsales API endpoints only return deals assigned to the token owner, or return only the first page of results.
Solution: Remove owner-specific filters from the API request to fetch all deals. Add include=owner,contact to get full related object data. For pipeline views that need all deals across the team, ensure the API key belongs to an admin user who has visibility into all records.
Lead creation succeeds (201) but the lead is not visible in Freshsales
Cause: The lead may be in a different view filter in Freshsales (e.g., 'My Leads' instead of 'All Leads'), or the lead may have been auto-assigned to a different owner and is only visible to them.
Solution: In Freshsales, switch the leads view from 'My Leads' to 'All Leads' to see leads owned by all team members. Check the lead's owner assignment in the API response to identify who it was assigned to. If you want all created leads to go to a specific owner, add owner_id to the lead creation request body.
Best practices
- Use the exact Authorization header format 'Token token=YOUR_API_KEY' — Freshsales does not accept the more common Bearer format.
- Store FRESHSALES_DOMAIN as just the subdomain (e.g., 'mycompany') and construct the full URL in your API route to keep the configuration clean.
- Include related objects using the include parameter (include=owner,contact,deal_stage) to fetch all needed data in one API call instead of making separate calls.
- Use Freshsales webhooks for real-time updates instead of polling — configure webhooks for deal stage changes and lead assignments to keep your V0 dashboard current.
- Implement optimistic UI updates in your V0 components — update the deal stage locally before the API call completes so the kanban board feels instant to the user.
- Use Freshsales's filter API for complex queries rather than fetching all records and filtering client-side — the server-side filtering is faster and reduces data transfer.
- Rotate your Freshsales API key immediately if it is ever committed to a Git repository or exposed in logs.
- For multi-user apps, implement per-user authentication with Freshsales OAuth rather than using a single shared admin API key.
Alternatives
HubSpot is a better choice if you need a broader platform that combines marketing automation, email campaigns, and CRM in a single tool with an extensive ecosystem.
Zoho CRM is an alternative if your team is already in the Zoho ecosystem (Zoho Books, Zoho Desk) and wants tightly integrated CRM without switching platforms.
Insightly is relevant for project-based businesses where CRM and project management need to work together in a single tool.
Frequently asked questions
What is the difference between Freshsales and HubSpot for a V0 integration?
Freshsales is a pure sales CRM with a simpler API and pricing structure, making it easier to integrate for custom dashboards. HubSpot's API covers more objects (marketing, CMS, service hub) but is correspondingly more complex. If you only need sales pipeline and contact management, Freshsales's API is more focused. HubSpot is better if you need marketing automation alongside CRM.
Can I build a real estate CRM on top of Freshsales with V0?
Yes — real estate is one of the most common Freshsales customization use cases. You can build property-specific deal pipelines (Property Interest → Site Visit → Offer → Closed), custom lead forms for property inquiry pages, and dashboards that show deals by property location or agent. V0 generates the visual pipeline and forms while the Next.js API routes handle all Freshsales CRUD operations.
Does my Freshsales API key give access to my entire company's data?
API keys in Freshsales operate with the same permissions as the user who generated them. An admin user's API key can access all records across the account. A regular sales user's key can only access records visible to them (typically their own leads, deals, and shared contacts). Use the minimum-privilege key needed for your app's use case.
How do I handle Freshsales custom fields in my V0 app?
Freshsales custom fields are returned in the API response under a custom_field key alongside the standard fields. To see what custom fields your account has configured, call GET /crm/sales/api/custom_fields?entity_type=deal (or lead, contact). In your V0 components, add those fields to your TypeScript interface and display them like standard fields. For creating/updating records, include custom field values in the custom_field object in your request body.
Can I receive real-time updates from Freshsales when deals change?
Yes, via Freshsales webhooks. Configure webhook endpoints in Freshsales Admin Settings → Integrations → Webhooks, pointing to your Vercel deployment URL. When a deal stage changes, a lead is assigned, or a contact is updated, Freshsales sends a POST request to your webhook route with the event payload. Your Next.js API route processes these events and can update your app's database or send notifications.
Is there a Freshsales npm package I can use instead of calling the REST API directly?
Freshsales does not have an official npm package. The integration uses direct HTTP calls to their REST API with the Authorization header. This is actually simpler than managing a third-party SDK dependency — the fetch() pattern shown in this guide covers all CRUD operations and does not add any npm dependencies beyond what V0 already includes in your project.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation