---
title: "Freshchat to Salesforce Service Cloud Migration: Technical Guide"
slug: freshchat-to-salesforce-service-cloud-migration-technical-guide
date: 2026-07-27
author: Nachi
categories: [Migration Guide, Salesforce Service Cloud]
excerpt: "A technical guide to migrating Freshchat conversations, messages, and contacts into Salesforce Service Cloud — covering data model mapping, API extraction, the conversation fidelity decision, and edge cases."
tldr: "Freshchat to Salesforce Service Cloud migration requires API-based extraction, schema translation (conversations → Cases, messages → CaseComments), and careful handling of rich chat formats with no direct Salesforce equivalent."
canonical: https://clonepartner.com/blog/freshchat-to-salesforce-service-cloud-migration-technical-guide/
---

# Freshchat to Salesforce Service Cloud Migration: Technical Guide


> [!NOTE]
> **TL;DR:** Freshchat to Salesforce Service Cloud migration requires API-based extraction, schema translation (conversations → Cases, messages → CaseComments or EmailMessages), and careful handling of rich chat formats that have no direct Salesforce equivalent. CSV-only methods work for small, shallow moves. For threaded history, files, ownership, and custom property fidelity, use an API/ETL pipeline or a managed migration service.

There is no native import path between Freshchat and Salesforce Service Cloud. This is a schema translation project — you are moving from a purpose-built messaging platform with conversations, messages, users, and channels into an enterprise CRM service layer where Case, Contact, Account, and EmailMessage objects follow strict relational hierarchies and governor limits. Every record must be extracted via the Freshchat REST API, transformed to fit Salesforce's schema, and loaded through Salesforce's Bulk API 2.0 or REST API. ([developers.freshchat.com](https://developers.freshchat.com/api/))

One important clarification: Freshchat's **Account API** is about Freshworks tenant configuration, not customer company records. Standalone Freshchat is not a full CRM. If your deployment is bundled with Freshsales Suite, CRM entities like companies, leads, or opportunities belong to a separate extraction workstream outside core Freshchat chat history.

This guide covers data model mapping, extraction strategies, API constraints on both sides, the conversation fidelity problem, edge cases, and validation — everything an engineering team needs to execute this migration without data loss. For a broader pre-flight checklist, see the [Salesforce Service Cloud Migration Checklist](https://clonepartner.com/blog/blog/salesforce-service-cloud-migration-checklist/). For sign-off testing, the [Post-Migration QA Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/) is the companion document. For teams migrating from Freshdesk (the ticket-based product, not Freshchat), the [Freshdesk to Salesforce Service Cloud guide](https://clonepartner.com/blog/blog/how-to-migrate-from-freshdesk-to-salesforce-service-cloud-2026/) covers that path.

*Last updated: July 2025. Salesforce API examples use v67.0; adjust to your org's current API version.*

## Why Teams Migrate from Freshchat to Salesforce Service Cloud

**Freshchat** is Freshworks' messaging platform for support conversations across web chat, mobile, WhatsApp, Facebook Messenger, and email. Its public API exposes users, agents, groups, channels, conversations, messages, files, and raw reports. API access is available on the **Pro and Enterprise plans only** — Free and Growth plans do not provide REST API access, which makes programmatic extraction impossible on those tiers. ([developers.freshchat.com](https://developers.freshchat.com/api/))

**Salesforce Service Cloud** is an enterprise CRM-based service platform where the Case object is the central hub connecting Accounts, Contacts, Assets, Entitlements, and Knowledge. It provides case management, omni-channel routing, SLA enforcement, and deep analytics within the Salesforce ecosystem.

Teams typically make this move for one or more of these reasons:

- **Enterprise CRM consolidation:** The organization standardizes on Salesforce across sales, marketing, and service. Running Freshchat alongside Salesforce creates a data silo that breaks 360-degree customer views.
- **Compliance and audit trails:** Salesforce's EmailMessage records are read-only and immutable, creating compliance-grade audit trails that Freshchat's conversation model does not natively provide. Organizations with compliance requirements should also evaluate **Salesforce Shield** (Platform Encryption, Event Monitoring, Field Audit Trail) for encryption-at-rest and granular audit logging — these are add-on features not included in base Service Cloud licenses.
- **SLA enforcement:** Salesforce Entitlements and Milestones offer built-in SLA tracking with automated breach notifications — capabilities that go beyond Freshchat's native SLA features. Note: Entitlements require **Enterprise Edition or higher**.
- **Scale and customization:** Salesforce supports custom objects, complex Apex-driven automation, and Flow orchestration at enterprise scale.

### Salesforce Edition Requirements

Not all Service Cloud features are available on every edition. This affects both migration target design and post-migration operations:

| Feature | Professional | Enterprise | Unlimited |
|---|---|---|---|
| Case Management | ✓ | ✓ | ✓ |
| Bulk API 2.0 | ✓ | ✓ | ✓ |
| Omni-Channel Routing | — | ✓ | ✓ |
| Entitlements & Milestones | — | ✓ | ✓ |
| Einstein Bots | — | Add-on | ✓ |
| Salesforce Shield | Add-on | Add-on | Add-on |
| Data Storage (base) | 10 GB | 10 GB | Unlimited* |
| File Storage (base) | 10 GB | 10 GB | Unlimited* |

*"Unlimited" still has per-org caps; contact Salesforce for specifics. Additional data storage is approximately **$125/GB/month** (list price) for Enterprise Edition — a significant cost factor when migrating millions of chat messages as individual records.*

## Data Model Mapping: Freshchat → Salesforce Service Cloud

This is the hardest part of the migration. Freshchat stores interactions as a continuous stream of messages within a conversation. Salesforce requires discrete, relational records. The schemas are fundamentally different.

### Core Object Mapping

| Freshchat Object | Salesforce Service Cloud Object | Notes |
|---|---|---|
| **User (contact/visitor)** | **Contact** | If no Account exists, create a default or derive from email domain. |
| **Agent** | **User** (with Service Cloud license) | Role and Profile assignment required. Deactivated agents need a mapping strategy. |
| **Group** | **Queue** | Freshchat groups map to Salesforce Queues for case routing. |
| **Conversation** | **Case** | Each conversation becomes a Case. Status mapping required. |
| **Message** | **CaseComment** or **EmailMessage** or **ConversationEntry** | The fidelity decision — see below. |
| **Conversation Property** | **Case Custom Field** | Freshchat supports up to 50 custom conversation properties. Each maps to a custom field on Case. |
| **Channel** | **Case Origin** / custom field | Web chat, WhatsApp, email map to Case Origin picklist values. A custom field is often cleaner. |
| **CSAT Score** | **Custom field on Case** | No native equivalent. Create a custom CSAT field. |
| **Attachment** | **ContentVersion / ContentDocumentLink** | Salesforce Files with ContentDocumentLink to the parent Case. |

### The Conversation Fidelity Decision

This is the single most consequential architectural choice in the migration. Freshchat conversations contain threaded, timestamped messages from users, agents, and bots with rich content (images, files, interactive elements). Salesforce offers three target models:

1. **CaseComment:** Simplest. Plain text only, no HTML. Agent vs. customer distinction via the `IsPublished` flag. Loses rich formatting and inline attachments. Works for archival. CaseComments count as "related records" and consume less data storage than standalone objects.
2. **EmailMessage:** Supports HTML body, From/To addresses, and attachments. Better fidelity. But EmailMessage is designed for email, not chat — you are shoehorning chat into an email model. If you want messages to appear in the Case email timeline, Email-to-Case must be enabled and `ParentId` must be set during loading; otherwise records can end up invisible to agents. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=000385818&language=en_US&type=1&utm_source=openai))
3. **ConversationEntry (enhanced Messaging):** Part of Salesforce's newer Messaging object model (API v47.0+). Designed for chat-style interactions but primarily intended for Salesforce's own Messaging channels, not historical import from third-party systems.

> [!WARNING]
> **Decision point:** Most migrations land on CaseComment for historical archive (acceptable loss of rich formatting) or EmailMessage for higher fidelity (HTML body preserved, but semantically odd for chat data). There is no option that preserves Freshchat's exact interactive message format inside Salesforce. Salesforce retired Legacy Chat (LiveChatTranscript) on February 14, 2026 — do not target the deprecated LiveChatTranscript object.

### Channel-Specific Transformation Rules

Freshchat supports multiple messaging channels, each with unique message formats that require specific handling:

| Channel | Message Format | Transformation Approach |
|---|---|---|
| **Web Chat** | `message_parts` with text, image, file types | Flatten to plain text (CaseComment) or HTML (EmailMessage). Inline images → ContentVersion attachments. |
| **WhatsApp** | Template messages with parameters, media messages, location pins, contact cards | Template messages: extract parameter values and reconstruct as readable text (e.g., "Your order #{{1}} ships on {{2}}" → "Your order #12345 ships on Jan 5"). Media: download and attach as ContentVersion. Location: convert to "Lat: X, Lng: Y" text. |
| **Facebook Messenger** | Rich cards, carousels, quick replies, persistent menus | Cards: flatten to " [Card Title] [Subtitle] [URL]" format. Carousels: render each card sequentially. Quick replies: record as " [User selected: Option X]". |
| **Apple Business Chat** | List pickers, time pickers, rich links | Flatten interactive elements to descriptive text: " [Time picker: User selected 2:00 PM]". Preserve rich link URLs. |
| **Email (via Freshchat)** | HTML body, standard attachments | Most direct mapping to EmailMessage. Preserve HTML body. Map From/To headers. |

### Field-Level Mapping Reference

| Freshchat Field | Salesforce Field | Type | Notes |
|---|---|---|---|
| `conversation_id` | `Case.Freshchat_Conversation_ID__c` | Text (External ID) | Store for deduplication and relationship linking |
| `status` (new/assigned/resolved) | `Case.Status` | Picklist | Map to Salesforce picklist values deliberately |
| `created_time` | `Case.CreatedDate` | DateTime | Requires "Set Audit Fields" permission via Bulk API |
| `assigned_agent_id` | `Case.OwnerId` | Lookup(User) | Map agent UUID → Salesforce User ID |
| `assigned_group_id` | `Case.OwnerId` (Queue) | Lookup(Queue) | If no agent assigned, route to Queue |
| `channel_id` | `Case.Origin` | Picklist | Map channel name to Origin value |
| `user.email` | `Contact.Email` | Email | Primary match key |
| `user.id` | `Contact.Freshchat_User_ID__c` | Text (External ID) | Stable external key for relational mapping |
| `user.reference_id` / `org_contact_id` | External ID or join key | Text | Useful for adjacent CRM joins |
| `message.message_parts` | `CaseComment.CommentBody` | TextArea (Long) | Flatten rich message parts to plain text; strip HTML if needed |
| `message.created_time` | `CaseComment.CreatedDate` | DateTime | Requires "Set Audit Fields" permission |
| `message.actor_type` | `CaseComment.IsPublished` | Boolean | `user` → true (public), `agent` → false, or vice versa based on your convention |

Salesforce supports both standard and custom objects, plus custom relationship fields. The real limitation is operational: every extra custom object adds permissions, page layouts, automation, and reporting overhead. Plan the target schema accordingly. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=platform.dev_objectfields.htm&language=en_US&utm_source=openai))

