Skip to content

Freshdesk to Zendesk Migration: API Limits, Mapping & Methods

A step-by-step technical guide to migrating from Freshdesk to Zendesk — covering API rate limits, field mapping, automation translation, inline image re-hosting, delta sync, sandbox testing, rollback procedures, QA checklists, and cutover planning for operations leads and support managers.

Raaj Raaj · · 32 min read
Freshdesk to Zendesk Migration: API Limits, Mapping & Methods
TALK TO AN ENGINEER

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

Migrating from Freshdesk to Zendesk is a data-model translation problem, not a drag-and-drop. Every migration carries a hidden risk: the silent loss of historical context. It rarely looks like a total failure — it looks like a successful import where 10% of your conversation threads, inline images, or attachment histories are quietly missing.

Freshdesk organizes support around contacts and tickets in a relatively flat hierarchy. Zendesk organizes everything around users, organizations, and tickets — a layered model where every record relates to others through explicit ID references. Every contact, conversation, attachment, and custom field value must be extracted from one structure and written into the other. The structural gaps between them are where data silently disappears.

This guide covers the exact API constraints on both sides, complete field-mapping tables, attachment and inline-image handling, automation translation examples, rollback and QA procedures, and a direct comparison of migration methods — so you can pick the approach that matches your volume, timeline, and risk tolerance.

For a deeper look at exporting your data before migration, see How to Export Data from Freshdesk: Methods, API Limits & Mapping. If identity mapping is the riskiest part of your account, read How to Migrate Users & Organizations Without Breaking History.

Freshdesk to Zendesk Migration: Why the Data Model Matters

Freshdesk's data model is contact-centric and flat. A contact submits a ticket. That ticket has conversations (replies, notes, forwards), each with optional attachments. Contacts can belong to companies, but companies are loosely coupled — they exist mainly for grouping and reporting. There is no mandatory hierarchy between contacts, companies, and tickets.

Zendesk's data model is ticket-centric and layered. A ticket has a requester (user), belongs to an organization, is assigned to a group and an agent, and contains a flat list of comments — each marked public or private. Organizations in Zendesk are first-class objects with their own domain mapping, shared ticket access rules, and tag inheritance. Users must exist before tickets can reference them. (developers.freshdesk.com)

This structural mismatch creates three problems every migration must solve:

1. Contact → User + Organization split. A single Freshdesk contact with a company association must be written as a Zendesk user and an organization membership. If you skip the organization step, you lose the company association — and any Zendesk automations built around organizations will not work. Freshdesk's view_all_tickets permission on contacts does not have a direct Zendesk equivalent; Zendesk handles organization-level ticket visibility through user restrictions and organization-level settings, not a single boolean.

2. Conversation → Comment translation. Freshdesk conversations have discrete types: replies, notes, and forwards. Zendesk comments are a flat list with a public boolean. Replies map to public comments; notes map to private comments. Forward threads have no direct equivalent — they must be appended as private notes or the context is lost.

3. Status mapping is not 1:1. Freshdesk uses four statuses by default (Open, Pending, Resolved, Closed) plus custom statuses. Zendesk uses six (New, Open, Pending, On-hold, Solved, Closed). You need an explicit mapping table before you write a single line of migration code.

Practical rule: create Zendesk organizations first, then users, then tickets. If you import tickets before the user and organization membership exist, you lose clean account context in the target. This import order matters even more if Freshdesk relied on view_all_tickets, because Zendesk handles that visibility through a combination of user restrictions and organization-level settings rather than one boolean on the contact. (developers.freshdesk.com)

For a detailed walkthrough on handling the user/organization relationship, see How to Migrate Users & Organizations Without Breaking History.

Pre-Migration Checklist

Before writing a single line of migration code, work through this checklist. Each item is a prerequisite that, if skipped, causes problems that are harder to fix after data has started moving.

  1. Confirm Freshdesk plan tier and current API usage. Check your plan's rate limit (Growth: 200/min, Pro: 400/min, Enterprise: 700/min). Review active marketplace apps and webhook integrations that share your rate-limit budget. Decide whether to pause non-critical integrations during the export window.
  2. Confirm Zendesk plan tier and API capacity. Determine whether you need the High Volume API add-on for large migrations. Verify sandbox access (Enterprise plans include a sandbox; other tiers require a trial instance).
  3. Audit Freshdesk data scope. Run counts: total tickets (including archived), total contacts, total companies, total agents, total custom fields, total tags. Identify tickets with more than 10 conversations (these require separate conversation-fetch calls). Identify tickets with attachments over 15 MB.
  4. Build field mapping tables. Map every Freshdesk field to its Zendesk equivalent using the reference tables in this guide. Pay special attention to custom statuses, custom dropdown values, other_emails, and product_id.
  5. Document automation translation plan. Inventory every Freshdesk automation rule, dispatch rule, scenario automation, and canned response. Document how each one will be rebuilt in Zendesk (see the automation translation section below).
  6. Provision Zendesk sandbox or trial instance. Set up a non-production Zendesk environment for test imports. Create matching groups, custom fields, and organization settings.
  7. Disable Zendesk triggers and automations in the target. Prevent imported tickets from firing welcome emails, auto-replies, or assignment rules. Disable the agent welcome email before user import.
  8. Reduce DNS TTL. If you are changing MX records or portal DNS as part of cutover, reduce TTL to 300 seconds at least 48 hours before cutover day.
  9. Define rollback criteria. Document what constitutes a failed migration and who has authority to call a rollback. See the rollback section below.
  10. Schedule agent training. Block time for your support team to learn Zendesk's interface, trigger logic, and macro system before cutover — not after.
  11. Get stakeholder sign-off. Confirm that the support manager, IT lead, and any compliance stakeholders have approved the migration plan, timeline, and acceptable data-loss thresholds.
  12. Document the rollback plan. Write down the specific steps to revert to Freshdesk if the migration fails. Distribute to all stakeholders before the bulk import begins.

