Data MigrationMay 15, 202612 min read

Zendesk to Salesforce Migration: The Engineer's Field Guide (2026)


Moving your support operations from Zendesk to Salesforce Service Cloud looks straightforward on the architecture diagram. Then you open the Zendesk API docs, discover your custom fields are named custom_field_8321 through custom_field_11043, realize Salesforce Service Cloud's Case object expects a flat schema while your conversation threads are nested four levels deep in JSON, and the migration agency quote lands at $42,000.

This guide is for the engineer who's been handed that migration. It also covers teams running a Zendesk integration with Salesforce who discover mid-project that a full data migration is the cleaner path.

Who this guide is for

  • You're a technical lead (CTO, head of engineering, senior support ops) at a 20–500 person company.
  • Your company is migrating from Zendesk to Salesforce Service Cloud, either standalone or as part of a broader Salesforce CRM consolidation.
  • You have 50,000 to 5,000,000 tickets in Zendesk, custom fields, and possibly Sunshine Custom Objects.

If your Zendesk instance has fewer than 5,000 tickets and no Sunshine Custom Objects, the native Zendesk CSV export plus the Salesforce Data Import Wizard is probably enough — we'll explain when DIY makes sense at the end. If you're at 5M+ records or have a multi-region Zendesk setup, the technical considerations in this guide still apply but the timeline assumptions don't.

Why this migration is harder than vendors admit

Zendesk custom fields are anonymous integers

In Zendesk, custom fields are referenced as custom_field_8321, custom_field_9912, etc. The human-readable name (e.g., "Customer Tier") lives in a separate ticket_fields API endpoint and must be joined manually.

Here's what a raw Zendesk ticket payload looks like:

{
  "ticket": {
    "id": 482211,
    "custom_fields": [
      { "id": 8321, "value": "premium" },
      { "id": 9912, "value": "high" },
      { "id": 11043, "value": "12500.00" }
    ]
  }
}

And the corresponding /api/v2/ticket_fields response that must be joined to make sense of it:

{
  "ticket_fields": [
    { "id": 8321, "title": "Customer Segment", "type": "tagger" },
    { "id": 9912, "title": "Resolution Tier", "type": "integer" },
    { "id": 11043, "title": "Contract Value", "type": "decimal" }
  ]
}

Salesforce Service Cloud uses named Custom Fields by design — Customer_Segment__c, Resolution_Tier__c, etc. Mapping anonymous Zendesk IDs to meaningful Salesforce custom field API names is the single largest source of migration error.

Conversation threads are deeply nested

Zendesk stores ticket replies as a comments collection with nested author objects, attachments, and metadata. Salesforce Service Cloud's CaseComment and EmailMessage objects are flat.

The Zendesk comment shape:

ticket
└── comments[]
    ├── author{}
    │   └── via{}            (channel metadata)
    ├── attachments[]
    └── audit_event{}

A five-year-old Zendesk instance often has 4–6 million comments. Naive extraction into Salesforce hits Bulk API limits within the first hour, and you discover the failure halfway through a 2-day import job.

Sunshine Custom Objects sunset July 1, 2026

After July 1, 2026, Zendesk's Sunshine Custom Objects API returns 410 Gone. Any custom object data not extracted before then is permanently lost.

See our Sunshine Custom Objects extraction guide for the full deprecation timeline and the relationship-graph preservation strategy.

If your Zendesk instance uses Sunshine for hardware assets, license entitlements, or B2B account hierarchies, those records need to be extracted before the sunset date AND mapped to Salesforce Custom Objects on the destination side. The mapping is non-trivial because Salesforce Custom Objects use a different relationship model (Master-Detail vs Lookup) than Sunshine.

Zendesk's Incremental Export API has cursor edge cases

The Incremental Export API (the only way to bulk-export at scale) has documented but frequently overlooked failure modes — including the ghost cursor loop where a cursor returns an empty page indefinitely under certain conditions.

We documented these failure modes in detail in our Zendesk API extraction guide.

Salesforce has its own rate limits, on the import side

Salesforce limits both Bulk API and Bulk API 2.0 to 15,000 batches per rolling 24-hour period — this allocation is shared across both API versions. In Bulk API 2.0, only ingest jobs consume batches; query jobs do not. Bulk API 2.0 handles record batching automatically, so you submit the full dataset and Salesforce determines the most efficient chunking — you do not manually set batch sizes.

A 2M-record migration will consume a significant share of that 24-hour batch allocation. Practical guidance: use Bulk API 2.0 for the import (it auto-batches), monitor job status via the Jobs endpoint, and spread large imports across multiple 24-hour windows if you're close to the limit. Note: PK Chunking is a Bulk API v1 feature only — it does not apply to Bulk API 2.0, which handles chunking internally.