## Migration Approaches Compared

| Approach | Best For | Scalability | Key Risk | Complexity |
|---|---|---|---|---|
| **CSV Export → Data Loader** | Small datasets, archival only | Small | No message-level fidelity; manual relationship mapping | Low |
| **API-to-API (custom scripts)** | Full-fidelity migration at any scale | Enterprise | Rate limits on both sides; engineering investment | High |
| **Custom ETL pipeline** | Enterprise programs needing repeatable loads | Enterprise | Highest build cost; most QA surface | High |
| **Middleware (Zapier, Make)** | Ongoing sync of new conversations | Light sync only | No historical backfill; per-task pricing | Medium |
| **Managed migration service** | Complex data, tight timelines, low eng bandwidth | Any | Vendor selection | Low (for you) |

**CSV Export → Data Loader:** Use Freshchat's Extract API (`/v2/reports/raw/`) to pull conversation metadata as CSV, then import into Salesforce via Data Loader. Salesforce's Data Import Wizard supports CSV only, up to 50,000 records, and a limited set of objects. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=sf.data_import_wizard.htm&language=en_US&type=5&utm_source=openai)) This loses individual message threading and attachment fidelity. Suitable only for archival where full conversation replay is not needed.

**API-to-API (custom scripts):** Full control, full fidelity. Engineers write scripts (Python, Node.js) to extract data from Freshchat REST APIs, transform JSON payloads, and load into Salesforce using Bulk API 2.0. Requires engineering time, rate-limit management on both sides, and retry logic. The right choice when you have a dev team and need message-level accuracy.