Freshdesk Export Constraints: API Rate Limits and Extraction Traps

Freshdesk's v2 REST API is the only reliable way to extract tickets with full conversation history, attachments, and custom field values. The built-in CSV export does not include conversation content or archived tickets. (developers.freshdesk.com)

Rate limits by plan

Freshdesk enforces per-minute, account-wide rate limits based on your plan tier:

Freshdesk Plan Rate Limit (requests/min)
Trial 50
Growth 200
Pro 400
Enterprise 700

These limits are account-wide — every app, integration, webhook handler, and migration script shares the same pool. If your production Freshdesk instance is running marketplace apps or automation integrations during the export, those requests eat into your migration budget. Endpoint-specific caps on Tickets List and Contacts List can reduce practical throughput further, and include=requester or include=description consumes extra credits per call. (support.freshdesk.com)

Danger

Do not test migration scripts on a Freshdesk trial account. Trial accounts are hard-capped at 50 requests per minute. A migration script that works fine in dev will hit hundreds of 429 errors at that rate, giving you a false picture of how the production run will behave. Always test against a paid sandbox or request a temporary rate limit increase from Freshdesk support.

The 30-day default trap

By default, GET /api/v2/tickets only returns tickets created in the last 30 days. For older data you must use the updated_since parameter. Even then, the endpoint is capped at 300 pages. Pagination defaults to 30 records per page and tops out at 100 per page. (developers.freshdesk.com)

The filter/search endpoint (/api/v2/search/tickets) has a harder limit: it returns a maximum of 30 results per page and caps at 10 pages — a hard ceiling of 300 tickets per search query. To work around this, split searches by date range or other criteria to keep result sets under 300.

Warning

Do not rely on one GET /tickets loop for a historical export. Segment by updated_since, and plan a separate conversation pass for long tickets. If you need archived tickets or a broader system snapshot, use the account export job instead of trying to coerce everything through the list endpoint.

Conversation pagination

View a Ticket?include=conversations only returns up to ten conversations. Tickets with longer threads require a separate call to the List All Conversations endpoint. This means ticket-count parity alone is not enough — you need conversation-count parity and attachment-count parity too. (developers.freshdesk.com)

Attachment URL expiry

Freshdesk attachment URLs returned by the API are time-limited. Community reports indicate attachment download URLs can expire in as little as 5 minutes. Your migration script must download each attachment immediately after fetching the conversation — not queue them for later batch download.

Alternative export methods

Beyond the paginated API, Freshdesk offers two other extraction paths:

  • Contact/company export jobs return downloadable CSVs. These are async — only one export job can run at a time per account, and download URLs expire after 15 days. Useful for flat bulk pulls of identity data.
  • Account export jobs produce a broader JSON snapshot that can include tickets, archived tickets, contacts, companies, groups, canned responses, surveys, and more. Use this when you need archived tickets or a configuration snapshot alongside the live API extraction.

(developers.freshdesk.com)

Zendesk Import Limits and Handling 429 Errors

The Zendesk side has its own rate constraints. Understanding these before you start writing import logic will save you days of debugging.

Rate limits by plan

Zendesk Suite Plan Rate Limit (requests/min)
Team 200
Growth 400
Professional 400
Enterprise 700
Enterprise Plus 2,500

The High Volume API add-on raises the limit to 2,500 requests/min on Growth plans and above (minimum 10 agent seats required). If you are running a large migration (100k+ tickets), purchasing this add-on for the migration window is often worth the cost. (developer.zendesk.com)

Endpoint-specific limits

Some Zendesk endpoints have their own rate limits that override the account-wide number:

  • Incremental Exports: 10 requests/min (30 with High Volume add-on)
  • Update Ticket: 30 updates per 10 minutes per user per ticket
  • Bulk User Import: up to 100 users per request
  • Bulk Organization Create: up to 100 organizations per request via create_many
  • Ticket Bulk Import: up to 100 tickets per request via /api/v2/imports/tickets/create_many
  • Queued background jobs: maximum 30 concurrent jobs

The Ticket Import API

Zendesk's Ticket Import API (POST /api/v2/imports/tickets) is purpose-built for migrations. Unlike the standard Tickets API, it lets you:

  • Set historical timestamps: created_at, updated_at, solved_at
  • Set created_at on individual comments to preserve conversation chronology
  • Import tickets directly into the archive using archive_immediately=true (recommended for 750,000+ historical tickets)
  • Suppress trigger execution on imported tickets
  • Suppress notification emails to CC'd users

(developer.zendesk.com)

// Zendesk Ticket Import payload example
{
  "ticket": {
    "assignee_id": 12345678,
    "requester_id": 87654321,
    "subject": "Historical Freshdesk Ticket",
    "tags": ["migrated_from_freshdesk"],
    "comments": [
      {
        "author_id": 87654321,
        "html_body": "<p>This is the original customer request.</p>",
        "created_at": "2023-10-12T08:22:11Z",
        "public": true
      }
    ],
    "created_at": "2023-10-12T08:22:11Z"
  }
}
Info

Imported tickets do not run triggers, and historical ticket metrics are not backfilled. Zendesk does not compute first reply time or first resolution time for imported tickets. Tag imported tickets (e.g., migrated_from_freshdesk) and exclude them from SLA reports. Plan your workflow cutover and reporting expectations separately from data loading. (support.zendesk.com)

Attachment workflow

Attachments are not embedded in the ticket import payload as raw blobs. Each attachment requires:

  1. A GET request to download from Freshdesk (with time-limited URLs)
  2. A POST request to upload to Zendesk (/api/v2/uploads) to get an upload token
  3. The returned upload token included in the comment payload

Upload tokens are time-limited — they expire at the time specified in the expires_at field. A ticket can contain at most 5,000 comments total. If you are migrating a Freshdesk ticket with thousands of updates, check comment counts before import rather than discovering the limit mid-job. (developer.zendesk.com)

