How to Migrate from Freshdesk to Gorgias: Complete Technical Guide
How to migrate from Freshdesk to Gorgias: API rate limits, object mapping, the sent_datetime trap, and step-by-step ETL architecture for e-commerce teams.
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
How to Migrate from Freshdesk to Gorgias: Complete Technical Guide
TL;DR — Freshdesk to Gorgias Migration
A Freshdesk to Gorgias migration is an API-to-API project, not a CSV import. The realistic timeline is 5–14 days for a typical 10,000–100,000 ticket migration. The biggest risks are API rate limits on both sides (Freshdesk's Tickets List endpoint is sub-limited to ~20 calls/min on Growth plans; Gorgias caps API key integrations at 40 requests per 20-second window), the sent_datetime trap (omit it and Gorgias sends every historical message to your customers as new email), and the 1MB message body limit that silently rejects long enterprise threads. Gorgias now supports up to 25 active ticket fields, but customer fields are capped at 4 — so complex Freshdesk custom field schemas require flattening into tags or metadata. Teams under 5,000 tickets with simple schemas can self-serve with a careful API script. Above that — or if you need zero-downtime delta syncs — use a managed migration service.
What Is a Freshdesk to Gorgias Migration?
A Freshdesk to Gorgias migration extracts tickets, conversations, contacts, tags, and custom field data from Freshdesk's REST API and loads it into Gorgias's ticket-and-message data model via the Gorgias REST API. Gorgias does not offer a built-in Freshdesk import wizard. Its documented first-party historical import supports Zendesk only — Freshdesk migrations are API-led or partner-led.
It is not a lift-and-shift. Freshdesk exposes tickets, conversations, contacts, companies, groups, and numeric statuses. Gorgias centers on tickets, messages, customers, tags, users, teams, and only open or closed ticket status. The data models diverge at nearly every layer.
Why E-commerce Teams Move from Freshdesk to Gorgias
Gorgias is built for e-commerce. The reasons teams switch are structural:
- Native Shopify integration. Gorgias surfaces order data, tracking info, and refund actions directly inside the ticket view. Freshdesk requires third-party apps or custom middleware to achieve the same.
- Ticket-based pricing. Gorgias charges by conversation volume and includes unlimited agent seats on most paid plans (Starter caps at 3). Freshdesk charges per agent per month — at $79/agent/month on the Pro plan, a 20-agent team pays $1,580/month before any ticket volume considerations.
- E-commerce automation. Gorgias rules trigger natively on Shopify events — order placed, refund issued, tracking updated. Freshdesk automations are channel-generic and require app-layer integrations for commerce event triggers.
The migration is non-trivial because of two architectural differences:
- Data model mismatch. Freshdesk separates tickets and conversations (replies and notes) as distinct objects. Gorgias treats everything as tickets containing messages — and a ticket cannot exist without at least one message.
- Custom field constraints. Freshdesk supports unlimited custom ticket and contact fields. Gorgias supports up to 25 active ticket fields but caps customer fields at 4 active at a time. Older migration guides cite a 4-field ticket cap — that was raised to 25 active ticket fields. The 4-field limit now applies only to customer fields.
Freshdesk to Gorgias: Object and Field Mapping
The mapping table below covers every core helpdesk object. Start here when scoping your migration.
| Freshdesk Object | Gorgias Object | Notes / Caveats |
|---|---|---|
| Ticket | Ticket | Gorgias ticket requires at least one message. Freshdesk uses integer statuses (2=Open, 3=Pending, 4=Resolved, 5=Closed); Gorgias uses open / closed only. Preserve Pending/Resolved as tags if agents still need them. Store the Freshdesk ID in external_id for reconciliation. |
| Conversation (Reply) | Message (from_agent: true/false) |
Each Freshdesk reply becomes a Gorgias message. Set sent_datetime on every imported message — omitting it causes Gorgias to send the message to the customer as new email. |
| Conversation (Private Note) | Message (channel: internal-note) |
Private notes become internal-note channel messages. Not sent to customers. |
| Contact | Customer | Matched by email. Gorgias auto-creates customers on ticket import if the email doesn't exist. Use external_id for deduplication. |
| Company | — (no direct equivalent) | Gorgias has no company/organization object. Flatten company name into a customer field or tag. |
| Agent | User | Agents must be pre-created in Gorgias before migration. Agent IDs will differ — store the mapping. |
| Group | Team | Map Freshdesk groups to Gorgias teams. Team IDs will differ. |
| Tag | Tag | Tags migrate cleanly by name. Gorgias tag names are case-sensitive — normalize casing before import to avoid VIP and vip becoming separate tags. |
| Custom Ticket Fields | Ticket Fields (max 25 active) | Freshdesk custom fields using cf_ prefix need mapping to Gorgias ticket field IDs. Supports Dropdown, Number, Text, and Yes/No types. Conditional logic is limited. |
| Custom Contact Fields | Customer Fields (max 4 active) | This is the hard constraint. If you have >4 Freshdesk contact custom fields, overflow goes to tags or the meta JSON on tickets. Customer field text values cap at 2,000 characters. |
| Ticket Priority | Tags | Gorgias has no native priority field. Map Freshdesk priority (1–4) to tags like priority-high. |
| Attachments | Message attachments | Freshdesk stores attachments as URLs. Pass them as attachments [].url in the Gorgias message payload or upload via POST /api/upload. Freshdesk attachment URLs may expire — download and rehost before migration. |
| SLA Policies | — | No API import path. Rebuild manually in Gorgias. |
| Automations / Macros | Rules / Macros | No migration path. Rebuild in Gorgias. |
| CSAT Ratings | — | Historical satisfaction ratings cannot be imported via API. Your CSAT baseline resets at go-live. |
| Knowledge Base (Solutions) | — | Gorgias Help Center imports directly from Zendesk, HelpDocs, Re:amaze, and Intercom — not Freshdesk. Handle KB migration separately. |
Multi-Channel Ticket Mapping
Freshdesk and Gorgias support overlapping but non-identical channel sets. The mapping for non-email channels requires explicit handling:
| Freshdesk Channel | Gorgias Channel | Migration Notes |
|---|---|---|
email |
Direct mapping. Preserve original from/to addresses. |
|
| Phone | phone |
Gorgias supports phone channel natively. Call recordings must be rehosted — Freshdesk URLs may expire. |
| Chat | chat |
Freshdesk chat transcripts map to Gorgias chat messages. Chat attachment limits differ: 20MB customer-sent, 10MB agent-sent, max 5 files per message. |
facebook |
Import as facebook channel. Historical Facebook messages import correctly, but real-time integration requires reconnecting the Facebook page in Gorgias. |
|
| Twitter/X | twitter |
Same as Facebook — historical import works, but live integration requires reconnection. |
instagram |
Gorgias supports Instagram DM natively. Historical messages can be imported with channel: instagram. |
|
whatsapp |
Gorgias supports WhatsApp via integration. Historical messages can be tagged but may not render in the WhatsApp-specific UI view. | |
| Portal/Widget | contact-form or chat |
Freshdesk portal submissions map to contact-form channel in Gorgias. |
| API-sourced | api |
Tickets imported via API default to channel: api, which can flatten channel analytics. Preserve the original channel in the import payload or tag with the source channel. |
Important: Gorgias auto-reply rules do not trigger when the message channel is api. If you import all historical tickets as channel: api instead of preserving their original channel, your channel-based analytics and automation rules will be inaccurate going forward.
What Has No Clean Equivalent in Gorgias
Companies/Organizations — Gorgias is customer-centric, not account-centric. If your Freshdesk workflow relies on company-level routing or reporting, flatten company data into customer-level tags or the meta JSON field on tickets.
SLA Policies and Business Hours — Must be manually recreated. There is no API import path.
CSAT History — Historical satisfaction ratings from Freshdesk cannot be written into Gorgias. You lose this data continuity. Export CSAT data from Freshdesk before decommissioning for historical reporting purposes.
Freshdesk Forums — Community portal data has no Gorgias equivalent and cannot be migrated. Export to a separate tool if needed.
Ticket ID Numbering — Gorgias auto-assigns sequential ticket IDs. You cannot control or set ticket numbers to match Freshdesk IDs. If customers reference Freshdesk ticket numbers in follow-up emails, store the original Freshdesk ticket ID in external_id and train agents to search by it. Consider adding the Freshdesk ticket number as a tag (e.g., fd-ticket-12345) for quick lookup.
The API Bottleneck: Rate Limits on Both Sides
Rate limits are the single biggest technical constraint in this migration. Here is the math.
Freshdesk Extraction Limits
Freshdesk enforces per-minute rate limits: Growth plan gets 200 calls/min, Pro gets 400 calls/min, and Enterprise gets 700 calls/min. Even if your Growth-plan customer has 200 calls/min total, you can only hit the Tickets List endpoint 20 times per minute. That is 20 pages × 100 records = 2,000 tickets per minute, maximum.
For a deep dive on Freshdesk extraction, see the Freshdesk data export guide.
| Freshdesk Plan | Overall Limit | Tickets List Sub-Limit | Max Ticket Stubs/Min |
|---|---|---|---|
| Growth | 200/min | ~20/min | 2,000 |
| Pro | 400/min | ~40/min | 4,000 |
| Enterprise | 700/min | ~70/min | 7,000 |
Key gotchas:
- The search/filter endpoint (
/api/v2/search/tickets) has a hard ceiling of 300 results (30 per page × 10 pages) from a single filter query. If you need to export thousands of filtered tickets, break your query into smaller date ranges. Use the list endpoint withupdated_sincewindowing instead. - The
include=conversationsparameter on the ticket view endpoint returns only up to 10 conversations. For full thread history, you must call the dedicatedGET /api/v2/tickets/{id}/conversationsendpoint per ticket. - Freshdesk's ticket list endpoint defaults to returning only tickets from the last 30 days. If you forget
updated_sinceand time-slicing, you will ship an incomplete migration without noticing.
Gorgias Load Limits
Gorgias allows 40 requests per 20-second window on an API key and 80 per 20-second window on an OAuth2 app, enforced per account using a leaky bucket algorithm (requests are consumed immediately and replenished gradually over the window). Enterprise accounts get the same request counts on a tighter 10-second window.
| Auth Method | Requests per Window | Window | Effective Rate |
|---|---|---|---|
| API Key | 40 | 20 seconds | ~2 req/sec |
| OAuth2 App | 80 | 20 seconds | ~4 req/sec |
| Enterprise (API Key) | 40 | 10 seconds | ~4 req/sec |
| Enterprise (OAuth2) | 80 | 10 seconds | ~8 req/sec |
The limit is per account, not per key. Your migration tool, your reporting sync, and your phone or chat integration all draw from the same 40 or 80 requests. If three systems hit the account at once, they fight over one budget, and the one that loses gets the 429.
Engineering tip: Use OAuth2 authentication for your migration script to double your budget from 40 to 80 requests per window. Pause all non-essential integrations during the migration run.
Other Gorgias API constraints worth knowing:
- Gorgias uses cursor-based pagination across all list endpoints (offset-based pagination was removed in February 2024).
- Starting from Sep 01, 2024, ticket subject length is limited to 998 characters. Endpoints return HTTP 400 when the length exceeds this.
- PUT endpoints perform full resource replacement, not partial update. Omitting optional fields in a PUT body may silently reset them to null or defaults. Always use PATCH for partial updates where supported, or include all fields in PUT requests.
- Gorgias list endpoints do not support server-side date filtering. Any filtering by creation or update timestamp must happen client-side after fetching.
Throughput Math
A single Gorgias ticket import requires at minimum 1 API call (ticket + first message in one POST /api/tickets call), plus 1 call per additional message. A ticket with 5 messages = 5 API calls.
On an API key at 40 req/20s (~120 calls/min):
- Simple tickets (1 message each): ~120 tickets/min
- Average tickets (3 messages): ~40 tickets/min
- Complex threads (8+ messages): ~15 tickets/min
On OAuth2 at 80 req/20s (~240 calls/min):
- Simple tickets (1 message each): ~240 tickets/min
- Average tickets (3 messages): ~80 tickets/min
- Complex threads (8+ messages): ~30 tickets/min
A 50,000-ticket migration with an average of 3 messages per ticket requires ~150,000 API calls. At 120 calls/min (API key), that is roughly 21 hours of continuous runtime — assuming zero retries and no customer upsert calls. On OAuth2 at 240 calls/min, that drops to about 10.5 hours. Add customer upserts (~1 call per unique customer) and attachment uploads, and realistic runtime extends 20–40% further. A 100,000-ticket migration with similar thread density will take 2–4 days of API runtime on a standard plan.
| Migration Size | Avg Messages/Ticket | Est. API Calls | API Key Runtime | OAuth2 Runtime |
|---|---|---|---|---|
| 5,000 tickets | 3 | ~15,000 | ~2 hours | ~1 hour |
| 25,000 tickets | 3 | ~75,000 | ~10.5 hours | ~5.2 hours |
| 50,000 tickets | 3 | ~150,000 | ~21 hours | ~10.5 hours |
| 100,000 tickets | 3 | ~300,000 | ~42 hours | ~21 hours |
| 100,000 tickets | 5 | ~500,000 | ~69 hours | ~35 hours |
These are theoretical minimums. Real-world migrations typically run 30–50% longer due to retries, customer upserts, attachment uploads, and transient errors.
Handling Attachments and the 1MB Message Limit
Starting from Jan 31, 2023, ticket message body_text and body_html size is limited to 1MB. Endpoints such as create or update message return HTTP 400 when the size of body_html or body_text attributes exceeds 1MB.
Long-running Freshdesk enterprise threads with extensive HTML signatures, inline images, and embedded content will fail during import if not handled. Freshdesk often stores massive inline base64 images within the HTML body of older tickets.
Your migration script must:
- Measure each message body before sending. If
body_htmlexceeds 1MB, strip embedded base64 images and replace them with hosted URLs. - Extract inline images from Freshdesk HTML bodies, upload them to a CDN or object store (e.g., AWS S3), and rewrite the
srcattributes to point to the hosted file. - Truncate gracefully as a last resort — never silently drop content. Log every truncation for post-migration audit. Append a note like
[Message truncated during migration — original available in Freshdesk archive]to the end of truncated messages.
Chat attachments have their own limits: up to 20MB per file for customer-sent attachments, 10MB for agent-sent. The limit is per message, with a maximum of 5 file attachments per message.
Attachment URL expiration: Freshdesk attachment URLs are authenticated and may expire after your Freshdesk subscription ends. Download all attachments to intermediate storage (S3, GCS, or local disk) during the extraction phase, then upload them to Gorgias during the load phase. Never rely on Freshdesk-hosted URLs persisting after decommissioning.
Comparing Migration Methods: CSV, DIY Scripts, or Managed Service
| Method | How It Works | Best For | Attachments | Thread History | Custom Fields | Complexity |
|---|---|---|---|---|---|---|
| Freshdesk CSV Export | Export tickets as CSV from admin panel, manually reformat | Archival only, <5K tickets with no history needs | ❌ Lost | ❌ Flat | ❌ Lost | Low |
| Third-party tool (e.g., Help Desk Migration) | Automated wizard maps and transfers records | <5K tickets, standard fields only | Partial | Partial | Limited | Low–Med |
| DIY API Script (Python/Node) | Custom ETL using both REST APIs | Engineering teams with API experience | ✅ Full control | ✅ Full control | ✅ Full control | High |
| Managed Migration Service | Expert-led extraction, transformation, and loading | 10K+ tickets, complex fields, zero-downtime needs | ✅ Full | ✅ Full | ✅ Full | Low (for you) |
Estimated engineering effort for DIY:
| Migration Complexity | Tickets | Engineering Hours | Skills Required |
|---|---|---|---|
| Simple (email-only, few custom fields) | <5,000 | 20–40 hours | Python/Node, REST APIs |
| Medium (multi-channel, custom fields, attachments) | 5,000–50,000 | 40–80 hours | API rate limiting, ETL pipelines |
| Complex (all channels, inline images, delta sync) | 50,000+ | 80–160 hours | Distributed systems, queue management |
Using CSVs for SaaS data migrations is not recommended for full migrations. Freshdesk's standard ticket export omits full conversation history and archived tickets. You lose thread context, private notes, and all attachments.
Help Desk Migration is the best-known self-serve option in Gorgias's marketplace and works for shallow archives with standard fields. For attachments, private notes, custom fields, or live cutovers, API-led migration remains the highest-fidelity path.
Step-by-Step Migration Architecture
The order of operations matters. Getting it wrong creates orphaned records and broken thread history.
Step 1: Pre-create Destination Objects
Create users (agents), teams, tags, ticket fields, and customer fields in Gorgias before loading any historical data. Store a mapping of Freshdesk IDs to Gorgias IDs for agents and teams — you need these to preserve ticket assignment history.
Extract agents from Freshdesk using GET /api/v2/agents and create them in Gorgias via POST /api/users. Extract groups via GET /api/v2/groups and create corresponding teams.
Decide which Freshdesk custom fields survive as Gorgias ticket fields (you have 25 active slots) and which get flattened into tags. For customer fields, pick the 4 most critical — everything else becomes tags or meta JSON.
Gorgias test environment: Gorgias does not offer a free sandbox. Request a trial account or use a secondary paid account for testing. Run your full migration script against the test account before touching production. This catches field mapping errors, rate limit issues, and sent_datetime problems before they affect real customer data.
Step 2: Extract from Freshdesk
Use the Freshdesk v2 REST API to paginate through all tickets. Use updated_since time-slicing to capture the full archive — not just the last 30 days.
# Freshdesk extraction with rate limit handling and time-slicing
import requests, time
from datetime import datetime, timedelta
FD_DOMAIN = "yourstore.freshdesk.com"
FD_API_KEY = "your_api_key"
def get_tickets(page=1, updated_since="2020-01-01T00:00:00Z"):
url = (
f"https://{FD_DOMAIN}/api/v2/tickets"
f"?per_page=100&page={page}"
f"&updated_since={updated_since}"
f"&include=description&order_by=updated_at&order_type=asc"
)
resp = requests.get(url, auth=(FD_API_KEY, "X"))
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
print(f"Rate limited. Sleeping {retry_after}s")
time.sleep(retry_after)
return get_tickets(page, updated_since)
if resp.status_code == 500:
# Transient server error — retry with exponential backoff
time.sleep(min(2 ** page, 120))
return get_tickets(page, updated_since)
resp.raise_for_status()
return resp.json()
def get_conversations(ticket_id, page=1):
"""Fetch full conversation thread — do NOT rely on include=conversations
which caps at 10 results."""
url = (
f"https://{FD_DOMAIN}/api/v2/tickets/{ticket_id}/conversations"
f"?per_page=100&page={page}"
)
resp = requests.get(url, auth=(FD_API_KEY, "X"))
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
return get_conversations(ticket_id, page)
resp.raise_for_status()
return resp.json()For each ticket, fetch complete conversation threads via GET /api/v2/tickets/{id}/conversations. Do not rely on include=conversations on the ticket endpoint — it returns only up to 10 conversations per ticket.
Extract contacts via GET /api/v2/contacts?per_page=100. If the customer already exists in Shopify, Gorgias handles the merge by email address.
Step 3: Transform and Map
- Map Freshdesk
statusintegers to Gorgiasopen/closed(2=Open →open, 3=Pending →open+status-pendingtag, 4=Resolved →closed+status-resolvedtag, 5=Closed →closed). - Map Freshdesk
priority(1–4) to Gorgias tags (priority-low,priority-medium,priority-high,priority-urgent). Gorgias has no native priority field. - Map Freshdesk
sourceintegers to Gorgias channel values (1=Email →email, 2=Portal →contact-form, 3=Phone →phone, 7=Chat →chat, 10=Outbound Email →email). - Flatten Freshdesk custom field values (
cf_order_number,cf_return_reason) into Gorgias ticket fields, tags (e.g.,return_reason:defective), or themetaJSON object. - Resolve agent email addresses to Gorgias user IDs using your stored mapping.
- Measure each
body_htmlto confirm it stays under 1MB. Extract and rehost inline images as needed. - Truncate ticket subjects exceeding 998 characters.
- Normalize tag casing to prevent duplicates (e.g., lowercase all tags).
Step 4: Load Tickets into Gorgias
Critical: Always set sent_datetime on imported messages. If you import a message that was already sent, make sure to set the sent_datetime value. If sent_datetime is empty, Gorgias will send it to the recipients. Omitting this field on historical messages will spam every customer in your migration batch. This is the #1 migration failure mode. There is no undo — once sent, messages cannot be recalled.
Create tickets via POST /api/tickets with the first message included. After creating the ticket, append subsequent replies and notes chronologically via POST /api/tickets/{ticket_id}/messages.
# Gorgias ticket creation with sent_datetime and error handling
import requests, time, json
GORGIAS_DOMAIN = "yourstore.gorgias.com"
GORGIAS_AUTH = ("user@yourstore.com", "your_api_key")
def create_ticket(freshdesk_ticket, messages, agent_map):
first_message = messages[0]
payload = {
"customer": {"email": freshdesk_ticket["requester_email"]},
"channel": map_channel(freshdesk_ticket["source"]),
"via": "api",
"status": "closed" if freshdesk_ticket["status"] >= 4 else "open",
"external_id": f"freshdesk-{freshdesk_ticket['id']}",
"subject": freshdesk_ticket.get("subject", "")[:998], # Enforce 998-char limit
"tags": build_tags(freshdesk_ticket),
"messages": [{
"channel": map_channel(freshdesk_ticket["source"]),
"via": "api",
"from_agent": first_message["incoming"] is False,
"sender": {"email": first_message["from_email"]},
"body_html": first_message["body"],
"body_text": first_message.get("body_text", ""),
"sent_datetime": first_message["created_at"], # REQUIRED — prevents resend
"created_datetime": first_message["created_at"],
"external_id": f"freshdesk-conv-{first_message['id']}"
}]
}
resp = requests.post(
f"https://{GORGIAS_DOMAIN}/api/tickets",
json=payload,
auth=GORGIAS_AUTH
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 20))
time.sleep(retry_after)
return create_ticket(freshdesk_ticket, messages, agent_map)
if resp.status_code == 400:
# Log the error for review — may be body size, subject length, or field issue
log_error(freshdesk_ticket["id"], resp.text)
return None
if resp.status_code >= 500:
time.sleep(5)
return create_ticket(freshdesk_ticket, messages, agent_map)
resp.raise_for_status()
gorgias_ticket = resp.json()
# Append remaining messages chronologically
for msg in messages[1:]:
add_message(gorgias_ticket["id"], msg)
return gorgias_ticketEach additional message must include sent_datetime and the correct from_agent boolean. Load messages oldest to newest to preserve thread order.
Use external_id on both tickets (freshdesk-{ticket_id}) and messages (freshdesk-conv-{conv_id}) for reconciliation and idempotent retries. Implement queue-based retries that honor 429 responses — Gorgias exposes Retry-After and X-Gorgias-Account-Api-Call-Limit headers to drive backoff. Checkpoint after each completed ticket so a restart never duplicates history.
Error handling beyond rate limits:
| HTTP Status | Cause | Recommended Action |
|---|---|---|
| 400 | Invalid payload (body >1MB, subject >998 chars, missing required field) | Log the ticket, fix the payload, retry from the error queue |
| 401/403 | Auth failure or permission issue | Stop migration, verify credentials |
| 404 | Referenced resource doesn't exist (e.g., invalid user ID) | Check agent/team mapping, skip and log |
| 429 | Rate limit exceeded | Read Retry-After header, sleep, retry |
| 500/502/503 | Gorgias server error | Exponential backoff (5s, 10s, 20s, 40s), max 3 retries, then log and skip |
| Timeout | Network or server timeout | Retry once after 10s, then log |
Step 5: Run Delta Sync
Keep Freshdesk live during the historical backfill. After the main load completes, poll Freshdesk with updated_since for tickets created or modified during the migration window. Import these as a final batch before cutover to ensure no tickets are lost during the transition.
Alternative approach: Freshdesk webhooks. Instead of polling, configure Freshdesk automation rules to fire webhooks on ticket creation and update events during the migration window. This provides near-real-time delta capture without polling overhead. However, webhook delivery is not guaranteed — use it as a supplement to, not a replacement for, a final updated_since sweep.
Step 6: Validate and Go Live
Run record-count reconciliation between source and target. Sample 2–5% of tickets for field-level accuracy. Verify zero false sends occurred. Complete UAT with the CS team before routing DNS and email forwarding to Gorgias.
GDPR and Data Compliance Considerations
Migrating customer PII between platforms creates compliance obligations:
- Data Processing Agreement (DPA). Ensure your DPA with Gorgias covers the data categories you're importing. Both Freshdesk and Gorgias store data in specific regions — verify data residency requirements. Gorgias stores data in the US by default; EU data residency is available on Enterprise plans.
- Right to erasure. Customers who previously requested deletion from Freshdesk should not be re-imported into Gorgias. Cross-reference your deletion log before extraction.
- Data minimization. Consider whether you need to migrate all historical tickets. Many teams choose to migrate only the last 12–24 months of data, importing older records only if they contain active warranty or subscription context.
- Transit encryption. Both Freshdesk and Gorgias APIs use TLS. If you stage data to intermediate storage (S3, local disk), ensure that storage is encrypted at rest.
- Third-party migration tools. If using Help Desk Migration or another third-party, verify they have appropriate SOC 2 or ISO 27001 certifications and a DPA covering your data.
How Long Does a Freshdesk to Gorgias Migration Take?
| Phase | Duration | Dependencies |
|---|---|---|
| Discovery & field mapping | 1–2 days | Access to both platforms, custom field audit |
| Script development & sandbox test | 2–3 days | Gorgias test account |
| Historical data migration | 1–5 days | Depends on volume and API plan tier |
| Delta sync (new tickets during migration) | 1 day | Freshdesk updated_since polling |
| UAT & validation | 1–2 days | CS team availability |
| Cutover & go-live | <1 day | DNS/email routing changes |
Total: 5–14 days for a typical 10,000–100,000 ticket migration. Smaller migrations (<5,000 tickets) with simple schemas can finish in under a week. Larger migrations (100,000+) may stretch to 2–3 weeks.
The API transfer phase itself may finish in hours on a clean, low-volume dataset. Field redesign, attachment handling, UAT, and the final delta sync are what stretch the timeline.
Rollback Plan
Gorgias does not support bulk delete via API. If the migration fails, your options are:
- Delete the Gorgias account and start fresh (nuclear option — requires Gorgias support).
- Tag all migrated tickets with
migrated-from-freshdeskand use Gorgias views to filter them out while you fix the issue. - Keep Freshdesk active as the system of record until migration is validated.
Never decommission Freshdesk until you have completed at least one full UAT cycle on Gorgias.
Post-Migration Freshdesk Retention
After migration, keep your Freshdesk account active for at least 30–90 days:
- Attachment URL access. If any migrated tickets reference Freshdesk-hosted attachment URLs (rather than rehosted copies), those URLs will break once the account is decommissioned.
- Audit trail. Regulators or customers may request access to historical records. Export a complete Freshdesk backup before cancellation.
- CSAT history. Since CSAT scores don't migrate, Freshdesk remains your only source for historical satisfaction data.
- Freshdesk export format. Use
GET /api/v2/tickets?per_page=100with full conversation fetching to create a JSON archive, or use Freshdesk's admin panel CSV export for a lightweight backup.
For a complete pre- and post-migration QA framework, see the help desk migration checklist.
What Do Customers and Agents Notice During Migration?
A properly executed migration results in zero customer-visible downtime. Customers should notice nothing — no emails, no gaps in history, no lost context.
The risks to manage:
- False notifications. If
sent_datetimeis not set, Gorgias sends old messages to customers as new emails. This is the most common migration failure. On a 50,000-ticket migration, asent_datetimeomission could spam tens of thousands of customers simultaneously. - Missing history. If conversations are not loaded in chronological order, agents lose context on returning customers. When a customer emails about a warranty claim on a 6-month-old order, the agent needs the exact Freshdesk thread to see what was promised.
- Broken CSAT continuity. Historical CSAT scores from Freshdesk cannot be imported. Your reporting baseline resets at go-live.
- Status nuance lost. Freshdesk Pending and Resolved states collapse into Gorgias
open/closed. If agents rely on these distinctions, they need to search by the tags you created during transformation. - Ticket number change. Gorgias assigns new sequential ticket IDs. Customers referencing old Freshdesk ticket numbers will need agents to search by
external_idor thefd-ticket-*tag.
Change management: Brief your CS team on where to find migrated tickets in Gorgias (use a migrated-from-freshdesk tag as a filter). The Gorgias UI organizes tickets differently than Freshdesk — agents need 1–2 days to adjust to the conversation-first layout. Because Freshdesk custom fields get flattened into tags, agents must learn to search by tag (e.g., tag:escalated_tier_2) rather than filtering by dedicated dropdown columns. Create a one-page cheat sheet mapping old Freshdesk workflows to new Gorgias equivalents.
Edge Cases and Known Limitations
Plan for these specific failure modes:
- Duplicate customers. Freshdesk allows multiple contacts with the same email in different companies. Gorgias deduplicates customers by email — you lose the company association. If a Freshdesk contact has multiple email addresses, always use the primary email as the unique identifier. Gorgias customer merges can also fail when both profiles already hold integration data from Shopify — dedupe before you attach store-backed context.
- Multi-brand support. Freshdesk supports multi-product ticket routing. Gorgias does not have native multi-brand — all tickets land in one account. Use tags to preserve product context (e.g.,
brand:storefront-a,brand:storefront-b). - Forum topics. Freshdesk community forums have no Gorgias equivalent. This data cannot be migrated.
- Throttling behavior. When Gorgias returns a
429 Too Many Requests, read theRetry-Afterheader and pause execution. Do not default to a fixed 5-second sleep — during large loads, a fixed retry interval risks compounding 429s and extending migration time significantly. - API-sourced channel analytics. Tickets imported via the API with
channel: apican flatten your channel analytics unless you preserve original source information in tags or metadata. - Shopify data sync is one-way. Shopify-backed customer data flows into Gorgias. Edits made in Gorgias customer profiles do not sync back to Shopify.
- Knowledge base. Gorgias Help Center imports directly from Zendesk, HelpDocs, Re:amaze, and Intercom — or by CSV. Freshdesk is not in the direct-import set. KB migration is a separate project.
- Partial ticket creation failures. If the API creates a ticket but fails on the 3rd of 5 messages, you have an incomplete ticket in Gorgias. Your script must detect this (using
external_idon messages) and resume from the last successful message, not recreate the entire ticket. - Time zone handling. Freshdesk stores timestamps in UTC. Ensure your migration script passes ISO 8601 timestamps with timezone offsets (e.g.,
2024-06-15T14:22:00+00:00) to avoid time-shifted thread ordering in Gorgias.
Validation and Testing
Post-migration QA requires strict reconciliation — not just total counts, but field-level accuracy on sampled records.
| Check | Method | Pass Criteria |
|---|---|---|
| Record count | Compare Freshdesk total tickets vs. Gorgias total tickets | 100% match (account for excluded spam/deleted) |
| Message count per ticket | Sample 50 tickets, compare conversation count | 100% match |
| Custom field / tag values | Sample 25 tickets, compare field-by-field | 100% match |
| Attachment accessibility | Open 20 random attachments in Gorgias | All load correctly (no 403 from expired CDN links) |
| Agent assignment | Verify 10 tickets assigned to correct Gorgias users | 100% match |
| Thread chronology | Sample 50 tickets with >10 replies, verify message order | Correct chronological sequence |
| No false sends | Check Gorgias outbox and customer email for unexpected messages | Zero false sends |
| Channel accuracy | Verify 20 tickets across different channels (email, chat, phone) | Correct channel attribution |
| Customer deduplication | Check 10 customers with multiple Freshdesk contacts | Single Gorgias customer per email |
| External ID lookup | Search 10 tickets by Freshdesk ID using external_id |
All found and accessible |
For a reusable QA checklist, see how to do a help desk data migration.
Build In-House vs. Use a Managed Migration Service
Build in-house when:
- You have fewer than 5,000 tickets with simple field schemas.
- Your engineering team has experience with rate-limited API integrations.
- You can tolerate iteration and a 1–2 week timeline.
- Your data is email-only with no inline images or complex attachments.
Use a managed service when:
- You have 10,000+ tickets with custom fields, attachments, and inline images.
- You need zero-downtime delta sync during cutover.
- You cannot risk false customer notifications from a
sent_datetimemisconfiguration. - Your engineering team is already committed to product work and cannot absorb the 40–160 hours this migration requires in scripting, testing, and debugging.
- You have multi-channel data (chat, phone, social) with channel-specific mapping requirements.
The hidden costs of DIY are real. Engineers routinely underestimate the complexity of Freshdesk's list endpoint sub-limits, the required sent_datetime logic, attachment rehosting, and the delta sync needed for a clean cutover. A failed load often means wiping the Gorgias instance and starting over — there is no bulk delete API to selectively undo a bad import.
Frequently Asked Questions
- Can I migrate Freshdesk to Gorgias without losing data?
- Yes, but only via API. CSV exports lose conversation threads, attachments, and custom fields. An API-based migration preserves all ticket messages, notes, tags, and metadata when properly executed. The main data you cannot migrate is historical CSAT scores, SLA policy configurations, automation rules, and community forum content.
- Will Gorgias send old emails to customers during migration?
- Yes, if you don't set the sent_datetime attribute on every imported message. Any message created via the Gorgias API without sent_datetime is treated as a new outgoing message and Gorgias will attempt to deliver it. This is the most common migration failure mode.
- How long does a Freshdesk to Gorgias migration take?
- A 10,000-ticket migration typically takes 5–8 days end-to-end including discovery, scripting, data transfer, and validation. The API transfer phase alone takes 4–12 hours depending on messages-per-ticket density and your Gorgias plan tier. A 100,000-ticket migration can take 2–3 weeks.
- What data cannot be migrated from Freshdesk to Gorgias?
- SLA policies, automation rules, macros, satisfaction survey history, knowledge base articles, community forum topics, and company/organization objects have no import path in Gorgias. These must be manually rebuilt or archived separately.
- How much does a Freshdesk to Gorgias migration cost?
- DIY costs are engineering time: 40–80 hours for a mid-complexity migration. Third-party automated tools charge $1–$3 per ticket depending on volume. Managed migration services typically range from $2,000–$10,000 depending on ticket volume, custom field complexity, and attachment density.