**Custom ETL pipeline:** Land Freshchat JSON in a staging store (PostgreSQL, S3), normalize IDs, map statuses and custom fields, then upsert ordered batches into Salesforce using external IDs. Maximum control and observability, but the highest build cost. This approach works well when the pipeline needs to become an ongoing sync foundation. For real-time delta sync during coexistence, consider Salesforce **Change Data Capture** or **Platform Events** to propagate changes bidirectionally.

**Middleware (Zapier, Make):** Low-code platforms can capture *new* Freshchat conversations and push them to Salesforce in real time. They do not support historical bulk migration — task quotas, API timeout restrictions, and per-task pricing make backfills impractical. Use only for ongoing sync *after* a one-time migration.

**Managed migration service:** When engineering bandwidth is scarce or the dataset is large (50K+ conversations with attachments), a managed service reduces the risk of broken relationships, missed records, and extended timelines.

**Practical recommendation:** Use CSV only for pilots and shallow history. Use API/ETL for real migrations. Use middleware for post-cutover sync. Use a managed service when the business cannot tolerate relationship loss or engineering distraction.

## Pre-Migration Planning

Before extracting a single record, complete this checklist:

- [ ] **Verify Freshchat plan tier:** Confirm your Freshchat plan (Pro or Enterprise) provides REST API access. Free and Growth plans do not.
- [ ] **Audit Freshchat data volume:** Total conversations, messages, users, attachments. Use the paginated API endpoints to get accurate counts.
- [ ] **Identify unused data:** Filter out bot-only conversations, spam, test users, abandoned sessions, and retired channels. These inflate volume without adding value and increase Salesforce storage costs.
- [ ] **Define migration scope:** All history vs. last N months? Active conversations only or resolved too?
- [ ] **Estimate Salesforce storage impact:** Calculate expected record counts. Each CaseComment is ~2 KB minimum. 100,000 conversations × 15 messages average = 1.5M CaseComments ≈ 3 GB of data storage. Compare against your org's storage limits and budget for overages at ~$125/GB/month (Enterprise Edition list price).
- [ ] **Map agents:** Build a Freshchat Agent UUID → Salesforce User ID lookup table. Decide what happens to conversations owned by deactivated agents — map them to a generic "Legacy Agent" user to preserve data without consuming a license.
- [ ] **Map conversation properties:** List all custom properties in Freshchat (up to 50) and create corresponding custom fields on the Case object in Salesforce.
- [ ] **Choose conversation fidelity model:** CaseComment vs. EmailMessage vs. flat transcript. This decision cannot easily be reversed.
- [ ] **Configure Salesforce org first:** Custom fields, picklist values, Queues, page layouts, and automation (Flows, assignment rules) must be configured before data loading begins.
- [ ] **Plan for attachments:** Estimate total attachment size. Salesforce Files (ContentVersion) has a default per-file limit of 2 GB and storage cost implications.
- [ ] **Choose cutover model:** Big bang (single weekend), phased (by region or department), or incremental (coexistence with delta sync).
- [ ] **Set up a sandbox:** Always run the migration against a Salesforce Full Copy Sandbox first. Never load directly to production on the first attempt.
- [ ] **Create rollback keys:** Add a custom External ID field on every target object holding the legacy Freshchat ID. This enables upsert-based re-runs and bulk rollback if needed.
- [ ] **Define data retention policy:** Determine what happens to Freshchat data after migration. Note your Freshworks contract termination timeline — data may be deleted 30–90 days after contract end depending on your agreement. Export and archive a complete backup before cancellation.