For a 100,000-ticket migration where the average ticket has 2 attachments, that is 200,000 additional round-trips — on top of the ticket and conversation requests.

By default, Zendesk attachment URLs are tokenized but still accessible to anyone who has the URL. Teams with sensitive data should review private attachment settings before go-live.

Retry logic

When you hit the rate limit, Zendesk returns a 429 Too Many Requests response with a Retry-After header specifying the number of seconds to wait. Do not hard-code a fixed sleep interval — the Retry-After value tells you exactly how long the lockout lasts.

import requests
import time
 
def import_ticket(url, payload, auth, max_retries=5):
    for attempt in range(max_retries):
        response = requests.post(url, json=payload, auth=auth)
        if response.status_code == 429:
            wait = int(response.headers.get("Retry-After", 60))
            time.sleep(wait)
            continue
        response.raise_for_status()
        return response.json()
    raise Exception("Max retries exceeded")

Separate your exporter and importer queues. Freshdesk limits and Zendesk limits are independent, and a single shared worker pool makes it harder to identify which side is actually bottlenecking.

Freshdesk to Zendesk Data Mapping: Complete Field Reference

The tables below map Freshdesk objects and fields to their Zendesk equivalents. This is the mapping we use on every Freshdesk-to-Zendesk engagement, refined over dozens of migrations.

Object-level mapping

Freshdesk Object Zendesk Object Notes
Contact User Email is the unique key. Import users before tickets. Handle other_emails as additional identities if reply-by-email continuity matters.
Company Organization Map company_idorganization_id. Domain mapping optional.
Agent User (role: agent) Agents must exist in Zendesk before ticket assignment.
Group Group Recreate groups manually or via API before import.
Ticket Ticket (via Import API) Use /api/v2/imports/tickets to preserve timestamps.
Conversation (reply) Comment (public: true) Map body or body_text to comment html_body. Prefer html_body when formatting matters.
Conversation (note) Comment (public: false) Private notes → private comments.
Conversation (forward) Comment (public: false) No native equivalent. Append as private note with forward context.
view_all_tickets User restriction + org sharing Not 1:1. Test portal visibility after import.
product_id Brand, form, tag, or custom field This is a target design choice, not a platform default.
Solution Article Help Center Article Separate migration via Help Center API.

Ticket field mapping

Freshdesk Field Zendesk Field Transformation
id external_id Store Freshdesk ID as external_id for cross-referencing.
subject subject Direct map.
description First comment body Freshdesk description = first conversation entry.
status (2=Open, 3=Pending, 4=Resolved, 5=Closed) status (new, open, pending, hold, solved, closed) Requires explicit mapping table. See below.
priority (1=Low, 2=Medium, 3=High, 4=Urgent) priority (low, normal, high, urgent) 1:1 if you map Medium → Normal.
source (1=Email, 2=Portal, 3=Phone…) via.channel Zendesk tracks channel differently; set as tag if needed.
requester_id requester_id Must reference an existing Zendesk user ID.
responder_id assignee_id Agent assignment.
group_id group_id Must reference an existing Zendesk group ID.
company_id organization_id Mapped through the user's organization membership.
tags tags Direct map. Validate tag format (Zendesk: lowercase, no spaces).
custom_fields custom_fields Create matching fields in Zendesk first. Map by field ID.
created_at created_at Preserved via Import API.
updated_at updated_at Preserved via Import API.

The rows to watch most closely are other_emails, view_all_tickets, product_id, and any custom dropdown field with platform-specific option values. Freshdesk supports multiple contact emails, while Zendesk handles multiple user identities through separate identity logic. (developers.freshdesk.com)

Status mapping table

Freshdesk Status Value Zendesk Status Notes
Open 2 open
Pending 3 pending
Resolved 4 solved
Closed 5 closed
Custom statuses 6+ Custom status or tag Zendesk supports custom statuses on Enterprise+. Otherwise, map to closest standard status + a tag.
Tip

Zendesk tags cannot contain spaces or uppercase letters. Freshdesk allows both. Run a normalization pass on all tags before import: lowercase, replace spaces with underscores, strip special characters.

Translating Freshdesk Automations into Zendesk Triggers, Automations, and Macros

Freshdesk automations, dispatch rules, scenario automations, and canned responses do not transfer via API. They must be rebuilt in Zendesk — and the concepts do not map 1:1. Understanding the structural differences before you start rebuilding will save significant rework.

Key structural differences

  • Freshdesk Dispatch Rules run once on ticket creation and assign properties based on conditions. The closest Zendesk equivalent is a trigger with the condition "Ticket: Is" → "Created."
  • Freshdesk Automations run on a time schedule (e.g., "if ticket has been pending for 48 hours, do X"). These map to Zendesk automations (not triggers), which also execute on time-based conditions.
  • Freshdesk Scenario Automations let agents apply a bundle of actions with one click. Zendesk's equivalent is a macro — but macros in Zendesk are simpler and do not support conditional logic within the macro itself.
  • Freshdesk Canned Responses map to Zendesk macros that insert pre-written text into comments.
  • Freshdesk trigger execution order is based on rule ordering within the admin panel. Zendesk triggers also execute in order, but all matching triggers fire — Freshdesk stops at the first match for dispatch rules. This difference can cause unexpected behavior if you have overlapping conditions.
  • Zendesk does not have Freshdesk's priority-based execution model for automations. In Freshdesk, you can set automation priorities to control which rule fires first. In Zendesk, trigger ordering is strictly positional — drag rules into the correct sequence.
  • SLA policies differ. Freshdesk SLAs are configured per product and per priority. Zendesk SLA policies are ordered by priority (the list position), and the first matching policy applies. You may need multiple Zendesk SLA policies to replicate what one Freshdesk SLA configuration covered.

