Freshdesk to LiveChat Migration: A Technical Guide
Technical guide to migrating from Freshdesk to LiveChat and HelpDesk — covering API limits, data model mapping, field transformation, and edge cases.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Freshdesk to LiveChat Migration: A Technical Guide
Migrating from Freshdesk to LiveChat is not a data copy. It is a data-model conversion between fundamentally different systems.
Freshdesk is an omnichannel helpdesk built around tickets, conversations, contacts, companies, SLAs, and automations. LiveChat is a real-time chat platform — and since LiveChat's legacy ticketing system retired on January 6, 2025, all asynchronous ticket handling now lives in HelpDesk (helpdesk.com), a separate product from Text, LiveChat's parent company.
That means a Freshdesk-to-LiveChat migration is actually a migration to two targets: LiveChat for real-time chat infrastructure and HelpDesk for historical ticket data. There is no native migration tool between them. No export button, no built-in importer. You need custom scripts, careful data-model mapping, and a clear understanding of what can — and what cannot — make the trip.
If you attempt to force every Freshdesk ticket into LiveChat's chat interface, the migration will fail. LiveChat's UI is not designed to handle long-running, multi-week email threads with complex HTML bodies, inline attachments, and CC chains.
This guide covers the technical realities: API constraints, data transformation, attachment handling, idempotency, crash recovery, and the sequence of operations needed to prevent data loss.
The Text Ecosystem: Where Your Data Actually Goes
Before writing any migration code, understand the destination architecture.
LiveChat handles synchronous, real-time messaging. A Chat contains multiple Threads, which contain an array of Events (messages, system notes, file uploads). It is optimized for live agent-customer conversations, not historical ticket archives. LiveChat does not expose a documented bulk historical chat import endpoint — the public Agent Chat API v3.5 covers start_chat, send_event, upload_file, and archive operations. The documented message event fields do not include a caller-supplied created_at, which means you cannot backfill old tickets as historical chats with their original timestamps.
HelpDesk handles asynchronous ticket support. A Ticket contains a stream of Events — a public reply is an event, an internal note is an event, a status change is an event. HelpDesk exposes a dedicated POST /v1/importedTickets endpoint built specifically for migrations, accepting historical events with custom timestamps.
The split: Freshdesk tickets, conversations, and contacts go to HelpDesk. Future real-time chat traffic goes to LiveChat. Knowledge base articles move to a separate KB product or require manual recreation. Freshchat transcripts (if you used them) are a separate extraction — they live in the Freshchat API, not the Freshdesk Tickets API.
Before you commit: HelpDesk does not support SLAs, multi-product routing, parent-child tickets, or knowledge base article import via API. If your Freshdesk workflows depend on these, evaluate whether HelpDesk can actually replace them before starting the migration.
Freshdesk vs. HelpDesk: Data Model Comparison
The core migration challenge is structural. These systems organize data differently.
| Freshdesk Object | HelpDesk Equivalent | Migration Notes |
|---|---|---|
| Ticket | Ticket (via /v1/importedTickets) |
Direct mapping with field transformation |
| Conversation (reply/note) | Event (type: message) |
Each conversation → one event. Private notes use isPrivate: true |
| Contact | Requester (email + name) |
HelpDesk has no standalone contact object — requester is embedded |
| Company | — | No equivalent. Flatten to tags or custom fields |
| Group | Team | Create teams first, map group IDs → team UUIDs |
| Agent | Agent | Must exist in HelpDesk before ticket import |
| Tag (string) | Tag (UUID) | Create tags via API first, then reference by UUID |
| Custom field (many types) | Custom field (4 types only) | HelpDesk supports: singleLine (120-char limit), multiLine (1,000-char limit), date, url |
| Attachment | Attachment (via transactions) | 3-step upload: create transaction → upload file → reference in ticket |
| Satisfaction rating | Rating | Freshdesk uses numeric scales; HelpDesk uses good/neutral/bad |
| Knowledge base article | — | No import API. Manual recreation required |
| SLA policy | — | Not supported in HelpDesk |
| Automation rule | Rule (limited) | Manual recreation; different trigger/action models |
| Time tracking entries | — | No equivalent in HelpDesk |
| Forum/community posts | — | HelpDesk has no community feature |
Key constraint: It's not possible to import Contacts or Tickets independently in HelpDesk. To maintain data integrity and relationships, you must migrate all associated objects.
Choosing Your Migration Pattern
Not every Freshdesk-to-LiveChat migration looks the same. Your approach depends on how much historical data matters and how quickly you need to cut over.
Pattern 1: LiveChat for new chat, Freshdesk kept read-only. Use this when you mainly want a better chat experience going forward and only need historical Freshdesk data for reference or compliance. Agents work in LiveChat for new conversations while older ticket history stays in Freshdesk. This is the simplest cutover — no data remodeling required — and is often the right call for smaller teams or short deadlines.
Pattern 2: HelpDesk for history, LiveChat for forward traffic. This is the most balanced pattern when you want to land fully on the Text stack without losing historical integrity. Freshdesk tickets, notes, replies, and attachments go into HelpDesk through the imported-ticket flow. Future synchronous conversations move to LiveChat. This pattern respects the actual product boundaries and is the recommended default for teams with more than 1,000 tickets or active open tickets at cutover time.
Pattern 3: Selective chat backfill into LiveChat. Reserve this for narrow edge cases — for example, a curated set of recent premium-support transcripts that agents need to see inside the LiveChat UI. Accept the trade-offs explicitly before starting: IDs are target-generated, original timestamps are not caller-specified in the documented message request, file handling requires re-upload, and ticket constructs like CC loops or private notes do not map cleanly. This pattern is not suitable as a general historical migration strategy.
API Constraints You Must Plan Around
Data migrations live and die by API limits. Both platforms enforce strict boundaries that will determine your migration timeline.
Freshdesk API Limits
Freshdesk enforces per-minute rate limits: Growth plan gets 200 calls/min, Pro gets 400 calls/min, and Enterprise gets 700 calls/min. Trial accounts are limited to 50/min.
The limit is per account, not per key. Every script, integration, and installed app that touches your Freshdesk draws from the same per-minute bucket. Even failed requests count — a 401 from a bad key or a 400 from a malformed body still consumes a call. Validate payloads before you send them.
Endpoint-specific sub-limits add another layer. On a Growth plan, endpoint-specific caps are Ticket Create 80/min, Update 80/min, List 20/min, Contacts 20/min.
The 300-page wall: The GET /api/v2/tickets endpoint paginates at 30 tickets per page (max 100 via per_page). List All Tickets caps at 300 pages (~30,000 tickets at max per_page), Filter Tickets at 10 pages (~300 tickets per query), and archived tickets are excluded from both.
For accounts with more than 30,000 tickets, you must use updated_since date-range windowing or the Account Export API. The standard pagination endpoint will silently stop returning results after page 300.
HelpDesk API Limits
HelpDesk.com API shares an authentication and authorization system with LiveChat. Authentication uses OAuth 2.1 with Personal Access Tokens or full OAuth authorization code flow.
The rate limit is 1,000 requests per 10-minute window per license — shared across all tokens and integrations on that license. That averages to roughly 100 requests/minute, but bursty patterns can exhaust the window early.
For ticket imports with attachments, each ticket consumes at minimum 3 API calls (create transaction + upload attachment + create imported ticket). For a 10,000-ticket migration with attachments on a HelpDesk license at the 1,000 req/10-min limit, the math works out to:
- 10,000 tickets × 3 minimum calls = 30,000 calls
- 30,000 calls ÷ 100 effective calls/minute = 300 minutes (~5 hours)
- Add download time for Freshdesk attachments and processing overhead: realistic estimate is 7–9 hours
Every additional attachment per ticket adds one call. A ticket with 5 attachments requires 7 calls minimum (1 transaction + 5 uploads + 1 import).
Rate limit strategy: Your migration middleware must implement exponential backoff with a loop, not recursion — recursive retry handlers will hit Python's call stack limit under sustained throttling. Read the Retry-After header in 429 responses from both APIs. If marketing syncs, BI exports, or AI tools run alongside your migration, they compete for the same bucket. Schedule migrations during off-hours and disable non-essential integrations on both platforms.
Step-by-Step Migration Process
Step 1: Audit Your Freshdesk Data
Before writing any code, inventory what exists:
- Ticket count by status: How many open, pending, resolved, closed? How many archived? Freshdesk automatically archives closed tickets after 120 days of inactivity — if you only pull "live" tickets you will miss part of your history.
- Custom fields: List every custom field with its type. HelpDesk supports only four types (
singleLinewith 120-char limit,multiLinewith 1,000-char limit,date,url). Dropdowns, checkboxes, and dependent fields have no equivalent and must be flattened to strings. - Attachments: Count total files and estimate total size. Each attachment adds API calls during import.
- Groups and agents: Map Freshdesk groups to HelpDesk teams. Confirm all agents will exist in HelpDesk before import. Flag agents who have left the company — these require a resolution strategy (see Step 3).
- Tags: Export all unique tags. HelpDesk tags are UUID-referenced objects — you must pre-create them.
- Knowledge base: HelpDesk has no KB import API. Plan for manual recreation or a separate KB tool.
- CC chains and watchers: Identify tickets with large CC lists. HelpDesk supports CC on tickets, but the
importedTicketsendpoint does not include a CC field in the documented schema — these contacts need post-import handling. - Webhooks and integrations: Catalog every Freshdesk webhook, app, or API integration pointing to external systems (CRMs, billing, BI pipelines). These all need remapping after cutover.
Step 2: Extract Data from Freshdesk
Freshdesk has three export paths: UI CSV (no conversations), Account Export (XML/JSON dump), and REST API (full data but rate-limited with hidden per-endpoint sub-limits).
For large datasets, start with the Account Export API (POST /api/v2/account/export). It includes both active and archived tickets in a single job:
{
"date_range": {
"start_date": "2024-01-01T00:00:00Z",
"end_date": "2026-01-01T00:00:00Z"
},
"resources": [
{ "name": "tickets", "include": ["notes", "attachments", "ticket_states", "requester"] },
{ "name": "archive_tickets", "include": ["notes", "attachments"] },
{ "name": "contacts" },
{ "name": "companies" }
],
"output_format": "json"
}If the Account Export does not fit your needs (or you need incremental extraction), use the REST API with time-based windowing to bypass the 300-page limit:
import requests
import time
FD_DOMAIN = "yourcompany.freshdesk.com"
FD_API_KEY = "your_api_key"
def get_tickets(page=1, per_page=100):
url = f"https://{FD_DOMAIN}/api/v2/tickets"
params = {"page": page, "per_page": per_page, "include": "description"}
max_retries = 5
base_delay = 60
for attempt in range(max_retries):
resp = requests.get(url, auth=(FD_API_KEY, "X"), params=params)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", base_delay * (2 ** attempt)))
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
raise RuntimeError(f"Exceeded max retries for page {page}")
def get_conversations(ticket_id):
url = f"https://{FD_DOMAIN}/api/v2/tickets/{ticket_id}/conversations"
max_retries = 5
base_delay = 60
for attempt in range(max_retries):
resp = requests.get(url, auth=(FD_API_KEY, "X"))
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", base_delay * (2 ** attempt)))
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
raise RuntimeError(f"Exceeded max retries for ticket {ticket_id} conversations")Freshdesk does not include the full conversation thread in the ticket payload. You must make a secondary API call for every single ticket to retrieve its replies and notes. If you have 50,000 tickets, that is 50,000 additional calls just for conversations — roughly 4.2 hours at 200 calls/min on a Growth plan before any other work.
Rate limit trap: The include=conversations parameter on the ticket list endpoint only returns up to 10 conversations per ticket and consumes extra API credits. For full thread extraction, use the dedicated /tickets/{id}/conversations endpoint. Pace requests using the X-RateLimit-Remaining header.
For accounts exceeding 30,000 tickets, use the updated_since parameter to slice by date range:
GET /api/v2/tickets?updated_since=2024-01-01T00:00:00Z&per_page=100&page=1
This resets the 300-page cap for each date window.
Persist extracted data immediately. Store the raw JSON in a local database (PostgreSQL recommended for its JSONB column support and upsert capability) or as flat files in S3. Do not attempt to transform and push data in memory on the fly. Separating extraction from transformation means a crash does not force you to re-query Freshdesk from scratch.
Use Freshdesk's id field as the primary key in your staging database. This becomes your deduplication key for replays: before inserting, INSERT ... ON CONFLICT (freshdesk_id) DO UPDATE to overwrite stale records without creating duplicates. Store extraction timestamps so you can identify which records were pulled during which run.
Step 3: Pre-Create Target Objects in HelpDesk
Before importing tickets, the target environment must be configured. Ticket import will fail if it references a team, agent, or tag that does not exist.
Teams: Map each Freshdesk Group to a HelpDesk Team. Create via POST /v1/teams. Store the Freshdesk group_id → HelpDesk teamID mapping in your staging database.
Agents: All agents referenced in ticket assignments must exist in HelpDesk. The most significant trap is the invitation model: create_agent does not activate an agent — it sends an email invitation. Any automation that proceeds to assign tickets before the agent accepts will operate on a ghost record.
The inactive agent problem: Tickets handled by agents who no longer work at your company will fail import if the email does not exist in HelpDesk. Two options:
- Provision temporary seats for former employees (costly but preserves exact history).
- Create a single "Legacy Agent" account, map all inactive agent IDs to it, and prepend the original agent's name to the ticket subject or body:
[Originally assigned to: jane.smith@company.com].
Option 2 is the practical choice for most migrations.
Tags: Freshdesk tags are plain strings. HelpDesk tags are UUID-referenced objects scoped to a team. Export all unique tags from Freshdesk, create each via POST /v1/tags, and store the tag_name → HelpDesk tagID mapping.
Custom fields: Create via POST /v1/customFields. Freshdesk dropdown values, checkboxes, and nested dependent fields must be flattened to strings that fit within HelpDesk's type constraints. Define the full mapping table before starting — changing field definitions mid-migration corrupts data already imported.
Store a permanent source-to-target ID map table in your staging database: (freshdesk_ticket_id, helpdesk_ticket_id, migrated_at, status). This table enables replays, spot checks, rollback audits, and delta sync calculations. Write to it after every successful import, including a checksum or event count for validation.
Step 4: Transform and Map Data
This is where most migrations break. The transformation layer must handle several mapping tasks.
Status mapping:
| Freshdesk Status | Freshdesk Value | HelpDesk Status |
|---|---|---|
| Open | 2 | open |
| Pending | 3 | pending |
| Resolved | 4 | solved |
| Closed | 5 | closed |
| Custom statuses | 6+ | Map to nearest equivalent or use tags |
Freshdesk's "Waiting on Customer" and "Waiting on Third Party" (common custom statuses) have no native HelpDesk equivalent. Map them to pending or onhold and add a tag for context.
Priority mapping:
| Freshdesk Priority | Freshdesk Value | HelpDesk Priority |
|---|---|---|
| Low | 1 | -10 |
| Medium | 2 | 0 |
| High | 3 | 10 |
| Urgent | 4 | 20 |
Conversation → Event transformation:
Each Freshdesk conversation (reply or note) becomes an event in the HelpDesk imported ticket payload. The importedTickets endpoint enforces specific ordering rules:
- First event must be of type
message - Events must be in strict chronological order (ascending
date) - All event dates must be in the past relative to import time
- Agent-issued messages require
agentID,agentName, and optionallyisPrivate - Messages without
agentIDare treated as requester-issued
def transform_conversations_to_events(conversations, agent_map):
events = []
for conv in sorted(conversations, key=lambda c: c["created_at"]):
event = {
"date": conv["created_at"],
"type": "message",
"message": {
"text": conv.get("body_text", ""),
"html": sanitize_html(conv.get("body", ""))
}
}
if conv.get("private", False):
event["isPrivate"] = True
agent_id = agent_map.get(conv.get("user_id"))
if agent_id:
event["agentID"] = agent_id
event["agentName"] = conv.get("from_email", "")
events.append(event)
return eventsAgent identification: Freshdesk tracks users via integer IDs. HelpDesk tracks agents via email addresses and UUIDs. During transformation, replace every Freshdesk requester_id and responder_id with the actual email address retrieved during extraction, then look up the HelpDesk UUID from your staging ID map.
HTML sanitization: Freshdesk conversation bodies can contain complex HTML from email clients — tables, embedded CSS, inline styles, and occasionally JavaScript. HelpDesk's rich text renderer does not handle all of it. Run bodies through an HTML sanitizer (e.g., Python's bleach library) to strip scripts and dangerous attributes while preserving structure. Do this in the sanitize_html() call in the transform step, not as an afterthought.
400 error handling strategy: When HelpDesk returns a 400 on a malformed payload, log the full request body and response, mark the ticket as failed in your staging database, and continue processing. Do not halt the entire migration on individual validation failures. Run a second pass on all failed records after the main import completes.
Step 5: Handle Attachments and Inline Images
Handling files is the most complex part of a help desk migration.
Standard attachments follow a three-step process in HelpDesk:
- Create an import transaction:
POST /v1/importedTickets/transactions - Upload files:
POST /v1/importedTickets/attachmentswith thetransactionID - Create the ticket:
POST /v1/importedTicketswith thetransactionIDand attachment IDs in event payloads
Transaction IDs expire after 24 hours. Every uploaded attachment in a transaction must be used in the associated ticket import — unused attachments cause validation errors. For large migrations, create transactions in batches no more than a few hours before you plan to consume them. Track transaction creation times in your staging database.
Freshdesk provides URLs for attachments in its API responses as authenticated, short-lived S3 URLs. You cannot pass the Freshdesk URL into HelpDesk and expect it to work. Your script must download each file locally during extraction, store it to disk or object storage, and re-upload to HelpDesk during import.
Unsupported file types: Freshdesk may allow file extensions that HelpDesk blocks (e.g., .exe, .bat). Catch 400 or 422 responses on file upload, log the failure, and append a note to the ticket body: [Attachment stripped: filename.exe — blocked by HelpDesk security policy].
The inline image trap: Attachments at the bottom of an email are easy to identify. Inline images — screenshots pasted directly into the email body — are much harder and easy to miss.
Freshdesk stores inline images as <img> tags within the HTML body of conversation objects. The src attribute points to a Freshdesk-hosted URL. When you deactivate your Freshdesk instance, those URLs break, leaving your HelpDesk tickets littered with broken image icons.
To prevent this, your migration script must:
- Parse the HTML body of every conversation
- Extract all
<img src="...">URLs pointing to Freshdesk domains (e.g.,*.freshdesk.com,*.freshservice.com,s3.amazonaws.compaths containing your Freshdesk subdomain) - Download each image during the extraction phase
- Upload to HelpDesk as attachments during the import phase
- Rewrite the
<img>tags with the new HelpDesk URLs before pushing the payload
This step is easy to skip and expensive to fix after Freshdesk is decommissioned.
Step 6: Import Tickets into HelpDesk
Use the POST /v1/importedTickets endpoint — HelpDesk's dedicated migration endpoint. Unlike the standard POST /v1/tickets, this endpoint accepts historical events with custom timestamps, preserving original creation dates and chronology.
Below is a complete example payload structure with all primary fields populated:
import requests
import time
HD_BASE = "https://api.helpdesk.com/v1"
HD_AUTH = ("your_account_id", "your_pat_token")
def import_ticket(ticket_data, events, team_id, tag_ids, agent_map, transaction_id=None, attachment_ids=None):
payload = {
"subject": ticket_data["subject"],
"teamIDs": [team_id],
"status": map_status(ticket_data["status"]),
"priority": map_priority(ticket_data["priority"]),
"requester": {
"email": ticket_data["requester"]["email"],
"name": ticket_data["requester"]["name"]
},
"tagIDs": tag_ids,
"events": events
}
# Include assignment only if agent exists in HelpDesk
if ticket_data.get("responder_id"):
hd_agent_id = agent_map.get(ticket_data["responder_id"])
if hd_agent_id:
payload["assignment"] = {
"team": {"ID": team_id},
"agent": {"ID": hd_agent_id}
}
# Include transaction and attachments if present
if transaction_id:
payload["transactionID"] = transaction_id
if attachment_ids:
# Attach to the relevant event(s) by index
payload["events"][0]["attachmentIDs"] = attachment_ids
max_retries = 5
base_delay = 10
for attempt in range(max_retries):
resp = requests.post(
f"{HD_BASE}/importedTickets",
json=payload,
auth=HD_AUTH,
headers={"User-Agent": "FreshdeskMigration/1.0"}
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", base_delay * (2 ** attempt)))
time.sleep(retry_after)
continue
if resp.status_code == 400:
# Log and skip — do not retry schema violations
return {"error": resp.json(), "freshdesk_id": ticket_data["id"], "status": "failed"}
resp.raise_for_status()
return {"helpdesk_id": resp.json().get("ID"), "freshdesk_id": ticket_data["id"], "status": "success"}
raise RuntimeError(f"Exceeded max retries for ticket {ticket_data['id']}")HelpDesk advises always including the User-Agent header in API requests. Requests missing it might be blocked by intermediary services.
Idempotency and crash recovery: The importedTickets endpoint is not inherently idempotent — submitting the same ticket twice creates two records. Your staging database's source-to-target ID map is your idempotency layer. Before calling POST /v1/importedTickets for any ticket, check whether a helpdesk_ticket_id already exists for that freshdesk_ticket_id. If it does, skip the ticket. This means a migration that crashes at ticket 8,432 of 50,000 can resume from ticket 8,433 without duplicating records.
Execution strategy: Do not run the final import in one massive batch.
- Sandbox test: Push 100 random tickets to a HelpDesk trial account, including your largest tickets (most conversations, most attachments) and edge cases (long CC chains, inline images, unusual custom field values). Verify HTML rendering, custom field mapping, and attachment integrity before touching production.
- Historical load: Migrate all closed and resolved tickets first. This data is static and can be loaded days before go-live with no risk of staleness.
- Delta sync: On go-live day, query Freshdesk for tickets updated since the historical load began. Use the
updated_sinceparameter filtered to the extraction start date. Transform and push only the delta. Your staging ID map will skip anything already imported. - Keep Freshdesk read-only until stakeholders sign off on validation.
Step 7: Validate the Migration
Post-migration validation is non-negotiable.
Quantitative checks:
- Total ticket count: source count in Freshdesk matches target count in HelpDesk across all statuses
- Conversation/event count per ticket: pull a sample of 200 tickets and compare thread length in Freshdesk to event count in HelpDesk
- Failed ticket log: review every record with
status: failedin your staging database
Qualitative spot checks:
- Select 50 random tickets across different statuses, assigned agents, ages, and ticket types
- Compare the Freshdesk UI side-by-side with HelpDesk for subject, body, conversation thread, attachments, tags, and custom field values
- Verify private notes did not become public replies
- Download a sample of attachments from HelpDesk to confirm file accessibility and integrity
- Confirm timestamps preserved original chronology
- Check at least 10 tickets with inline images to verify URLs resolve
Custom field truncation: The singleLine 120-character limit will silently truncate values longer than that. Query your staging database for any transformed custom field values that exceeded 120 characters and verify the truncation strategy produced acceptable output.
Step 8: Post-Cutover Integration Remapping
Migration does not end when the last ticket imports. Every external system that touched Freshdesk needs to be redirected.
Webhooks: Freshdesk supports outbound webhooks on ticket events. Catalog every webhook endpoint URL from Freshdesk's admin panel. Recreate equivalent webhooks in HelpDesk pointing to the same downstream systems (CRM, billing, Slack, BI pipelines). Test each one before decommissioning Freshdesk.
API integrations: Any internal tool that called the Freshdesk API (reporting scripts, CRM sync, agent toolbars) must be updated to call the HelpDesk API. The data models differ significantly — a script that parsed Freshdesk ticket JSON will not work unchanged against HelpDesk's response schema.
Email routing: Freshdesk captures support email by forwarding to a Freshdesk-provided address or via SMTP ingestion. Update MX records or email forwarding rules to route incoming support emails to HelpDesk's inbound address before decommissioning Freshdesk.
LiveChat widget: Deploy the LiveChat widget to your website and product surfaces. Configure routing rules, operating hours, and canned responses. This is independent of the HelpDesk migration but must be completed before telling customers the new channel is live.
Agent training: HelpDesk's UI and keyboard shortcuts differ from Freshdesk's. Agents who primarily worked tickets (not chat) need orientation on HelpDesk. Agents handling real-time chat need LiveChat training. Plan this alongside the technical cutover, not after.
Data That Will Not Survive the Migration
Be explicit with stakeholders about what gets lost before the migration starts, not after:
- Companies/Organizations: HelpDesk has no company object. Contact-to-company relationships are gone. Store company names in tags or custom fields if needed.
- SLA data: Freshdesk SLA timers, breach history, and policy configurations have no target in HelpDesk.
- Ticket activities/timeline: Freshdesk logs status changes, field edits, and agent actions as activities. Only message-type events import into HelpDesk. The audit trail of "who changed what and when" does not transfer.
- Satisfaction survey responses: Freshdesk CSAT scores use numeric scales (1–7 or 1–10 depending on configuration). HelpDesk uses three discrete values:
good,neutral,bad. The scale change makes historical CSAT data non-comparable. - Automations and triggers: Must be manually recreated in HelpDesk's rule system, which uses a different trigger/action model.
- Knowledge base articles: No import API. Recreate manually or use a separate KB product.
- Time tracking entries: No equivalent in HelpDesk.
- Forum discussions: HelpDesk has no community/forum feature.
- Custom field types: Dropdowns, checkboxes, dependent fields, and nested fields flatten to strings. Data is preserved; structure is not.
- CC chain history: CC recipients on historical tickets may not be preserved in the
importedTicketsflow.
Edge Cases That Break Migrations
Even a well-built migration script will hit these:
- Archived tickets: CSV exports don't include full conversation histories or archived tickets. Archived tickets are also excluded from the standard
GET /api/v2/ticketsendpoint. Use the Account Export API orupdated_sincewindowing to reach them. - Tickets with 50+ conversations: Some tickets accumulate dozens of replies and notes over months. Test your largest tickets first — not just happy-path records — to confirm HelpDesk's payload size limits are not exceeded.
- Inline images: Freshdesk stores inline images as embedded URLs in HTML conversation bodies. These URLs expire or require authentication. Download and re-upload during extraction (see Step 5).
- CC recipients: HelpDesk supports CC on tickets, but the
importedTicketsendpoint does not include a CC field in the documented schema. Add CC'd contacts post-import viaPATCH /v1/tickets/{ticketID}. - Message size limits: Freshdesk allows exceptionally long HTML bodies (automated server logs, for example). HelpDesk enforces character limits on individual events. Truncate long messages at a safe threshold or convert them to attached text files, appending a note to the event:
[Full content in attached file: ticket-12345-body.txt]. - Unsupported file types: Catch upload rejections and append a note to the ticket indicating which file was stripped and why.
- Multi-product Freshdesk: If you run multiple products in Freshdesk (each with separate portals), each product's ticket pool needs its own team mapping in HelpDesk. Map the full group-to-team topology before starting and validate it with test imports from each product.
- Freshchat data: If you used Freshdesk's live chat (Freshchat), those conversations live in a separate system with their own API and do not come through the Freshdesk Tickets API. LiveChat's Agent Chat API v3.5 does not have a bulk historical chat import endpoint — HelpDesk's
importedTicketsendpoint is the closest available landing spot for Freshchat history that needs to be preserved. - Transaction expiry under slow imports: If your migration slows (due to rate limiting, large file downloads, or errors), transaction IDs created more than 24 hours before use will be rejected. Build transaction creation into the per-ticket loop, not as a pre-flight batch step.
Migration Timeline Estimates
The following estimates assume a single migration worker, no concurrent API consumers on either platform, and that attachments average 2 files per ticket. The math: each ticket with 2 attachments = 1 transaction + 2 uploads + 1 import = 4 HelpDesk calls, plus 1 Freshdesk ticket call + 1 Freshdesk conversation call = 6 total calls per ticket.
| Scenario | Freshdesk Plan (API limit) | HelpDesk calls needed | Approx. time |
|---|---|---|---|
| 5,000 tickets, no attachments | Growth (200/min) | 10,000 | 1–2 hours |
| 5,000 tickets, avg 2 attachments | Growth (200/min) | 20,000 + download time | 4–6 hours |
| 20,000 tickets, no attachments | Pro (400/min) | 40,000 | 2–3 hours |
| 20,000 tickets, avg 2 attachments | Pro (400/min) | 80,000 + download time | 8–12 hours |
| 50,000+ tickets, avg 2 attachments | Enterprise (700/min) | 200,000+ + download time | 24–48+ hours |
The HelpDesk 1,000 req/10-min limit (≈100/min) is the effective bottleneck in most scenarios, not the Freshdesk read limit. Download bandwidth for attachments is an additional variable not captured in the rate limit math. Run a 500-ticket timed test before committing to a go-live window — actual throughput often differs from theoretical maximums due to network latency and payload processing time.
When to Skip the Migration
Not every migration is worth the engineering investment:
- Under 1,000 mostly-closed tickets: The scripting effort exceeds the value of the historical data. Export to CSV for archival and start fresh in HelpDesk.
- Heavy reliance on Freshdesk automations, SLAs, or multi-product routing: HelpDesk does not replicate these features. Migrating data without migrating workflows creates a broken experience.
- Extensive knowledge base: HelpDesk has no KB import API. If your KB is a critical operational asset (not just supplemental documentation), plan a separate workstream or evaluate a dedicated KB tool before committing to this migration path.
- Freshchat-heavy operations: If the majority of your customer interactions were through Freshchat rather than email tickets, the Freshdesk-to-HelpDesk migration captures only a fraction of your history. Evaluate whether that fraction justifies the migration effort.
Making It Work
Freshdesk-to-LiveChat migration is straightforward in concept — extract, transform, load — but the details are demanding. The mismatch between Freshdesk's rich helpdesk data model and HelpDesk's leaner ticket-plus-events model means some data loss is inevitable. The key is knowing exactly what will be lost before you start, getting stakeholder sign-off on those trade-offs, validating every batch after import, and having a crash recovery strategy before the first ticket is pushed.
The same split-target pattern appears in adjacent migrations like Freshservice to LiveChat, Zendesk to LiveChat, and HappyFox to LiveChat. The extraction side will feel familiar if you have done helpdesk migrations before. The import side is where HelpDesk's API shows its constraints — four custom field types, transaction-based attachments, a shared rate limit that punishes concurrent operations, and an invitation-based agent model that creates ghost records if you move too fast.
For teams running this independently: start small, validate continuously, keep your extracted data in a persistent staging store, and do not decommission Freshdesk until every downstream integration has been tested against HelpDesk.
Frequently Asked Questions
- Can I migrate Freshdesk tickets directly into LiveChat?
- No. LiveChat's native ticketing was sunset in January 2025. Historical ticket data migrates to HelpDesk (helpdesk.com), a separate product by Text, using the POST /v1/importedTickets endpoint. LiveChat handles only real-time chats and does not expose a documented bulk historical chat import endpoint.
- What Freshdesk data can't be migrated to HelpDesk?
- Companies/organizations, SLA policies, satisfaction survey scores (different rating model), knowledge base articles, time tracking entries, forum discussions, and automation rules all have no direct import path. Custom field types like dropdowns and checkboxes get flattened to strings.
- How do I bypass the Freshdesk 300-page API limit?
- Use time-based chunking with the updated_since parameter to reset the 300-page cap for each date window, or use Freshdesk's Account Export API (POST /api/v2/account/export) which can include both active and archived tickets in a single job.
- How long does a Freshdesk to HelpDesk migration take?
- Depends on ticket volume and attachments. A 5,000-ticket migration without attachments on a Growth plan takes 2–4 hours. With attachments, expect 6–10 hours. 50,000+ ticket migrations can take 24–48 hours due to rate limits on both sides.
- What happens to inline images during the migration?
- Inline images will break unless your migration script parses the HTML body of each conversation, downloads the images from Freshdesk's servers, uploads them to HelpDesk, and rewrites the image URLs in the payload before import.