## Migration Architecture: Extract → Transform → Load

```
┌─────────────────┐     ┌──────────────────────┐     ┌───────────────────────┐
│   FRESHCHAT      │     │   STAGING LAYER       │     │   SALESFORCE SC        │
│                  │     │                       │     │                        │
│  /v2/conversations├────►│  PostgreSQL / S3      │     │                        │
│  /v2/messages    │     │                       │     │                        │
│  /v2/users       │ ETL │  ┌─────────────────┐  │     │                        │
│  /v2/agents      ├────►│  │ Transform Layer │  │     │  1. Accounts           │
│  /v2/groups      │     │  │                 │  │LOAD │  2. Contacts           │
│  /v2/channels    │     │  │ - Flatten msgs  │  ├────►│  3. Cases              │
│  /v2/reports/raw │     │  │ - Map statuses  │  │     │  4. CaseComments       │
│                  │     │  │ - Resolve IDs   │  │     │  5. ContentVersions    │
│  Attachments ────├────►│  │ - Clean fields  │  │     │  6. ContentDocLinks    │
│  (download binary)     │  └─────────────────┘  │     │                        │
└─────────────────┘     └──────────────────────┘     └───────────────────────┘
                               │
                               ▼
                     ┌──────────────────┐
                     │  Validation Logs  │
                     │  - Row counts     │
                     │  - Error records  │
                     │  - Audit trail    │
                     └──────────────────┘
```