Worked translation examples

Example 1: Time-based follow-up

Freshdesk automation: "When ticket status is Pending for 48 hours, send a reminder email to the requester."

Zendesk equivalent: Create a Zendesk automation (Admin → Business Rules → Automations) with:

  • Condition: "Hours since status category Pending" → "Is" → "48"
  • Action: "Notification: Email user" → "(requester)" with your reminder template

Note: Zendesk automations run hourly, so the actual execution may be up to 49 hours after status change, not exactly 48.

Example 2: Dispatch rule for ticket assignment

Freshdesk dispatch rule: "If ticket subject contains 'billing', assign to Billing group and set priority to High."

Zendesk equivalent: Create a Zendesk trigger (Admin → Business Rules → Triggers) with:

  • Conditions (all): "Ticket: Is" → "Created" AND "Subject text" → "Contains the following string" → "billing"
  • Actions: "Group" → "Billing" AND "Priority" → "High"

Remember that Zendesk triggers fire on every update that matches, not just creation, unless you include the "Ticket: Is Created" condition explicitly.

Example 3: Canned response to macro

Freshdesk canned response: A saved reply template named "Refund Confirmation" that inserts a formatted HTML response with placeholders for customer name and order number.

Zendesk equivalent: Create a Zendesk macro with:

  • Action: "Comment/description" → your HTML template using Zendesk placeholders ({{ticket.requester.name}}, plus a custom field for order number if needed)
  • Action: "Status" → any status change you want bundled with the response

Freshdesk placeholders like {{ticket.requester_name}} need to be rewritten to Zendesk's placeholder syntax ({{ticket.requester.name}}). Map every placeholder individually — the syntax is different and Zendesk will render unrecognized placeholders as literal text.

Example 4: Scenario automation to macro

Freshdesk scenario automation: "Escalate to Manager" — sets priority to Urgent, assigns to Managers group, adds tag escalated, and posts a private note saying "Escalated per policy."

Zendesk equivalent: Create a Zendesk macro with multiple actions:

  • "Priority" → "Urgent"
  • "Group" → "Managers"
  • "Add tags" → "escalated"
  • "Comment/description" → "Escalated per policy." with "Comment mode" → "Private"

Zendesk macros cannot include conditional logic ("if priority is already Urgent, skip"). If your Freshdesk scenario automations included conditional branches, you will need separate macros for each branch or accept the simplification.

For a deeper walkthrough on automation, macro, and workflow migration, see Your Help Desk Data Migration's Secret Saboteur: Automations, Macros, and Workflows.

What Breaks: Inline Images, Attachments, and Custom Fields

This is where most migrations silently lose data. The ticket count matches, but the content inside does not.

Inline images

Freshdesk stores inline images (screenshots pasted into ticket replies) as embedded references within the HTML body of a conversation. When you extract that HTML via the API, the <img> tags point to Freshdesk-hosted URLs.

If you import that HTML directly into Zendesk, the images may render initially — until Freshdesk rotates or expires those URLs. Then every historical ticket shows broken image placeholders.

The correct approach: download every inline image during export, re-upload it to Zendesk via the Uploads API, get the new Zendesk URL, and rewrite the <img src> in the HTML body before importing the comment. This is tedious, API-intensive, and the step that most automated tools skip.

Zendesk treats inline images distinctly from regular attachments — the comment listing API has a dedicated include_inline_images flag. Attachment preservation and inline-image preservation are not the same test. Run a sample migration with tickets that contain pasted screenshots inside the conversation body, not just files attached at the bottom of the thread. (developer.zendesk.com)

Attachment size limits and transfer cost

Freshdesk allows attachments up to 20 MB per conversation on paid plans (15 MB on trial/Sprout). Zendesk's API allows attachment uploads up to 50 MB per file, so the size ceiling is not the problem on the Zendesk side.

The problem is the transfer pipeline. Each attachment requires a download from Freshdesk, an upload to Zendesk, and the token attached to the comment — three API calls per file, consuming rate-limit budget on both sides. Many automated SaaS migration tools silently skip attachments over 15–20 MB to save bandwidth and processing time. If your team relies on log files, large PDFs, or video recordings, verify the payload limits of your migration method before committing.

Custom fields

Freshdesk custom fields use a naming convention (cf_fieldname) while Zendesk custom fields are referenced by numeric ID. Before import:

  1. Create every custom field in Zendesk manually or via the Ticket Fields API
  2. Record the Zendesk field ID for each
  3. Build a lookup map: Freshdesk cf_* name → Zendesk field ID
  4. Transform every ticket's custom field values during import

Dropdown fields need special attention. Freshdesk stores the display value; Zendesk stores a tag-formatted value. If your Freshdesk dropdown has "Hardware Issue" as an option, Zendesk needs hardware_issue. Miss this transformation and the import silently drops the value.

Comment body formatting

Freshdesk conversations expose HTML bodies. Zendesk imported comments accept value, body, or html_body. If you post plain body, Zendesk collapses repeated spaces and newlines. If the original Freshdesk thread used rich HTML or pasted troubleshooting steps, flattening to plain text changes what agents see. Always use html_body when the source conversation contains formatting. (developers.freshdesk.com)

What else gets lost

Beyond attachments and fields, watch for these commonly dropped items:

  • Satisfaction ratings — Freshdesk CSAT data does not map to Zendesk satisfaction ratings automatically. You can store ratings as tags or custom field values.
  • Time tracking entries — Freshdesk's time entries are separate API objects. Zendesk tracks time differently. Manual mapping required.
  • Forward conversations — No native Zendesk equivalent. Must be appended as private notes with context preserved.
  • Automations, macros, and workflows — These do not transfer via API. They must be rebuilt in Zendesk from scratch. See the automation translation section above and Your Help Desk Data Migration's Secret Saboteur: Automations, Macros, and Workflows.

