How to Migrate from Intercom to Zendesk (Step-by-Step)
A technical guide to migrating from Intercom to Zendesk — covering API rate limits, field mapping, timestamp preservation, attachment handling, and failure modes.
Migrating from Intercom to Zendesk is a data modeling problem, not a button-click. Intercom organizes everything around conversations — flat threads of messages between contacts and admins. Zendesk organizes everything around tickets — structured records tied to requesters, organizations, groups, and agents with discrete public/private comments. Every field, timestamp, and relationship has to be translated between these two models, and the gaps between them are where data gets silently dropped.
This guide covers the structural mismatches, API constraints on both sides, field mapping decisions, attachment handling, and a comparison of migration methods — so you can pick the approach that fits your volume, timeline, and risk tolerance.
For a deeper look at Zendesk's architecture, pipelines, and configuration, see The Ultimate Zendesk Guide.
Why Migrating from Intercom to Zendesk Is a Data Modeling Challenge
Intercom's data model is contact-centric. A contact (user or lead) initiates a conversation. That conversation is a single thread with conversation_parts — each part is a message, a note, an assignment action, or a state change. Tags, custom attributes, and SLA data hang directly off the conversation object. Intercom uses companies linked to contacts, but there is no native concept of an "organization" in the Zendesk sense — and companies are not directly tied to conversations. (developers.intercom.com)
Zendesk's data model is ticket-centric. 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. Ticket metadata like priority, type, status, and custom fields are first-class properties. Side conversations, satisfaction ratings, and audit trails are separate API objects.
This structural gap creates three concrete problems:
-
Conversations → Tickets: Each Intercom conversation becomes one Zendesk ticket. But Intercom's
conversation_partsinclude assignment changes, state changes, and notes alongside actual messages. You have to decide which parts become public comments, which become private internal notes, and which are metadata you encode as tags or custom field values. -
Contacts → Users + Organizations: Intercom contacts can be users or leads. Zendesk requires every ticket to have a
requester_id— a user who must already exist in Zendesk before ticket import. Intercom companies need to be mapped to Zendesk organizations, and the contact-company link needs to become a user-organization membership. If a contact belongs to multiple companies in Intercom, you have to pick one primary organization in Zendesk or use organization memberships if your plan supports it. -
State mapping: Intercom conversations have three states —
open,closed, andsnoozed. Zendesk tickets have five-plus states —new,open,pending,on-hold,solved, andclosed. You need an explicit mapping table, and snoozed conversations have no direct Zendesk equivalent.
The extraction side is where many DIY migrations fail first. Intercom's list conversations endpoint does not return full conversation_parts — you must retrieve each conversation individually to get the full message thread and attachments. If you have 50,000 conversations, that is 50,000 individual API calls just for content retrieval, on top of the pagination calls to list them. (developers.intercom.com)
Hard source limit: If an Intercom conversation has more than 500 parts, the retrieve endpoint returns only the 500 most recent parts. Detect these threads before cutover and decide whether to accept truncation, preserve Intercom as a read-only archive for those threads, or build a compensating export path. (developers.intercom.com)
Key distinction: Intercom treats conversations as living threads that can be reopened indefinitely. Zendesk treats closed tickets as archived records. Any conversation you import as "closed" in Zendesk cannot receive new comments — agents must create a follow-up ticket.
The API Rate Limits That Break DIY Migration Scripts
Every Intercom-to-Zendesk migration is bottlenecked by two rate-limiting regimes running simultaneously. Your extraction speed from Intercom and your import speed into Zendesk are independent ceilings, and the lower of the two sets your throughput.
Intercom API Limits
Intercom's official REST documentation describes default limits of 10,000 API calls per minute per app and 25,000 API calls per minute per workspace, distributed into 10-second windows to prevent traffic spikes. Exceeding the limit returns an HTTP 429 response with a Retry-After header. (developers.intercom.com)
Older Intercom community answers (from around January 2022) still cite 1,000 requests per minute and 166 per 10 seconds. Migration plans copied from old blog posts go stale fast. Always check the current vendor documentation before sizing your project.
The real constraint for migrations is not the raw rate limit — it is the number of calls required. Listing conversations returns metadata only. You must make a separate GET /conversations/{id} request for every single conversation to retrieve the full thread and attachments. That per-conversation retrieval is the biggest time sink on the extraction side.
Zendesk API Limits
Zendesk's Support API rate limits vary by plan:
| Zendesk Plan | Requests per Minute |
|---|---|
| Team | 200 |
| Growth | 400 |
| Professional | 400 |
| Enterprise | 700 |
| Enterprise Plus | 2,500 |
| High Volume API add-on | 2,500 |
(Source: Zendesk Developer Docs — Rate Limits)
Some endpoints have their own additional limits. The Update Ticket endpoint is capped at 100 requests per minute on standard plans. The Incremental Export endpoint is limited to 10 requests per minute (30 with the High Volume add-on).
A real migration is not one read and one write per ticket. You page conversations, retrieve each full thread, resolve users and organizations, download binaries, upload those binaries to Zendesk, and then import the ticket. Each ticket with attachments requires at minimum two API calls on the Zendesk side (upload the attachment, then import the ticket) — and tickets with multiple attachments multiply that further.
This means your migration middleware needs separate source and target throttlers, durable checkpoints, idempotent retries, and 429-aware backoff on both sides. Standard ETL tools rarely have the granularity to manage per-endpoint throttling, which leads to complete job failures and silently lost conversations.
Attachment token expiry: Zendesk upload tokens expire after 60 minutes. If your script uploads a batch of attachments but doesn't import the associated tickets within that window, the uploads are deleted and the tokens return 404 errors. Batch your uploads and imports together.
How to Preserve Ticket History and Attachments in Zendesk
The most common failure in Intercom-to-Zendesk migrations is losing historical timestamps. If you use Zendesk's standard ticket creation endpoint (POST /api/v2/tickets), every ticket gets stamped with the current date. A three-year conversation history will appear as if it all happened today.
Use the Ticket Import API
The correct endpoint is POST /api/v2/imports/tickets. This is Zendesk's dedicated import endpoint that:
- Preserves historical timestamps —
created_at,updated_at, andsolved_at - Accepts a custom
author_idfor every comment - Accepts
value,body, orhtml_bodyfor comment content - Bypasses triggers and automations
- Does not send notifications to CC'd users
For bulk imports, POST /api/v2/imports/tickets/create_many accepts up to 100 tickets per request and processes them as an async job. (developer.zendesk.com)
curl https://{subdomain}.zendesk.com/api/v2/imports/tickets \
-d '{
"ticket": {
"subject": "Original Intercom Conversation",
"created_at": "2023-03-15T14:22:00Z",
"updated_at": "2023-03-16T09:45:00Z",
"solved_at": "2023-03-16T09:45:00Z",
"status": "closed",
"requester_id": 12345,
"assignee_id": 67890,
"tags": ["migrated-from-intercom"],
"comments": [
{
"author_id": 12345,
"created_at": "2023-03-15T14:22:00Z",
"value": "Hi, I need help with my billing.",
"public": true
},
{
"author_id": 67890,
"created_at": "2023-03-15T14:35:00Z",
"value": "Sure, let me look into that for you.",
"public": true
}
],
"custom_fields": [
{ "id": 114094651234, "value": "intercom-conv-98765" }
]
}
}' \
-H "Content-Type: application/json" \
-v -u {email}/token:{api_token} -X POSTImported tickets are not the same as native Zendesk tickets. Triggers do not run on imported tickets unless the ticket is updated later. Imported tickets do not support native Zendesk metrics or SLA history. CC'd users are not notified during import. For historical tickets, that is fine. For still-active tickets, you need a deliberate post-import workflow — do not assume Zendesk automation will replay the past. (developer.zendesk.com)
SLA and metrics warning: Zendesk explicitly states that metrics and SLAs are not supported for imported tickets. Running reports on imported data will produce incomplete results. Add a migrated-from-intercom tag to all imported tickets so you can exclude them from SLA calculations and Zendesk Explore reports. Make that distinction clear with leadership before anyone expects historical Zendesk dashboards to mirror Intercom performance data.
The Two-Step Attachment Process
Attachments cannot be included inline in the ticket import payload. Zendesk requires a two-step process:
- Upload the file to
/api/v2/uploads?filename=.... This returns an uploadtoken. - Include the token in the comment's
uploadsarray when importing the ticket.
# Step 1: Upload the file
curl "https://{subdomain}.zendesk.com/api/v2/uploads?filename=screenshot.png" \
--data-binary @screenshot.png \
-H "Content-Type: image/png" \
-v -u {email}/token:{api_token} -X POST
# Response includes: { "upload": { "token": "abc123xyz" } }
# Step 2: Use the token in the ticket import
# Include "uploads": ["abc123xyz"] in the comment objectThe token is single-use — you cannot reuse it across multiple tickets. It expires after 60 minutes. The maximum file size is 50 MB. You cannot attach a file directly to a ticket or retroactively to an existing comment — the token must be included when the comment is created. (developer.zendesk.com)
Your migration script needs to: download the file from Intercom's CDN, upload it to Zendesk's uploads endpoint, capture the token, and embed it in the corresponding comment — all within the expiry window. If any step fails silently, the ticket imports successfully but the attachment is missing. You will not know until an agent opens the ticket.
Plaintext vs. HTML for Message Bodies
Decide early whether you are preserving formatting or prioritizing clean search and rendering. Intercom lets you retrieve conversation content as plaintext. Zendesk's import endpoint accepts plain text (value) or HTML (html_body) for comment content. Plaintext is usually the safer default for historical migrations. If you keep HTML, budget time to rewrite inline image and attachment references after the binaries are re-hosted in Zendesk. (developers.intercom.com)
Mapping Intercom Data to Zendesk: A Complete Field Guide
Good mapping decisions determine whether your agents can actually find and use historical conversations after the move. A bad mapping turns migration into a data graveyard.
For a deep dive on mapping methodology and downloadable CSV templates, see Your Ultimate Guide to Data Mapping for a Flawless Helpdesk Migration.
Core Object Mapping
| Intercom Object | Zendesk Object | Notes |
|---|---|---|
| Contact (User) | User | Match on email address. Create before ticket import. |
| Contact (Lead) | User | Leads without email need a placeholder identity. |
| Company | Organization | Map company_id to organization. Handle multi-company contacts. |
| Conversation | Ticket | One conversation = one ticket. |
| Conversation Part (message) | Comment (public) | Map author to author_id. Set public: true. |
| Conversation Part (note) | Comment (private) | Set public: false. Failing to do this exposes internal chatter to customers. |
| Conversation Part (assignment) | Tag or internal note | No direct equivalent. Encode as internal note or skip. |
| Tag | Tag | Direct 1:1 mapping. Zendesk tags cannot contain spaces — sanitize first (e.g., "high priority" → "high_priority"). |
| Custom Attribute | Custom Field | Create custom fields in Zendesk first. Match data types. If the field does not exist before import, the data is silently dropped. |
| Admin / Team Assignee | Assignee / Group | Requires a directory crosswalk between Intercom admins and Zendesk agents. |
| Statistics / SLA fields | Custom field or internal note | Do not force them into Zendesk's native metrics system. Preserve as reference only. |
Status Mapping
| Intercom State | Zendesk Status | Rationale |
|---|---|---|
open |
open |
Active conversations stay active. |
snoozed |
pending |
Closest equivalent — waiting on external action. Snoozed-until timestamps are lost; Zendesk's pending state has no scheduled reopen time. |
closed |
closed |
Use archive_immediately parameter on import. |
Preserving Historical Searchability
Zendesk assigns new IDs to every imported ticket — it does not preserve Intercom conversation IDs. Any bookmarks, internal wikis, or integrations referencing Intercom conversation IDs will break unless you store the mapping.
Create a custom text field in Zendesk (e.g., "Intercom Conversation ID") and populate it during import with the original Intercom conversation.id. Agents can then search using Zendesk's custom field search syntax:
GET /api/v2/search?query=type:ticket custom_field_123456:intercom-conv-98765If the legacy ID is buried in a note instead of a searchable field, agents will stop using it within a week. (support.zendesk.com)
Also store the mapping externally — a simple CSV or database table linking intercom_conversation_id → zendesk_ticket_id. You will need this for post-migration QA, redirect rules, and integration updates.
Company-to-Organization Relationships
Intercom contacts can belong to multiple companies. Zendesk can model many-to-many user/organization relationships through organization memberships, but only if your plan supports multiple organizations per user. If your operating model requires a single default organization, preserve extra Intercom company references in a custom user field or internal note instead of silently dropping them. (developers.intercom.com)
Map Intercom companies to Zendesk organizations first, capture the generated Zendesk Organization IDs, and then associate the imported users to those IDs. The import order matters.
Public Replies vs. Internal Notes
Intercom threads mix customer-visible messages and teammate-only notes in the same conversation parts array. Preserve the split carefully. Public replies become public Zendesk comments. Internal teammate notes become private comments with public: false. If you flatten everything into public comments, you have a privacy problem. If you drop internal notes, you erase the context that makes historical tickets useful in the first place.
What Gets Lost (and What to Do About It)
Some Intercom data has no Zendesk equivalent. Know these gaps before you start:
- Conversation ratings — Intercom's rating system does not map to Zendesk CSAT. You can preserve the score as a custom field, but it will not appear in Zendesk's native satisfaction reports.
- Contact-level notes — These are separate from conversation parts. Migrate them as user-level notes or append to a synthetic ticket.
- Fin AI Agent data — Intercom's AI participation metadata (
ai_agent_participated, resolution data) has no Zendesk equivalent. Preserve as tags or custom fields if needed for reporting. - Snoozed-until timestamps — Zendesk's pending state does not support a scheduled reopen time. The trigger is lost.
- Group conversations — Intercom allows multiple contacts on a single conversation. Zendesk tickets have one requester. Additional participants can be added as CCs, but the model is fundamentally different.
- SLA and statistics fields — Intercom's native SLA data and conversation statistics do not translate to Zendesk metrics. Preserve as custom fields or internal notes for reference, but do not expect them to appear in Zendesk Explore reporting.
What to Do Before You Start
Regardless of which migration method you choose, complete this preparation work first:
- Audit your Intercom data. Count conversations, contacts, companies, tags, and custom attributes. Identify conversations with 50+ parts — these are your edge cases. Flag any conversations that might exceed the 500-part retrieval limit.
- Clean up before migrating. Delete spam conversations, merge duplicate contacts, and archive stale leads. Every record you migrate costs time and API calls.
- Set up your Zendesk instance. Create custom fields (including an "Intercom Conversation ID" field), groups, agent accounts, and organizations before import. The Ticket Import API requires valid
requester_id,assignee_id, andgroup_idvalues — if any are missing, the import fails. - Plan your cutover window. Decide when agents stop using Intercom and start using Zendesk. Run the bulk migration before cutover, then run a delta sync to catch late-arriving conversations. A one-shot weekend dump is the risky option.
- Tag everything. Add a
migrated-from-intercomtag to all imported tickets. This lets you filter them out of SLA reports and identify them during QA.
For a complete pre-migration checklist, see The Go-Live Day Checklist: 15 Things to Do for a Smooth HelpDesk Migration.
Migration Methods: Automated Tools vs. DIY vs. Custom Engineering
There are three common approaches. Each has real trade-offs.
Self-Serve Automated Tools
Platforms like Help Desk Migration offer a UI-based migration wizard. For a direct comparison, see ClonePartner vs Help Desk Migration.
Works well for: Small volumes (under 5,000 tickets) with standard fields and minimal customization. Fast setup. Predictable per-ticket pricing.
Breaks on: Limited control over how conversation parts map to comments. Attachment handling is opaque and hard to verify for completeness. Custom attribute mapping is often rigid or incomplete. Rate limit handling is a black box — you cannot tune retry logic. Edge cases like multi-company contacts, conversations with 100+ parts, or the 500-part retrieval ceiling may fail silently.
DIY In-House Scripts
Building your own migration scripts using both APIs gives maximum control.
Works well for: Teams with a strong integration engineer and a flexible timeline. Full control over mapping logic. No per-ticket cost.
Breaks on: A production-grade script with proper rate limiting, retry logic, attachment handling, idempotency, and error logging typically takes 2–4 weeks for a senior engineer. Rate limit mishandling causes silent data loss (partial ticket imports where the text arrives but attachments do not). The per-conversation Intercom retrieval is the biggest time sink. Testing requires a Zendesk sandbox, careful QA, and a rollback plan.
Custom Engineering (Migration Service)
A dedicated migration team builds, tests, and executes custom scripts tailored to your dataset. This is the approach ClonePartner takes.
Works well for: Complex mappings, strict cutover windows, attachment-heavy history, and compliance-sensitive data. Battle-tested rate limit handling tuned to your specific Zendesk plan. Per-record verification. Zero downtime — the migration runs in parallel while your team continues using Intercom.
Breaks on: Higher upfront cost than self-serve tools for small, simple datasets. Requires a scoping phase (typically 1–2 days) before execution begins.
| Method | Good Fit | Where It Typically Breaks |
|---|---|---|
| Automated SaaS tool | Standard fields, modest volume, low customization | Long threads, custom transforms, attachment edge cases, audit-heavy QA |
| DIY scripts | Strong internal team, flexible timeline | Retry logic, idempotency, attachment re-hosting, operator QA burden |
| Custom engineering partner | Complex mappings, strict cutover, attachment-heavy history | Higher scoping effort up front, but lower execution risk |
The common failure modes across automated tools and DIY scripts are the same: truncated long threads, lost timestamps from using the wrong Zendesk endpoint, broken attachments because binaries were never re-hosted, company-to-organization mismatches, and the assumption that imported tickets will behave like native Zendesk tickets for triggers and SLA reporting. These are platform constraints, not bad luck. (developers.intercom.com)
How ClonePartner Handles Intercom-to-Zendesk Migrations
We have executed this exact migration many times. Here is what the process looks like:
-
Scoping and mapping review — We audit your Intercom workspace to identify custom attributes, tag structures, conversation volumes, and attachment density. We produce a mapping document for your sign-off before writing a single line of code.
-
User and organization pre-migration — Contacts and companies are migrated first. Every user is created or matched in Zendesk before ticket import begins, so
requester_idreferences never fail. -
Conversation extraction with intelligent throttling — Our scripts dynamically adjust request pacing based on Intercom's rate limit response headers, maximizing throughput without hitting 429 errors.
-
Ticket import with attachment validation — Each conversation is imported via the Ticket Import API with historical timestamps. Attachments go through the two-step upload/token process with per-file verification. If an upload fails, the script retries before moving to the next ticket.
-
Post-migration QA — We run a full record-count reconciliation and spot-check tickets across volume tiers. Every custom field, tag, and attachment is validated against the source.
-
Delta sync — If conversations are created or updated in Intercom during migration, we run a delta pass to catch them before cutover. Your team does not have to stop working in Intercom until QA is complete.
For a behind-the-scenes look at our process, see How We Run Migrations at ClonePartner.
We are not the right answer for every migration. If your Intercom workspace is small, lightly customized, and not attachment-heavy, a self-serve tool may be enough. If you have long threads, messy custom fields, multiple organization relationships, compliance-sensitive notes, or a hard zero-downtime cutover window, you want custom engineering instead of a generic adapter.
Making the Right Call
An Intercom-to-Zendesk migration is straightforward in principle — conversations become tickets, contacts become users, companies become organizations. The complexity lives in the details: rate limit choreography across two APIs, the two-step attachment upload process, timestamp preservation via a dedicated import endpoint, the 500-part thread ceiling, and the dozen structural mismatches that automated tools paper over instead of solving.
If your dataset is small and simple, a self-serve tool can get the job done. If you have years of conversation history, attachments, custom fields, and a support team that cannot afford to lose context on a single ticket, the engineering needs to be precise.
That is what we do. We treat migration as a data engineering discipline — custom extraction, field-level mapping, per-record verification, and a cutover plan that keeps support running throughout.
Frequently Asked Questions
- Does Zendesk preserve original timestamps when importing from Intercom?
- Only if you use the dedicated Ticket Import API endpoint (POST /api/v2/imports/tickets). This endpoint preserves created_at, updated_at, and solved_at timestamps. The standard ticket creation API overwrites all dates with the current time.
- How do attachments work in an Intercom to Zendesk migration?
- Zendesk requires a two-step process: upload each file to /api/v2/uploads to get a single-use token (expires in 60 minutes), then embed that token in the ticket comment's uploads array during import. Your script must download each file from Intercom, upload it to Zendesk, and use the token before expiry. The max file size is 50 MB.
- What data gets lost when migrating from Intercom to Zendesk?
- Intercom conversation ratings, snoozed-until timestamps, Fin AI agent metadata, contact-level notes, and group conversation multi-contact structures have no direct Zendesk equivalent. These can be partially preserved using custom fields and tags, but native reporting for them is lost. Imported tickets also do not support native Zendesk SLA or metric history.
- How long does an Intercom to Zendesk migration take?
- It depends on volume and complexity. A dataset under 10,000 conversations with minimal attachments can be migrated in 1–3 days. Larger datasets (50,000+ conversations) with attachments and custom fields typically take 3–7 days including QA, mainly due to API rate limits on both platforms.
- Can I keep Intercom conversation IDs in Zendesk after migration?
- Zendesk assigns new ticket IDs and cannot preserve Intercom IDs. The best practice is to create a custom text field in Zendesk (e.g., 'Intercom Conversation ID') and populate it during import so agents can search for migrated tickets by their original ID.