### Step 1: Extract from Freshchat

Freshchat's REST API is the primary extraction path. Key endpoints:

- `GET /v2/conversations` — list all conversations (paginated)
- `GET /v2/conversations/{id}/messages` — messages per conversation (max 50 per page)
- `GET /v2/users` — requires at least one filter parameter; cannot list all users without filtering
- `GET /v2/users/{user_id}/conversations` — user-scoped conversation discovery
- `GET /v2/agents` — list all agents
- `GET /v2/groups` — list all groups
- `GET /v2/channels` — list all channels
- `POST /v2/reports/raw/` — Extract API for bulk CSV report generation

> [!NOTE]
> **Rate limit reality:** Freshchat's per-tier rate limits are not comprehensively documented in the public API reference — some limits appear in account-level documentation and vary by plan tier. The API returns HTTP 429 when limits are exceeded, with `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers. The dashboard API limit is documented at 100 requests/minute. In practice, expect extraction throughput of approximately **1,500–2,500 conversations/hour** (including message sub-requests) at sustainable rate-limit headroom. Implement exponential backoff on every extraction script. ([developers.freshchat.com](https://developers.freshchat.com/api/))

A key Freshchat-specific constraint: conversation discovery can be user-scoped through `GET /v2/users/{user_id}/conversations`. If your user inventory is incomplete, conversation extraction via this path will be incomplete too. Cross-reference with `GET /v2/conversations` to verify coverage. ([developers.freshchat.com](https://developers.freshchat.com/api/))

The Extract API at `/v2/reports/raw/` can generate CSV reports for events like `Conversation-Created`, `Message-Sent`, `Resolution-Time`, and CSAT scores. This is useful for bulk metadata extraction but does not include full message content — you still need the messages endpoint for that.

**API endpoint note:** Freshchat's newer `.api.freshchat.com` endpoints behave differently from the legacy `{subdomain}.freshchat.com` endpoints. Test your extraction scripts against the exact URL format your account uses before running a full extraction.

**Extraction code (Python):**

```python
import requests
import time
import logging

# Configure for your account — adjust BASE_URL to match your endpoint format
# Legacy: https://{subdomain}.freshchat.com/v2
# Newer:  https://{subdomain}.api.freshchat.com/v2
BASE_URL = "https://{subdomain}.freshchat.com/v2"
HEADERS = {"Authorization": "Bearer {api_token}", "Accept": "application/json"}

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def extract_conversations():
    page = 1
    all_conversations = []
    while True:
        resp = requests.get(
            f"{BASE_URL}/conversations",
            headers=HEADERS,
            params={"page": page, "items_per_page": 50}
        )
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 60))
            logger.warning(f"Rate limited. Sleeping {retry_after}s")
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        data = resp.json()
        convos = data.get("conversations", [])
        if not convos:
            break
        all_conversations.extend(convos)
        logger.info(f"Extracted {len(all_conversations)} conversations (page {page})")
        page += 1
    return all_conversations

def extract_messages(conversation_id):
    page = 1
    messages = []
    while True:
        resp = requests.get(
            f"{BASE_URL}/conversations/{conversation_id}/messages",
            headers=HEADERS,
            params={"page": page, "items_per_page": 50}
        )
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 60))
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        data = resp.json()
        msgs = data.get("messages", [])
        if not msgs:
            break
        messages.extend(msgs)
        page += 1
    return messages

def download_attachment(url, local_path):
    """Download attachment immediately — URLs may expire."""
    resp = requests.get(url, headers=HEADERS, stream=True)
    resp.raise_for_status()
    with open(local_path, 'wb') as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)
    return local_path
```

### Step 2: Transform

Transformation handles these operations:

- **Flatten message parts:** Freshchat messages use `message_parts` arrays with types like `text`, `image`, `file`. Flatten to plain text for CaseComment or HTML for EmailMessage. See the channel-specific transformation rules table above for WhatsApp, Messenger, and other channel formats.
- **Map statuses:** Freshchat's `new` / `assigned` / `resolved` → Salesforce's Status picklist values.
- **Resolve agent IDs:** Build a lookup table mapping Freshchat agent UUIDs to Salesforce User IDs.
- **Resolve user IDs:** Match Freshchat users to Salesforce Contacts by email. Create new Contacts for unmatched users.
- **Map conversation properties:** Each custom conversation property becomes a custom field value on the Case.
- **Handle timestamps:** Convert Freshchat's ISO 8601 timestamps to Salesforce's expected DateTime format. Preserve UTC. Note: Salesforce displays CreatedDate in the viewing user's time zone in reports and UI — during validation, compare timestamps in UTC to avoid false mismatches caused by time zone rendering.
- **Clean custom fields:** A text field in Freshchat cannot be mapped to a Picklist in Salesforce unless the data perfectly matches the Picklist values. Run a standardization pass before insertion.

### Step 3: Load into Salesforce Service Cloud

**Load order matters.** Salesforce enforces referential integrity:

1. **Accounts** (create default accounts or derive from contact email domains)
2. **Contacts** (linked to Accounts via `AccountId`)
3. **Users** (agent mapping — typically pre-existing in Salesforce)
4. **Cases** (linked to Contact via `ContactId`, to Queue/User via `OwnerId`)
5. **CaseComments / EmailMessages** (linked to Case via `ParentId`)
6. **Attachments / ContentVersions** (linked via `ContentDocumentLink`)

**Use Bulk API 2.0 for Cases and Contacts.** Salesforce's Bulk API 2.0 can process up to 100 million records per 24-hour period. It auto-chunks into internal batches of 10,000 records. Each job supports up to 150 MB of CSV data and handles one object at a time. You can run up to 25 concurrent jobs. In practice, expect ingest throughput of approximately **30,000–60,000 Cases/hour** with 15–20 fields per record, depending on org complexity and active automation. ([developer.salesforce.com](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm))

For CaseComments, use the standard REST API — Bulk API does not support CaseComment inserts in all configurations. Batch your REST calls using the Composite API (up to 25 subrequests per call) to reduce API call consumption.

```python
# Salesforce Bulk API 2.0 — Create ingest job for Cases
# Note: Replace v67.0 with your org's current API version
import requests

SF_BASE = "https://{instance}.salesforce.com"
SF_HEADERS = {"Authorization": "Bearer {access_token}", "Content-Type": "application/json"}

# Step 1: Create job
job = requests.post(
    f"{SF_BASE}/services/data/v67.0/jobs/ingest",
    headers=SF_HEADERS,
    json={
        "object": "Case",
        "operation": "upsert",
        "externalIdFieldName": "Freshchat_Conversation_ID__c",
        "contentType": "CSV",
        "lineEnding": "LF"
    }
).json()

# Step 2: Upload CSV data
csv_headers = {"Authorization": "Bearer {access_token}", "Content-Type": "text/csv"}
requests.put(
    f"{SF_BASE}/services/data/v67.0/jobs/ingest/{job['id']}/batches",
    headers=csv_headers,
    data=open("cases.csv", "rb")
)

# Step 3: Close job to begin processing
requests.patch(
    f"{SF_BASE}/services/data/v67.0/jobs/ingest/{job['id']}",
    headers=SF_HEADERS,
    json={"state": "UploadComplete"}
)
```

> [!TIP]
> **JWT Bearer flow** is the recommended authentication method for backend migration scripts against Salesforce. It is certificate-based, requires no refresh tokens, and no human login. Configure a Connected App with the `api` and `refresh_token` OAuth scopes.

**Error handling essentials:**

- Read Freshchat `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers and slow down before hard failures.
- Watch Salesforce API usage headers and stop cleanly on `REQUEST_LIMIT_EXCEEDED`.
- Keep row-level success and failure logs for every object load.
- Make every load idempotent through external IDs — never match on email or subject text alone. ([developers.freshchat.com](https://developers.freshchat.com/api/))

## Edge Cases and Failure Modes

### Attachment Expiration
Freshchat attachment URLs often require authentication or expire after a set time (commonly 24–72 hours). Your script must download the binary file immediately during extraction and store it locally or in S3 before pushing to Salesforce as a `ContentVersion`. Never defer attachment downloads to the load phase.

### Duplicate Contacts
Freshchat users may not have unique emails — anonymous web visitors get auto-generated IDs. Decide before migration: create placeholder Contacts for anonymous visitors (e.g., with a naming convention like `anonymous-{freshchat_user_id}@placeholder.local`), or skip them entirely. Deduplicate users in your staging database before pushing to Salesforce.

### Bot Messages
Freshchat conversations may include Freddy AI bot interactions. These messages have an `actor_type` of `system` or `bot`. Decide whether to import bot messages as CaseComments for historical reference or discard them. Interactive elements like buttons, carousels, and quick replies have no Salesforce equivalent and must be flattened to descriptive text (e.g., `[Bot presented options: A, B, C] [User selected: B]`).

### Multi-Message Conversations (200+ Messages)
Freshchat's Download Transcript app caps at 200 messages per conversation. For conversations exceeding this, you *must* use the API's paginated messages endpoint to get the full history.

### Missing or Deactivated Agents
If a Freshchat agent has left the company, they will not have an active Salesforce User license. Map their historical cases to a generic "Legacy Agent" user in Salesforce to preserve the data without consuming a license.

### User-Scoped Extraction Gaps
If your user inventory is incomplete, conversation discovery via `GET /v2/users/{user_id}/conversations` will miss orphaned conversations. Cross-reference with `GET /v2/conversations` to verify full coverage.

### Salesforce Governor Limits
If you load Cases with Apex triggers or Flows active, governor limits (DML operations, SOQL queries per transaction) can cause batch failures. **Disable all automation during bulk loads** — Validation Rules, Triggers, Process Builders, and Flows — then re-enable and backfill after the load completes.

### Storage Costs
Salesforce data storage is expensive (~$125/GB/month at Enterprise Edition list price). Storing millions of individual chat messages as separate records will rapidly consume your storage limits. For reference: 1 million CaseComments with average 500-character bodies ≈ 1.5–2 GB of data storage. Compiling chat threads into a single rich-text Case description or using CaseComments (which count differently against storage) is often a necessary compromise. Always calculate projected storage consumption before committing to a fidelity model.

### Time Zone Display Discrepancies
Salesforce stores all DateTime fields in UTC but displays them in the viewing user's configured time zone. During validation, always compare timestamps in UTC. Agents may report that "timestamps are wrong" when they are actually seeing correct UTC values rendered in their local time zone — this is expected behavior, not a migration defect.

## Validation and Testing

Do not assume a successful API `200 OK` response means the data is correct.

**Record count validation:**
- Total conversations in Freshchat = Total Cases in Salesforce
- Total messages per conversation = Total CaseComments per Case
- Total unique users = Total Contacts created or matched

**Field-level validation:**
- Spot-check 5–10% of migrated Cases for correct status, owner, timestamps, and custom field values
- Verify attachment links resolve correctly
- Confirm Contact → Case relationships are intact

**Sampling strategy:**
- Pull a random sample of 50 conversations from Freshchat
- Locate each corresponding Case in Salesforce
- Compare message count, timestamps, and content
- Include at least 5 conversations per channel type (web chat, WhatsApp, etc.) in the sample

**UAT process:**
- Have 2–3 support agents review their own historical conversations in Salesforce
- Confirm the data is usable for their workflow — search, reporting, customer context

**Rollback plan:**
- Tag all migrated records with a custom field (`Migration_Source__c = 'Freshchat'`) or use External IDs
- If rollback is needed, bulk-delete records matching that tag

**Validate incrementally** — check after Contacts, check after Cases, check after Comments. Do not wait until the end to discover a broken relationship mapping.

## Post-Migration Tasks

Once the data is in Salesforce, the operational work begins.

- **Rebuild automation:** Freshchat's assignment rules, IntelliAssign, and bot flows do not transfer. Rebuild using Salesforce Omni-Channel Routing (Enterprise Edition+), Flows, and Einstein Bots.
- **Configure Omni-Channel:** Set up presence statuses, routing configurations, and skill-based routing to replace Freshchat's group-based assignment.
- **Agent training:** Salesforce Service Cloud's console is a different paradigm from Freshchat's inbox. Budget time for agent onboarding on the Service Console, macros, and Knowledge.
- **Monitor for gaps:** Run daily reports for the first two weeks comparing Freshchat conversation counts against Salesforce Case counts to catch missed records.
- **Monitor delta sync:** If you kept Freshchat live during the transition, monitor the sync error logs daily for failed updates until full cutover. Consider Salesforce **Change Data Capture** to detect modifications on the Salesforce side that need back-propagation during coexistence.
- **Archive source data:** Export a complete backup of Freshchat data to your own storage (S3, GCS, etc.) before canceling your Freshworks contract. Freshworks typically deletes tenant data 30–90 days after contract termination.

## Best Practices

- **Back up everything** before starting. Export Freshchat data to a local database or S3 bucket as your source of truth.
- **Always use External IDs.** Create custom fields like `Freshchat_Conversation_ID__c` marked as External IDs on every target object. This enables upsert operations, prevents duplicates on re-runs, and gives you a rollback key.
- **Run test migrations in a sandbox.** Use a Salesforce Full Copy Sandbox. Never load directly to production on the first attempt.
- **Disable Salesforce automation during import.** Turn off Validation Rules, Triggers, Process Builders, and Flows before loading data. Leaving them on will slow the Bulk API and cause unexpected record failures.
- **Log everything.** Every API call, every transformation decision, every skipped record. You will need the audit trail when debugging edge cases in week two.
- **Validate incrementally** — object by object, not all at once at the end.
- **Automate transforms** instead of doing them in spreadsheets. Manual CSV manipulation is the fastest path to broken data.
- **Version your migration scripts** and tag them with the Salesforce API version they target. API behavior can change between versions.

## When to Use a Managed Migration Service

**Build in-house when:**
- You have a dedicated dev team with Salesforce API experience
- Data volume is under 10K conversations
- You can tolerate a 2–4 week engineering investment

**Consider a managed service when:**
- Data volume exceeds 50K conversations with attachments
- Engineering time is better spent on product work
- You need zero-downtime migration with parallel operation during cutover
- The Freshchat account uses complex conversation properties, multi-channel data (WhatsApp, Messenger), or heavy bot history that requires careful transformation

## What This Migration Actually Costs

The Freshchat → Salesforce Service Cloud migration is a 4–8 week project for most teams, depending on data volume, conversation fidelity requirements, and Salesforce org readiness. The data migration itself is the smaller workstream. The larger effort is Salesforce configuration — custom fields, page layouts, Flows, Omni-Channel, Knowledge base setup, and agent training.

**Rough effort estimates by scale:**

| Scale | Conversations | Estimated Engineering Effort | Elapsed Time |
|---|---|---|---|
| Small | < 5,000 | 40–80 hours | 1–2 weeks |
| Medium | 5,000–50,000 | 120–240 hours | 3–5 weeks |
| Large | 50,000–500,000 | 300–500 hours | 5–8 weeks |
| Enterprise | 500,000+ | 500+ hours | 8–12 weeks |

These estimates include extraction, transformation, loading, and two full validation cycles. They do not include Salesforce org configuration, agent training, or automation rebuild.

Budget 2–5x the effort of a chat-to-chat migration. The payoff is a unified service platform that ties into your CRM, enforces SLAs, and scales with the organization — but only if the migration is executed with fidelity.

For most Freshchat to Salesforce Service Cloud projects, the winning pattern is: design the Salesforce schema first, migrate with API/ETL second, validate by relationship and timeline fidelity third, and use middleware only for short-term coexistence after the main load.

> Migrating from Freshchat to Salesforce Service Cloud? We have handled hundreds of chat platform migrations — including the edge cases that break DIY scripts. Book a 30-minute technical scoping call.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I export Freshchat conversations directly into Salesforce Service Cloud?

No. There is no native import path between Freshchat and Salesforce. You must extract data via Freshchat's REST API (conversations, messages, users endpoints), transform it to match Salesforce's Case/Contact/CaseComment schema, and load it through Salesforce's Bulk API 2.0 or REST API.

### How do Freshchat conversations map to Salesforce objects?

Each Freshchat conversation becomes a Salesforce Case. Individual messages become CaseComments (plain text) or EmailMessages (HTML-supported). Freshchat users map to Contacts, agents to Users, groups to Queues, and conversation properties to custom Case fields.

### Can we migrate Freshchat attachments to Salesforce?

Yes, but Freshchat attachment URLs often expire or require authentication. Your script must download the binary files immediately during extraction and upload them to Salesforce as ContentVersion records linked to the corresponding Case via ContentDocumentLink.

### How long does a Freshchat to Salesforce Service Cloud migration take?

Typically 4–8 weeks end to end. The data migration itself may take days, but Salesforce org configuration — custom fields, Flows, Omni-Channel routing, page layouts, and agent training — is the larger workstream and must be completed before data loading begins.

### What happens to Freshchat bot messages during migration?

Freshchat bot messages (Freddy AI) have an actor_type of 'system' or 'bot.' You can import them as CaseComments for historical reference or exclude them entirely. Interactive elements like buttons and carousels have no Salesforce equivalent and must be flattened to text.