Risks and Mitigations

Every Freshdesk-to-Zendesk migration carries specific risks. The warnings are distributed throughout this guide, but operations leads presenting a migration plan to leadership need them in one place.

Risk Likelihood Impact Mitigation
Inline images break post-migration (Freshdesk URLs expire) High High — agents see broken images on historical tickets Re-host every inline image via Zendesk Uploads API during import; verify with sample migration
Attachments over 15 MB silently skipped by SaaS tools Medium Medium — loss of log files, PDFs, recordings Verify attachment size handling before committing to a method; use custom scripts for large attachments
Conversation threads truncated (10-conversation include limit) High High — incomplete ticket history Use separate List All Conversations endpoint for tickets with 10+ conversations; verify conversation-count parity
Custom dropdown values silently dropped High Medium — loss of categorization data Transform display values to tag-format during import; verify a sample before bulk run
30-day default returns only recent tickets High Critical — missing historical data Use updated_since parameter; segment by date range; use account export for archived tickets
Rate limit exhaustion stalls migration Medium Medium — extended migration window Monitor X-RateLimit-Remaining headers; throttle proactively; consider High Volume API add-on
Freshdesk attachment URLs expire before download Medium High — permanent attachment loss Download attachments immediately after fetching conversations; do not queue for batch download
Zendesk rejects tickets with suspended/deleted requesters Medium Medium — failed imports for affected tickets Audit requester status before import; resolve suspended users in pre-migration phase
Automation logic differences cause unexpected behavior High Medium — tickets misrouted or double-actioned post-cutover Disable all triggers during import; rebuild and test automations in sandbox before enabling
view_all_tickets visibility breaks in Zendesk Medium Medium — customers lose access to shared tickets Test portal visibility after import; configure organization-level sharing settings explicitly
Tag format mismatch (spaces, uppercase) High Low — tags silently dropped or malformed Run normalization pass: lowercase, replace spaces with underscores, strip special characters
SLA metrics not backfilled on imported tickets Certain Low — reporting gaps for historical tickets Tag imported tickets; exclude from SLA reports; set reporting expectations before cutover

Migration Methods: Native CSV vs. SaaS Tools vs. Engineer-Led

There are three real options. Each has hard trade-offs.

Method 1: Zendesk CSV Import

Zendesk allows bulk importing users and organizations via CSV through the admin UI. CSV files cannot exceed 1 GB, and Zendesk recommends up to 500,000 rows. You can also create tickets via CSV, but with severe limitations.

What it cannot do:

  • No conversation history — CSV import creates tickets with a single description, not multi-comment threads
  • No attachments
  • No timestamp preservation (tickets get current timestamps)
  • No custom field mapping beyond basic text fields

Verdict: Useful for importing a user list or a small set of tickets where history does not matter. Not a viable path for a full historical migration. If you try to force historical comments and attachments through a spreadsheet-shaped process, you will end up rebuilding an API pipeline anyway. (support.zendesk.com)

Method 2: SaaS Migration Tools

Self-service tools (like Help Desk Data Migration) provide a web-based wizard that connects to both platforms and transfers data automatically.

Strengths:

  • Low setup effort — connect API keys, map fields, run
  • Handles basic ticket + conversation + user migration
  • Pricing based on ticket volume

Limitations:

  • Inline images are frequently stripped or broken — most tools import the HTML body but do not re-host embedded images
  • Large attachments (over 15–20 MB) may be skipped silently
  • Custom field mapping is limited to what the tool's UI supports
  • Rate limit handling is opaque — you cannot control retry logic or concurrency
  • Forward conversations and side notes may be dropped or merged incorrectly
  • No ability to handle edge cases (suspended users, empty comment bodies, custom status mapping)

These tools are usually best when your mapping stays close to the default. Once you start hitting edge cases, you are at the mercy of the vendor's support team rather than your own code.

Verdict: Reasonable for small-to-mid migrations (under 50,000 tickets) where you can accept some data loss in edge cases and have time to QA manually.

Method 3: Engineer-Led Custom Migration

A dedicated engineering team writes custom scripts that handle the full export → transform → import pipeline.

Strengths:

  • Full control over every field mapping, transformation, and edge case
  • Inline images re-hosted and rewritten in HTML bodies
  • Attachment pipeline handles rate limits and retry logic on both sides
  • Custom status, custom field, and tag normalization built into the script
  • Can run incremental syncs to minimize cutover downtime

Limitations:

  • Requires significant engineering time (typically 2–6 weeks for DIY)
  • Must build and maintain retry logic, error handling, logging, and validation
  • API rate limits still apply — script must be tuned for your specific plan tiers

Be honest about what "full control" means if you go DIY. It means owning Freshdesk windowing, conversation fan-out, upload-token expiration, 429 handling on both sides, idempotency, audit logs, and rollback plans. If your team can do that well, DIY works. If not, DIY often becomes delayed outsourcing after the hard bugs surface.

Verdict: The only reliable method for migrations over 50,000 tickets, migrations with heavy attachment loads, or migrations where losing inline images or conversation context is not acceptable.

Criteria CSV Import SaaS Tool Engineer-Led
Conversation history ✅ (partial) ✅ (full)
Inline image preservation ❌ (usually)
Attachment fidelity ⚠️ (size limits)
Timestamp preservation
Custom field mapping ⚠️ (limited)
Rate limit handling N/A Opaque Full control
Downtime required High Medium Minimal
Cost Free $ per ticket $$ fixed

Delta Sync and Zero-Downtime Cutover

A migration that requires you to freeze support operations while data moves is a migration that costs you customer trust. The goal is to keep your team working in Freshdesk during the bulk import and cut over with minimal disruption.

How delta sync works

The bulk import handles the majority of historical data — every ticket created before the migration window. But your support team keeps working in Freshdesk during that import, which means new tickets are created and existing tickets are updated.

