Freshsales Connector Guide

The Freshsales connector lets Tealfabric workflows send and retrieve CRM records such as contacts and deals through the Freshsales API. It is useful for pipeline automation, lead routing, and lifecycle updates where sales teams rely on current CRM data.

Document information
FieldValue
Canonical URL/docs/04_connecting-systems/connectors/f/freshsales
Version (published date)2026-05-08
Tagsconnectors, reference, freshsales
Connector IDfreshsales-1.0.0

Freshsales connector workflow showing API key authentication, contact and deal writes, filtered CRM retrieval, and workflow decisions based on pipeline state.

When to use this connector

Use this connector when workflows need to create or update CRM objects and then read them for downstream actions. Common scenarios include routing inbound leads, syncing lifecycle stages, and triggering notifications when deal values or statuses change. The connector is most effective when endpoint usage and field mappings are standardized across teams.

Prerequisites

Before configuring the integration, create a Freshsales API key with permissions for the resources your workflow needs. Confirm your account domain and ensure the API base URL points to the correct tenant. Establish an ownership process for key rotation and API usage monitoring before production rollout.

Configuration

Store these values in the integration profile:

  • api_key (required): Freshsales API key.
  • base_url (optional): Freshsales tenant URL, for example https://domain.freshsales.io.
  • api_secret (optional): Additional secret if required by your environment.
  • timeout_seconds (optional): Request timeout in seconds; default 30.

Create contacts with send

Use send to create or update CRM records. Keep payloads minimal and schema-aligned so records remain consistent across automations.

const baseUrl = "https://api.example.com/api/v1";
const tenantId = "<TENANT_ID>";
const apiKey = "<API_KEY>";

type ContactResult = {
  message_count: number;
  data: { contact?: { id?: string; email?: string } };
  response: { contact?: { id?: string; email?: string } };
};

async function createContact(integrationId: string): Promise<ContactResult> {
  const response = await fetch(
    `${baseUrl}/integrations/${encodeURIComponent(integrationId)}/execute`,
    {
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "X-Tenant-ID": tenantId,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        operation: "send",
        endpoint: "api/contacts",
        method: "POST",
        data: {
          contact: {
            first_name: "Jordan",
            last_name: "Lee",
            email: "jordan.lee@example.com",
            phone: "555-2048",
            job_title: "Revenue Operations Manager",
          },
        },
      }),
    }
  );
  if (!response.ok) throw new Error(`Request failed: ${response.status}`);
  const payload = (await response.json()) as { success?: boolean; data?: ContactResult };
  if (!payload.success || !payload.data) throw new Error("Freshsales send failed");
  return payload.data;
}
curl -X POST "https://api.example.com/api/v1/integrations/<ENTITY_ID>/execute" \
  -H "X-API-Key: <API_KEY>" \
  -H "X-Tenant-ID: <TENANT_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "send",
    "endpoint": "api/contacts",
    "method": "POST",
    "data": {
      "contact": {
        "first_name": "Jordan",
        "last_name": "Lee",
        "email": "jordan.lee@example.com",
        "phone": "555-2048"
      }
    }
  }'
{
  "success": true,
  "data": {
    "message_count": 1,
    "data": {
      "contact": {
        "id": "<ENTITY_ID>",
        "email": "jordan.lee@example.com"
      }
    },
    "response": {
      "contact": {
        "id": "<ENTITY_ID>",
        "email": "jordan.lee@example.com"
      }
    }
  }
}

Retrieve deals with receive

Use receive to query deals and contacts for workflow decisions such as routing high-value opportunities. Apply pagination and filters so large datasets can be processed predictably.

const baseUrl = "https://api.example.com/api/v1";
const tenantId = "<TENANT_ID>";
const apiKey = "<API_KEY>";

type Deal = {
  id?: string;
  name?: string;
  amount?: number;
  deal_stage_id?: number;
};

async function listOpenDeals(integrationId: string): Promise<Deal[]> {
  const response = await fetch(
    `${baseUrl}/integrations/${encodeURIComponent(integrationId)}/execute`,
    {
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "X-Tenant-ID": tenantId,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        operation: "receive",
        endpoint: "api/deals",
        query: {
          per_page: 20,
          page: 1,
          filter_type: "open",
        },
      }),
    }
  );
  if (!response.ok) throw new Error(`Request failed: ${response.status}`);
  const payload = (await response.json()) as {
    success?: boolean;
    data?: { message_count?: number; data?: Deal[]; total_size?: number };
  };
  if (!payload.success || !payload.data?.data) throw new Error("Freshsales receive failed");
  return payload.data.data;
}
curl -X POST "https://api.example.com/api/v1/integrations/<ENTITY_ID>/execute" \
  -H "X-API-Key: <API_KEY>" \
  -H "X-Tenant-ID: <TENANT_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "receive",
    "endpoint": "api/deals",
    "query": {
      "per_page": 20,
      "page": 1,
      "filter_type": "open"
    }
  }'
{
  "success": true,
  "data": {
    "message_count": 1,
    "data": [
      {
        "id": "<ENTITY_ID>",
        "name": "Q2 Renewal Expansion",
        "amount": 85000,
        "deal_stage_id": 2
      }
    ],
    "total_size": 1
  }
}

Test connection with test

Use test to validate api_key and tenant reachability. Only test enforces required api_key validation up front; other operations attempt the API call and surface remote auth errors when credentials are missing or invalid.

{
  "success": true,
  "data": {
    "message": "Freshsales connection test successful",
    "details": {
      "api_base_url": "https://domain.freshsales.io"
    }
  }
}

Reliability guidance

Freshsales API limits can vary by account plan, so use pacing and bounded retries for production workflows. Keep field mapping definitions versioned to prevent silent failures when CRM schemas change. For large data pulls, rely on pagination checkpoints so jobs can resume safely after interruptions.

Related references