GDPR data residency may require staying in-region

If your Zendesk EU instance contains EU customer PII, the extracted data should not transit a US-region intermediary even briefly. This matters for Salesforce Service Cloud orgs in eu-central or eu-west regions.

Evicta runs a Stockholm-hosted extraction by default, and offers a Zero-Trust CLI bundle (evicta-zero-trust-cli) for fully air-gapped extraction inside your own VPC. Evicta generates the extraction logic and delivers it as a standalone bundle you run on your own infrastructure — Evicta's cloud never sees a single byte. Whichever you use, the output lands in your destination — Salesforce, S3, or your own Postgres — without persisting on Evicta servers.

The three approaches teams actually use

DIY: write the migration yourself

When this makes sense: under 50,000 tickets, no Sunshine Custom Objects, fewer than 10 custom fields, and at least one engineer who has shipped a Zendesk API integration before.

Realistic timeline: 3–8 weeks of focused engineering work. The extraction side typically takes 2–4 weeks (rate limits, pagination state machines, schema mapping) and the Salesforce import side another 1–4 weeks (Bulk API 2.0 configuration, picklist value reconciliation, foreign key resolution).

Risks: cursor edge cases that silently drop records, rate limit miscalculations that stretch extraction from 24 hours to 18 days, and field mapping errors that surface during user acceptance testing when an agent realizes "Customer Tier" column is showing "8321" instead of "Premium".

Migration agency

Help Desk Migration Service and Relokia are the two best-known agencies in this space. Both have shipped hundreds of Zendesk migrations and offer white-glove project management.

Realistic pricing: $30,000–$50,000 for mid-size migrations, more for Enterprise with Sunshine and complex schema. The price scales with record count and custom field complexity.

What they're good at: end-to-end project management, multi-tool expertise (they support many platforms), and accepting accountability for the full migration outcome.

What they're not good at: fast turnaround (typical engagement is 4–8 weeks), custom field automation (most of the field mapping is still done manually by their team), and price flexibility (the price tag reflects the human-hours model).

Flat-fee extraction tool (Evicta)

Pricing: $1,499 one-time for unlimited extraction volume, $499 for Starter (under 50k records).

What's included in the $1,499 Premium tier: full extraction of tickets, users, organizations, ticket events, custom fields (with LLM-assisted human-readable column names), conversation threads (preserving the public/internal distinction), and Sunshine Custom Objects with relationship graph preserved. Output as Postgres or JSONL. The $499 Starter tier covers up to 50,000 tickets, outputs Postgres + CSV, and does not include Sunshine Custom Objects or JSONL.

What's NOT included: the Salesforce-side import. We extract; you import. If you want a fully managed end-to-end migration including Salesforce configuration, picklist mapping decisions, user training, and go-live cutover, pair us with a Salesforce consultancy or use one of the migration agencies above.

Why this works: the extraction is the hard, technical, non-Salesforce-specific part. Doing it as software at flat fee is straightforward. The Salesforce import is genuinely configuration-dependent and benefits from human involvement.

Comparison

How teams actually decide between DIY, agency, and Evicta.

DIY (in-house script)Migration agencyEvicta
CostEngineer-weeks$30k–$50k$1,499 flat
Delivery time3–8 weeks4–8 weeks<24 hours
Sunshine Custom ObjectsIf you write the API clientRequires custom SOWYes
Custom field namesManualManualAutomated, you approve
Output formatsWhatever you buildCSV mostlyPostgres & JSONL
Persisted on vendor serversn/aOftenZero bytes
Risk of mid-run failureHighMediumValidated before write
Salesforce importYou build itIncludedYou build it (or pair with agency)

Step-by-step: extracting Zendesk data for Salesforce

1. Authentication and read-only token scope

Generate a read-only Zendesk API token via Admin Center → Apps and integrations → Zendesk API → Token Access. The OAuth scopes needed: tickets:read, users:read, organizations:read, ticket_comments:read, attachments:read, groups:read. Note: there is no dedicated sunshine:read or custom_fields:read scope in Zendesk's OAuth model — access to Sunshine Custom Objects and ticket field metadata is governed by the read global scope or your API token's account-level permissions, not granular OAuth scopes.

The token should be revoked from your Zendesk admin panel the moment extraction completes — it has read-only scope but rotation is good hygiene.

2. Schema discovery: mapping Zendesk to Salesforce Service Cloud

This is where most migrations fail. The Zendesk-to-Salesforce mapping is mechanical for standard fields and judgmental for custom ones.

Standard field mapping:

Zendesk                  →  Salesforce Service Cloud
──────────────────────────────────────────────────────
ticket                   →  Case
ticket.subject           →  Case.Subject
ticket.description       →  Case.Description
ticket.status            →  Case.Status (with picklist mapping)
ticket.priority          →  Case.Priority
ticket.requester         →  Contact (lookup)
ticket.assignee          →  Case.OwnerId (User lookup)
ticket.organization      →  Account (lookup)
ticket.tags              →  Case.Tags__c (custom field)
ticket.comments[]        →  CaseComment[] or EmailMessage[]
ticket.custom_fields[]   →  Case.Custom_*__c (one mapping per field)

Critical gotcha — picklist value mapping: Zendesk's status values (new, open, pending, solved, closed) do not map 1:1 to Salesforce's default Case Status picklist (New, Working, Escalated, Closed). Many orgs customize their Salesforce picklist with additional values like "Waiting on Customer". The mapping decisions are org-specific — Evicta proposes a default mapping, but you should review and override based on your Salesforce org's actual picklist values.

3. Conversation thread flattening

The core transform that takes Zendesk's nested comment structure and produces flat Salesforce CaseComment records:

# Pseudocode — actual implementation handles edge cases
# like missing authors, deleted comments, attachment URLs

for ticket in tickets:
    for comment in ticket.comments:
        yield {
            'ParentId': salesforce_case_id_lookup[ticket.id],
            'CommentBody': sanitize_html(comment.body),
            'IsPublished': comment.public,  # critical: maps to Salesforce visibility
            'CreatedDate': comment.created_at,
            'CreatedById': salesforce_user_id_lookup[comment.author_id]
        }

The comment.public boolean is what distinguishes a customer-facing reply from an internal agent note. Salesforce exposes this via CaseComment.IsPublished — preserve it or you'll have internal notes visible to customers in the Salesforce Customer Community.

4. Sunshine Custom Objects → Salesforce Custom Objects

Sunshine objects map to Salesforce Custom Objects, but the relationship model is different. Sunshine uses bidirectional relationship records; Salesforce uses Master-Detail or Lookup fields on the Custom Object itself.

Migration approach:

  1. Extract Sunshine object types from /api/v2/custom_objects
  2. For each type, create the equivalent Salesforce Custom Object with matching API name (e.g., hardware_assetHardware_Asset__c)
  3. Map Sunshine attributes to Salesforce custom fields on the Custom Object
  4. Convert Sunshine relationship records to Salesforce Lookup field values on the destination object

The deadline matters here. Sunshine sunsets July 1, 2026 — extract before, import after. The import side has no deadline, but the extraction side becomes impossible after sunset.

5. Validation and dry-run

Always extract first, validate against a 100-record sample, then run the full extraction. Salesforce import errors mid-job are painful — Bulk API 2.0 jobs can partially succeed, leaving you with an inconsistent destination database.

The Evicta validation flow:

  1. Read-only token connects to your Zendesk instance
  2. LLM samples 100 records, proposes column name mappings
  3. You review the proposed mapping in the dashboard
  4. You approve, override, or reject each mapping
  5. Full extraction runs against the approved schema
  6. Output lands in your destination — Postgres or JSONL on Premium, Postgres + CSV on Starter

If the preview doesn't look right, you walk. No card required. No paid run without explicit approval.

Importing into Salesforce Service Cloud

Evicta does not handle this side. This section is honest technical help for the buyer — you will need to run the Salesforce import yourself, or pair with a Salesforce consultancy.

Choose your import method

Three options depending on volume:

  • Data Import Wizard: up to 50,000 records per import, no automation required. Good for orgs migrating under 5,000 tickets.
  • Data Loader: medium volume with PK Chunking. Free desktop application. Good for orgs in the 5,000–500,000 ticket range.
  • Bulk API 2.0: large, programmatic, recommended for >100,000 records. Requires writing import client code or using a consultancy.

Order of imports — referential integrity matters

Salesforce requires parent records before child records. Correct order for Zendesk-to-Salesforce:

  1. Accounts (from Zendesk organizations)
  2. Contacts (from Zendesk users)
  3. Custom Objects (from Sunshine)
  4. Cases (from Zendesk tickets)
  5. CaseComments / EmailMessages (from Zendesk comments)
  6. Files / ContentDocument (from Zendesk attachments)

Skipping ahead — e.g., importing Cases before Contacts — causes Bulk API jobs to fail mid-batch with INVALID_FIELD_FOR_INSERT_UPDATE errors that are painful to recover from.

Common Salesforce import errors and how to handle them

Three concrete errors a migration team will encounter:

  • DUPLICATE_VALUE on Contact email: Salesforce's duplicate matching rules can reject Contact inserts when an email matches an existing record. Solution: dedupe before import, or use upsert with an external_id field set to the Zendesk user ID.

  • INVALID_FIELD_FOR_INSERT_UPDATE on Case.OwnerId: this fires when the OwnerId references a User that doesn't exist or isn't active in the destination Salesforce org. Solution: pre-create User records for all historical Zendesk agents, or assign ownership to a default service-account User during import and reassign post-migration.

  • STORAGE_LIMIT_EXCEEDED: typical on migrations of 5+ year Zendesk instances. Salesforce storage is metered per org. Solution: archive Cases older than X years before import, or upgrade Salesforce storage allocation pre-migration.