Delta sync captures those changes. The technical mechanism:

  1. Record the bulk import start time as your high-water mark.
  2. After the bulk import completes, query Freshdesk for all tickets with updated_since set to that high-water mark. This returns every ticket created or modified since the bulk run started.
  3. For each ticket in the delta set, check whether it already exists in Zendesk by looking up its external_id (the original Freshdesk ticket ID). If it exists, update it. If it does not, create it via the Import API.
  4. Run the delta sync again if the gap between the first delta run and cutover is large enough that more tickets were modified. In practice, most teams run 2–3 delta passes before the final cutover.

Idempotency and conflict resolution

The external_id field is your idempotency key. Before importing any ticket, look it up in Zendesk via GET /api/v2/search.json?query=type:ticket external_id:{freshdesk_id}. If it already exists, decide whether to update or skip.

Conflict resolution for tickets modified in both systems during the migration window is rare but possible if agents start working in Zendesk before the formal cutover. The simplest policy: Freshdesk is the system of record until the cutover moment. Any edits made in Zendesk during the migration window are treated as disposable test data.

Zero-downtime cutover sequence

The cutover itself should be a coordinated, time-boxed event. Here is the sequence:

  1. 48 hours before cutover: Reduce DNS TTL for your support portal domain and any MX records to 300 seconds. This ensures DNS changes propagate quickly on cutover day.
  2. Cutover day, T-2 hours: Run the final delta sync from Freshdesk to Zendesk.
  3. T-1 hour: Announce to agents that Freshdesk is entering read-only mode. Agents should finish active conversations and not pick up new tickets.
  4. T-0 (cutover moment): Update MX records to point to Zendesk. Update email forwarding rules to route incoming support email to Zendesk. Disable the Freshdesk customer portal (or redirect its DNS to Zendesk's Help Center URL).
  5. T+15 minutes: Run a final micro-delta sync to capture any tickets that arrived between the last delta run and the MX record switch. The email queue may still contain messages routed to Freshdesk during DNS propagation.
  6. T+30 minutes: Verify that new incoming emails are creating tickets in Zendesk. Have an agent send a test email and confirm it arrives.
  7. T+1 hour: Enable Zendesk triggers and automations. Brief agents that Zendesk is now the primary system.
  8. T+24 hours: Verify DNS propagation is complete. Monitor for straggler emails arriving at Freshdesk.
Warning

Do not disable Freshdesk immediately after cutover. Keep it running in read-only mode for at least 72 hours. Straggler emails from cached DNS records, webhook retries from third-party tools, and agent bookmarks pointing to old URLs will all surface during this window.

For the full go-live sequence, see Zero-Downtime Help Desk Data Migration: How to Keep Support Running During the Move.

Sandbox and Sample Migration

Running a sample migration against a non-production Zendesk instance is not optional. It is how you catch field mapping errors, attachment failures, and automation conflicts before they affect your live environment.

Setting up a Zendesk sandbox

Zendesk Enterprise plans include a sandbox environment accessible from Admin Center → Sandbox. The sandbox mirrors your production configuration (triggers, automations, custom fields) and lets you import test data without affecting live operations.

If you are on a Zendesk Team, Growth, or Professional plan, you do not have access to the built-in sandbox. The workaround: create a free Zendesk trial instance. Trial instances have full API access and support the Ticket Import API. They are rate-limited to 200 requests/min, which is sufficient for sample migrations of a few hundred tickets.

Running a sample migration

  1. Select a representative sample of 100–500 tickets from Freshdesk. Include tickets with: long conversation threads (10+ replies), inline images, large attachments, custom field values, custom statuses, CC'd recipients, and private notes.
  2. Export the sample using the same scripts and export logic you plan to use for the full migration. Do not use a simplified export for testing — you need to test the actual pipeline.
  3. Import the sample into the sandbox or trial instance.
  4. Inspect the results manually. Open 20–50 imported tickets in the Zendesk agent UI and verify:
    • Conversation thread order is correct (oldest first)
    • Inline images render (not broken placeholders)
    • Attachments download successfully
    • Private notes are marked private
    • Custom field values are populated correctly
    • Tags are present and correctly formatted
    • Requester and assignee are correct
    • Timestamps match the original Freshdesk values
  5. Run automated count checks. Compare total ticket count, total conversation count per ticket, and total attachment count between source and target.
  6. Test your rebuilt automations. Create a new test ticket in the sandbox and verify that triggers and macros fire correctly.
Tip

Select edge cases deliberately. Your sample should include the hardest tickets, not a random slice. If you have a ticket with 200 conversations, a 19 MB attachment, and 8 custom fields, that ticket should be in your sample.

Rollback Plan

Every migration plan needs a documented rollback procedure. The question is not whether the migration might fail — it is whether you can recover quickly when something unexpected happens.

What rollback looks like

Zendesk does not offer a bulk delete via the Ticket Import API. If you import 50,000 tickets and then discover a systematic mapping error, you cannot easily remove them in bulk. The standard Tickets API supports deleting tickets individually or in batches of up to 100, but this is slow and rate-limited. Tickets deleted via API go to a "suspended" state and then are permanently purged after 30 days.

This means your rollback strategy should focus on reverting operations to Freshdesk, not on cleaning Zendesk.

Rollback procedure

  1. Revert MX records to point incoming email back to Freshdesk. If you pre-reduced TTL to 300 seconds, this change propagates within 5–10 minutes.
  2. Re-enable the Freshdesk customer portal (or point its DNS back to the original configuration).
  3. Notify agents to resume working in Freshdesk immediately.
  4. Disable Zendesk triggers and automations to prevent any new tickets from being actioned in the Zendesk instance.
  5. Investigate the failure. Determine the root cause — was it a mapping error, rate-limit exhaustion, attachment pipeline failure, or data quality issue?
  6. Clean the Zendesk instance. If you used a sandbox or trial, discard it and provision a new one. If you imported into production Zendesk, delete the imported tickets in batches or tag them for exclusion from views and reporting.
  7. Fix the issue and re-run the sample migration before attempting the full migration again.

Rollback decision criteria

Define these before cutover day, not during the crisis:

  • Ticket count mismatch exceeds 2%. If more than 2% of tickets failed to import, do not proceed to cutover.
  • Conversation count mismatch exceeds 1%. Missing conversations are worse than missing tickets — they represent lost customer context.
  • Attachment import failure rate exceeds 5%. If attachments are systematically failing, the root cause (URL expiry, rate limits, size limits) must be resolved.
  • Inline images broken on more than 10% of sampled tickets. This indicates the re-hosting pipeline is not working correctly.
  • Any critical automation fires incorrectly in sandbox testing. Do not go live with untested trigger logic.

These thresholds are starting points — adjust them based on your organization's tolerance.

Post-Migration QA Checklist

The one-paragraph version of "validate and reconcile" is not enough for a real migration. Use this checklist before signing off.

Automated count verification

  • Total ticket count in Zendesk matches Freshdesk source count (excluding any intentionally excluded tickets)
  • Total user count in Zendesk matches Freshdesk contact + agent count
  • Total organization count matches Freshdesk company count
  • For a random sample of 100 tickets: conversation count per ticket matches source
  • For a random sample of 100 tickets: attachment count per conversation matches source

Manual inspection (50–100 tickets)

  • Inline images render correctly (not broken placeholders)
  • Private notes are marked as private (not visible to end users in the Help Center)
  • CC lists are preserved on tickets that had CC'd recipients
  • Organization membership is correct (users belong to the right organization)
  • Custom field values are populated and display correctly in the agent UI
  • Tags are present, lowercase, and correctly formatted
  • Ticket timestamps (created_at, updated_at) match source values
  • Author attribution on comments is correct (the right user is shown as the commenter)
  • Attachments over 15 MB download successfully
  • HTML formatting in conversation bodies is preserved (tables, bold, lists)

Operational verification

  • New incoming emails create tickets in Zendesk (send a test email)
  • Agent can reply to a migrated ticket and the customer receives the response
  • Rebuilt triggers fire correctly on new tickets
  • Rebuilt macros apply the correct actions
  • Help Center portal displays correctly (if migrated)
  • Customer-facing portal shows correct ticket visibility (test with a customer account)
  • SLA policies are applying to new tickets (not to imported historical tickets)

Sign-off

  • Support manager confirms agent workflow is functional
  • QA lead confirms data parity meets acceptance thresholds
  • Stakeholders notified that migration is complete
  • Freshdesk instance set to read-only (not deleted) for at least 30 days as a safety net

Estimated Timeline and Effort

Timelines vary by data volume, complexity, and team capacity. The table below gives phase-by-phase estimates for three common migration sizes.

Phase Small (under 10K tickets) Medium (10K–100K tickets) Large (100K+ tickets)
Data audit and field mapping 2–3 days 3–5 days 5–7 days
Script development / tool configuration 3–5 days 1–2 weeks 2–3 weeks
Sandbox setup and sample migration 1–2 days 2–3 days 3–5 days
Bulk import (runtime) 2–6 hours 6–24 hours 1–4 days
QA and validation 1–2 days 3–5 days 5–7 days
Automation rebuild and testing 2–3 days 3–5 days 5–10 days
Cutover and stabilization 1 day 1–2 days 2–3 days
Total elapsed time 2–3 weeks 4–6 weeks 6–10 weeks

Typical team composition: 1 engineer (migration scripting), 1 support operations lead (field mapping, automation rebuild, QA), and 1 QA resource (manual ticket inspection). Larger migrations benefit from a dedicated project manager to coordinate cutover timing.

Example timeline: Mid-size B2B SaaS migration

A B2B SaaS company with 75,000 tickets, 12 custom fields, 8 Freshdesk automations, 25 canned responses, and 40 agents completed a Freshdesk-to-Zendesk migration in 28 calendar days:

  • Days 1–4: Data audit. Exported sample of 300 tickets. Built field mapping tables. Identified 3 custom statuses requiring tag-based mapping (Zendesk Professional plan, no custom status support). Discovered 1,200 tickets with inline images requiring re-hosting.
  • Days 5–14: Script development. Built export pipeline with updated_since windowing and conversation fan-out for tickets with 10+ replies. Built import pipeline with attachment re-hosting, tag normalization, and retry logic. Tested against Zendesk trial instance.
  • Days 15–17: Sample migration of 500 tickets in sandbox. Identified 2 issues: dropdown custom field values not transforming correctly, and 4 inline images with unusual URL patterns not caught by the regex. Fixed and re-ran.
  • Days 18–20: Bulk import. Ran overnight during low-support-volume hours. Completed in 14 hours. Support team continued working in Freshdesk during this window.
  • Days 21–24: QA. Ran automated count checks (99.8% ticket parity, 99.6% conversation parity). Manually inspected 100 tickets. Found 3 tickets with broken inline images (edge case: SVG files embedded as data URIs). Accepted as known limitation.
  • Days 25–26: Automation rebuild. Translated 8 Freshdesk automations into 6 Zendesk triggers and 2 Zendesk automations. Converted 25 canned responses to macros. Tested in sandbox.
  • Days 27–28: Cutover. Final delta sync (captured 1,400 tickets modified during the 10-day import+QA window). MX record switch. Agent briefing. Monitoring. Freshdesk kept in read-only mode.

Step-by-Step Migration Sequence

Whether you run this yourself or bring in a partner, the execution order matters. Importing in the wrong sequence creates orphaned records and broken references.

1. Audit and map your Freshdesk data

Export a sample of 100–500 tickets with conversations and attachments. Identify all custom fields, custom statuses, tags, and agent/group configurations. Build your field mapping tables. Pay special attention to other_emails, company domains, and view_all_tickets — these affect identity and visibility, not just reporting.

2. Prepare your Zendesk instance

Create all groups, custom fields (with correct field types and dropdown options), and configure organization settings. Disable triggers and automations that would fire on newly created tickets. Disable welcome emails before user import.

3. Import organizations and users

Create organizations from Freshdesk companies first — Zendesk expects organizations to exist before user membership data lands cleanly. Then use the Zendesk Users API to bulk-import contacts as users (up to 100 per request). Establish organization memberships. (support.zendesk.com)

4. Import agents and groups

Ensure every Freshdesk agent has a corresponding Zendesk agent account. Map Freshdesk group IDs to their new Zendesk group IDs. Keep a stable ID map — you will need it for every ticket assignment.

5. Run the ticket import

Use the Ticket Import API. Process tickets in batches of up to 100 via /api/v2/imports/tickets/create_many. Include all comments with original timestamps, re-hosted attachments and inline images, and mapped custom fields. For closed legacy tickets, decide whether archive_immediately belongs in your plan.

6. Run a delta sync

Capture any tickets created or updated in Freshdesk after the bulk import started. Import only this delta set to minimize cutover downtime. The delta window is typically 1–4 hours of data rather than a replay of the entire history.

7. Validate and reconcile

Compare source and target: total tickets, conversations per ticket, attachment counts, user counts, and organization memberships. Spot-check 50–100 tickets manually for inline images, formatting, and author attribution. Verify that attachments over 15 MB transferred correctly and that internal notes retained their private status. Use the full QA checklist above.

8. Rebuild automations and cut over

Recreate Freshdesk automations, canned responses, and SLA policies as Zendesk triggers, macros, and SLA targets. Use the translation examples in this guide as a starting point. Data parity does not mean operational parity — this step is a separate workstream. Re-enable triggers post-migration. Update email forwarding, disable the Freshdesk portal, redirect DNS, and brief your support team.

For the complete go-live sequence, see The Go-Live Day Checklist: 15 Things to Do for a Smooth Help Desk Data Migration.

When to DIY and When to Get Help

Be honest about your situation:

  • Under 5,000 tickets, minimal attachments, no custom fields? A SaaS tool or even CSV import may be fine. Run a test batch and inspect 20 tickets manually.
  • 5,000–50,000 tickets with moderate complexity? SaaS tools can work, but plan for a week of QA and manual cleanup on edge cases.
  • Over 50,000 tickets, heavy attachments, inline images, custom statuses? You need custom scripts. The question is whether you build them in-house (2–6 weeks of engineering time) or outsource to a team that has done it before.
  • Regulated industry (healthcare, finance) with data residency requirements? You need full control over where data transits. Off-the-shelf tools route data through their own servers. Custom scripts or an on-prem migration agent give you that control.

The mistake to avoid is choosing the tool before choosing the fidelity target. Decide first whether you need full historical context, account-level visibility, attachment preservation, and a delta sync. Then choose the method that can actually deliver that outcome.

Frequently Asked Questions

How long does a Freshdesk to Zendesk migration take?
Timelines depend on data volume and complexity. A small migration (under 10K tickets) typically takes 2–3 weeks. A mid-size migration (10K–100K tickets) takes 4–6 weeks. Large migrations (100K+ tickets) can take 6–10 weeks. The bulk import runtime itself is a fraction of the total — most time goes to data audit, script development, QA, and automation rebuild.
Can I migrate from Freshdesk to Zendesk without downtime?
Yes, with a delta sync strategy. Run the bulk import while your team continues working in Freshdesk, then run incremental delta syncs to capture changes. On cutover day, run a final delta sync, switch MX records, and redirect your portal DNS. The typical downtime window is under 1 hour if DNS TTL was pre-reduced.
What data gets lost in a Freshdesk to Zendesk migration?
The most common losses are inline images (Freshdesk URLs expire post-migration), large attachments silently skipped by SaaS tools, forward conversation context, satisfaction ratings, time tracking entries, and automation/workflow logic. All of these are preventable with the right migration approach, but none transfer automatically.
Do Freshdesk automations transfer to Zendesk?
No. Freshdesk automations, dispatch rules, scenario automations, and canned responses must be manually rebuilt as Zendesk triggers, automations, and macros. The concepts are similar but not 1:1 — for example, Freshdesk dispatch rules fire once on creation, while Zendesk triggers fire on every matching update unless scoped with a 'Ticket: Is Created' condition.
What is the rollback plan if the migration fails?
Zendesk does not support bulk deletion of imported tickets. The primary rollback strategy is reverting operations to Freshdesk: switch MX records back, re-enable the Freshdesk portal, and notify agents. Keep Freshdesk running in read-only mode for at least 72 hours after cutover to catch straggler emails and DNS propagation issues.

More from our Blog

How to Migrate Users & Organizations Without Breaking History: A Complete Guide
Help Desk

How to Migrate Users & Organizations Without Breaking History: A Complete Guide

Migrating users and organizations from a help desk without breaking history requires migrating foundational data in a specific order. The core challenge is preserving the relationships between records, not just moving the records themselves. To prevent orphaned tickets and lost context , you must first migrate Organizations , then migrate Users and associate them with their organizations. Only after users and organizations are in place can you safely migrate all Tickets and their complete historical data.

Raaj Raaj · · 8 min read
Zero-Downtime Help Desk Data Migration: How to Keep Support Running During the Move
Help Desk

Zero-Downtime Help Desk Data Migration: How to Keep Support Running During the Move

This guide details the 3-stage technical process for a zero-downtime help desk migration. Learn how to use an initial bulk data transfer, a continuous delta migration (Change Data Capture), and a seamless final cutover to move platforms without any service interruption. Discover how an engineer-led approach can guarantee a 100% accurate, 50x faster migration.

Raaj Raaj · · 6 min read