What this actually costs

Concrete cost breakdown for a hypothetical 250,000-ticket migration (typical mid-size Zendesk instance):

DIY:

  • Senior engineer time: 4 weeks @ $200/hour fully-loaded = $32,000
  • 1–2 incidents and rework: typically +$8,000
  • Total: $35,000–$45,000 in engineering opportunity cost

Agency:

  • Fixed project quote: $28,000–$48,000
  • Scope changes during migration: +$5,000–$15,000
  • Total: $30,000–$55,000

Evicta + your engineer for the import side:

  • Flat extraction fee: $1,499
  • Your engineer time managing Salesforce import: 3–5 days, approximately $8,000
  • Total: approximately $9,500–$10,000

The math doesn't favor the agency quote unless you genuinely need someone else to project-manage the full migration.

When you should NOT use Evicta

This section earns the trust that converts the rest of the post. Three honest scenarios:

  • You have under 5,000 tickets, no Sunshine Custom Objects, and fewer than 10 custom fields. Native Zendesk CSV export plus Salesforce Data Import Wizard is fine. Don't pay anyone.

  • You need someone to manage the full migration project, including the Salesforce-side configuration, picklist mapping decisions, user training, and go-live cutover. Hire a migration agency or a Salesforce consultancy. They are good at this and you will not regret paying them.

  • You're on a Zendesk Enterprise plan with multi-region data residency requirements and a hard line that data must never leave your VPC. The Premium tier's Zero-Trust CLI bundle (evicta-zero-trust-cli) runs the full extraction inside your own environment — Evicta's cloud sees zero bytes. The only scenario where DIY is the only path is if your policy prohibits running any externally-generated code whatsoever.


Salesforce API limits verified against official documentation as of May 2026: Bulk API limits cheatsheet · Data Import Wizard limit · PK Chunking reference


Frequently Asked Questions

How long does a Zendesk to Salesforce migration take in 2026?

Extraction: under 24 hours for any volume up to 2 million records with Evicta. 3–8 weeks if writing your own script. 4–8 weeks for a migration agency. Salesforce-side import and configuration: typically 1–3 weeks regardless of which extraction approach you choose.

Does Evicta handle the Salesforce import side?

No. Evicta extracts Zendesk data and produces Postgres or JSONL output (Premium, $1,499) mapped to your Salesforce schema. The Salesforce Bulk API 2.0 import is on your side, or you pair us with a Salesforce consultancy for that piece. We do the hard technical extraction work; the Salesforce-side configuration is genuinely org-specific and benefits from human involvement.

What about Sunshine Custom Objects in a Salesforce migration?

Zendesk Sunshine Custom Objects sunset on July 1, 2026 — after that date the API returns 410 Gone. Evicta extracts the full custom object graph including bidirectional relationship records. The Sunshine-to-Salesforce-Custom-Object mapping is non-trivial because the relationship models differ (Sunshine uses relationship records, Salesforce uses Master-Detail or Lookup fields), but the extraction side gives you everything you need.

Can the extraction run inside our VPC?

Yes. The Premium tier includes a Zero-Trust CLI bundle (evicta-zero-trust-cli). Evicta generates the extraction logic and delivers it as a standalone Node.js bundle you run entirely inside your own AWS, GCP, or on-prem environment. Evicta's cloud never sees a single byte — not the credentials, not the tickets, not the output. HIPAA-eligible. Included in the $1,499 Migration tier.

How are Zendesk custom field names mapped to Salesforce API names?

An LLM samples values inside each opaque Zendesk field ID (custom_field_8321) and proposes a Salesforce-compatible custom field API name (e.g., Customer_Segment__c). You review and approve every mapping in the dashboard before any extraction writes happen. Full audit log included. If the LLM proposes a name that doesn't match your Salesforce org's existing custom field naming conventions, you override it during the review step.

What if the production run fails?

The 100-record preview validates the schema before the paid run. If the production extraction hits a Zendesk API failure we can't recover from, you pay nothing. We've shipped this against the Incremental Export API's documented edge cases (and several undocumented ones) specifically so this rarely happens.

MIGRATION-READY EXTRACTION

We built Evicta for exactly this migration.

Salesforce Service Cloud needs flat schemas, named custom fields, and clean conversation threading. Evicta produces all three in under 24 hours from your Zendesk instance. Flat $1,499. Free 100-record schema preview against your real data, no card required.