---
title: "Zammad to Zendesk Migration: The Technical Guide"
slug: zammad-to-zendesk-migration-the-technical-guide
date: 2026-07-22
author: Abdul
categories: [Migration Guide, Zendesk]
excerpt: "A step-by-step technical guide to migrating from Zammad to Zendesk. Covers API extraction, object mapping, attachment handling, rate limits, and edge cases."
tldr: Zammad to Zendesk migration requires API-to-API extraction using Zammad's REST API and Zendesk's Ticket Import API. No native migration path exists between the two platforms.
canonical: https://clonepartner.com/blog/zammad-to-zendesk-migration-the-technical-guide/
---

# Zammad to Zendesk Migration: The Technical Guide


# Zammad to Zendesk Migration: The Technical Guide

Migrating from Zammad to Zendesk is an API-to-API data translation problem. There is no native import wizard, no built-in connector, and no vendor-provided migration path between the two platforms. Zammad stores ticket conversations as **articles** — individual messages attached to a ticket — while Zendesk uses **comments** within a discrete ticket model. If you need full support history in Zendesk with timestamps, internal notes, attachments, and customer relationships intact, the practical path is extracting via Zammad's REST API and loading through Zendesk's Ticket Import API. ([docs.zammad.org](https://docs.zammad.org/en/latest/api/intro.html?highlight=expand))

This guide covers the full architecture: object mapping, API constraints on both sides, extraction strategies, transform logic, time/cost estimation, and the failure modes that derail teams mid-migration.

**Tested against:** Zammad 6.x REST API and Zendesk API v2 as of mid-2025. Zammad's API can change between major versions — verify endpoint behavior against your installed version before scripting.

For a broader pre-migration framework, see the [Zendesk Migration Checklist](https://clonepartner.com/blog/blog/zendesk-migration-checklist/). For general scoping guidance, see the [Help Desk Data Migration Playbook](https://clonepartner.com/blog/blog/help-desk-data-migration-playbook/).

> [!WARNING]
> There is no official reverse of Zammad's inbound Zendesk migrator. Zammad documents Zendesk → Zammad migration, but for Zammad → Zendesk you must use API-based import workflows. ([docs.zammad.org](https://docs.zammad.org/en/latest/migration/index.html))

## Why Companies Migrate from Zammad to Zendesk

The most common drivers across Zammad-to-Zendesk migration projects:

- **Ecosystem breadth.** Zendesk's marketplace has 1,500+ integrations. Zammad's integration ecosystem is narrower, relying heavily on its REST API for custom connections. Teams that need native connectors to Salesforce, Shopify, or Jira hit limits with Zammad first.
- **Managed SaaS operations.** Zammad can be self-hosted, which means your team owns infrastructure, upgrades, and database maintenance. Moving to Zendesk shifts that operational burden to the vendor.
- **AI and automation.** Zendesk's investment in AI — Intelligent Triage, AI Agents, Answer Bot — is tightly coupled with its ticketing data model. Zammad's AI features are newer and less mature.
- **Reporting and compliance.** Zendesk Explore provides built-in analytics dashboards, CSAT surveys, SLA enforcement, and agent capacity metrics out of the box. Zammad's reporting is more limited and often requires custom queries or external BI tools.
- **Team scaling.** Organizations growing past 20–30 agents often find Zendesk's role-based permissions, multi-brand support, and enterprise security features (SSO, audit logs, data locality) more aligned with their needs.

If cost is the headline reason for evaluating platforms, look at total operating cost rather than seat price alone. Zammad often wins on pure licensing, while Zendesk can win on day-to-day administration and ecosystem breadth.

## Zammad vs Zendesk: Core Data Model Differences

Before you map a single field, understand the structural gap between these two platforms.

| Concept | Zammad | Zendesk |
|---|---|---|
| Ticket conversations | **Articles** — each message, note, or phone call is a separate article with its own `type`, `sender`, and `internal` flag | **Comments** — each reply or note is a comment on the ticket, marked `public` or not |
| Customer records | **Users** (role-based: customer, agent, admin) | **Users** (split into end-users and agents with separate permissions) |
| Companies | **Organizations** | **Organizations** |
| Groups/Teams | **Groups** (used for routing and permissions) | **Groups** (used for assignment and views) |
| Custom fields | **Object attributes** on tickets, users, organizations, and groups | **Custom fields** on tickets, users, organizations; **Custom objects** for extended data |
| Knowledge base | **Knowledge Base** with categories and answers (multilingual via translations) | **Help Center** (Zendesk Guide) with sections and articles |
| Tags | Tags on tickets | Tags on tickets, users, organizations |
| Attachments | Stored per article, fetched via article attachment endpoint | Stored per comment, uploaded via Upload API and referenced by token |

The key translation challenge: Zammad **articles** map to Zendesk **comments**, but the metadata structure differs. Zammad articles carry a `type` field (email, phone, note, web, sms, chat, social) and a `sender` field (Customer, Agent, System). Zendesk comments are simpler — they're either `public` or internal, with an `author_id`. Some source-channel nuance gets flattened in the translation. ([docs.zammad.org](https://docs.zammad.org/en/latest/api/ticket/articles.html))

## Migration Approaches Compared

### 1. API-Based Migration (Recommended)

**How it works:** Extract all data from Zammad using its REST API (`/api/v1/`), transform it in a middle layer, and load it into Zendesk using the Ticket Import API (`/api/v2/imports/tickets`) and standard object APIs. For batch creation, Zendesk also offers `/api/v2/imports/tickets/create_many` which accepts up to 100 tickets per request and returns a job status for tracking. ([developer.zendesk.com](https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_import/))

**When to use it:** Any migration where you need full ticket history, attachments, timestamps, and relationship integrity.

**Pros:**
- Full control over data mapping and transformation
- Preserves timestamps (`created_at`, `updated_at`, `solved_at`)
- Handles attachments, internal notes, and custom fields
- Can be scripted for repeatability and test runs
- Batch endpoint (`create_many`) reduces total API calls

**Cons:**
- Requires engineering time to build and test
- Must handle pagination, rate limiting, and error recovery
- Zendesk Ticket Import API has specific constraints (no SLA metrics, no side conversations)

**Complexity:** High

### 2. CSV Export/Import

**How it works:** Export flat records from Zammad (or derive CSVs from API pulls) and import into Zendesk via its data importer for users, organizations, and custom object records. ([support.zendesk.com](https://support.zendesk.com/hc/en-us/articles/4408893496218-Bulk-importing-user-data-with-the-data-importer))

**When to use it:** Only for migrating users and organizations — not full ticket history.

**Pros:**
- No code required for basic imports
- Fast to test

**Cons:**
- Zammad has no native CSV export for ticket history
- Zendesk's CSV importer doesn't handle full ticket conversation history
- No attachment support
- No timestamp preservation for tickets

**Complexity:** Low

### 3. Direct Database Access (Self-Hosted Zammad Only)

**How it works:** If you run self-hosted Zammad, query the PostgreSQL database directly to extract tickets, articles, users, organizations, and attachments. Transform with SQL or ETL scripts, then load into Zendesk via API.

**When to use it:** Large migrations (100K+ tickets) where API extraction would be too slow, or when you need data that the API doesn't expose.

Key tables in Zammad's PostgreSQL schema (Rails conventions):
- `tickets` — core ticket records
- `ticket_articles` — individual messages/notes per ticket
- `ticket_article_types` — lookup for article type names
- `ticket_article_senders` — lookup for sender types
- `stores` — attachment metadata; binary content stored in `store_files` or on filesystem depending on storage adapter
- `users`, `organizations`, `groups` — standard entity tables
- `object_manager_attributes` — custom field definitions
- `tags`, `tag_objects`, `tag_items` — tag associations

**Pros:**
- Fastest extraction method
- Access to all data including audit trails and internal metadata
- No API rate limit concerns on the source side

**Cons:**
- Only works for self-hosted Zammad instances
- Requires understanding Zammad's database schema (Ruby on Rails conventions)
- Schema may change between Zammad versions — verify against your installed version
- Still need Zendesk API for loading

**Complexity:** High

### 4. Third-Party Migration Tools

**How it works:** Platforms like Help Desk Migration offer pre-built connectors for helpdesk-to-helpdesk moves. Connect source and target accounts, map fields, run a demo migration, validate, then run full and optional delta passes. ([help-desk-migration.com](https://help-desk-migration.com/zammad/))

**When to use it:** Small to mid-size migrations where speed matters more than customization.

**Pros:**
- Pre-built mapping for common objects
- Visual interface, less engineering required
- Often include demo/trial migration

**Cons:**
- Zammad is not commonly supported by most third-party migration tools
- Limited control over field-level transformations
- Custom fields and complex relationships may not map correctly
- Per-record pricing can get expensive at scale

**Complexity:** Low–Medium

### 5. Middleware / iPaaS (Zapier, Make, n8n)

**How it works:** Use an integration platform to connect Zammad triggers/API to Zendesk actions. Typically event-driven, not suited for bulk historical migration. Zammad webhooks are event-driven and retry failed deliveries up to four times. ([admin-docs.zammad.org](https://admin-docs.zammad.org/en/4.x/manage/webhook.html))

**When to use it:** Ongoing sync of new tickets or incremental forwarding during a phased transition — not historical migration.

**Pros:**
- No code for simple workflows
- Good for post-migration sync

**Cons:**
- Not designed for bulk data migration
- Extremely slow for historical data
- Rate limit issues on both sides
- No timestamp preservation for historical records

**Complexity:** Low

### Approach Comparison Table

| Method | Ticket History | Attachments | Timestamps | Custom Fields | Complexity | Best For |
|---|---|---|---|---|---|---|
| API-to-API | ✅ Full | ✅ Yes | ✅ Preserved | ✅ Mapped | High | Any migration |
| CSV Export/Import | ❌ No | ❌ No | ❌ No | Partial | Low | Users/orgs only |
| Direct DB Access | ✅ Full | ✅ Yes | ✅ Preserved | ✅ All | High | Self-hosted, large datasets |
| Third-Party Tools | Partial | Varies | Varies | Limited | Low–Med | Small, standard migrations |
| Middleware/iPaaS | ❌ No (live only) | Varies | ❌ No | Limited | Low | Ongoing sync |

### Which Approach to Choose

- **Small team, < 5K tickets, no custom fields:** CSV for users + a third-party tool for tickets (if one supports Zammad). Otherwise, API-to-API.
- **Mid-market, 5K–100K tickets, custom fields:** API-to-API migration. No shortcut.
- **Enterprise, 100K+ tickets, self-hosted Zammad:** Direct database extraction + Zendesk API loading.
- **Ongoing sync needed post-migration:** API-to-API for historical data, then middleware or Zammad webhooks for forward sync.
- **Low engineering bandwidth:** Third-party tool or managed migration service.

## Migration Time & Cost Estimation

Before committing to an approach, estimate the API call volume and wall-clock time.

### API Call Formula

For an API-to-API migration with `T` tickets, `A` average articles per ticket, and `F` average attachments per article:

```
Total API calls ≈
    (T / 100)                          # Zammad ticket pagination
  + T                                   # Zammad article fetches (1 per ticket)
  + (T × A × F)                        # Zammad attachment downloads
  + ceil(T × A × F)                    # Zendesk attachment uploads (1 per file)
  + (users / 100)                       # Zammad user pagination
  + users                               # Zendesk user create_or_update
  + ceil(T / 100)                       # Zendesk ticket import (using create_many, 100/batch)
```

### Example: 50,000 Tickets

Assumptions: 50K tickets, 4 articles/ticket avg, 0.3 attachments/article avg, 5K users.

| Phase | API Calls | Target |
|---|---|---|
| Extract tickets (paginated) | 500 | Zammad |
| Extract articles | 50,000 | Zammad |
| Download attachments | 60,000 | Zammad |
| Upload attachments | 60,000 | Zendesk |
| Create/update users | 5,000 | Zendesk |
| Import tickets (batched at 100) | 500 | Zendesk |
| **Total Zendesk API calls** | **~65,500** | |

### Wall-Clock Time by Plan

| Zendesk Plan | Rate Limit | Zendesk-Side Time (est.) | Total with Extraction (est.) |
|---|---|---|---|
| Team (200 req/min) | 200/min | ~5.5 hours | 8–12 hours |
| Professional (700 req/min) | 700/min | ~1.6 hours | 4–6 hours |
| Enterprise (2,500 req/min) | 2,500/min | ~26 min | 2–4 hours |

These are optimistic lower bounds. In practice, add 30–50% for retries, rate limit backoff, error handling, and Zammad-side extraction time. Attachment-heavy instances (e.g., insurance, legal) may double these figures.

### Engineering Time

| Task | Estimated Hours |
|---|---|
| Data audit and field mapping | 4–8 |
| Script development (extraction, transform, load) | 40–80 |
| Attachment handling logic | 8–16 |
| Error handling, retry logic, checkpointing | 8–16 |
| Test migration (sandbox) × 2 runs | 8–16 |
| Validation and debugging | 8–24 |
| Cutover execution and monitoring | 4–8 |
| **Total** | **80–168 hours** |

## When to Use a Managed Migration Service

Building an API-to-API migration script in-house is technically straightforward on paper. In practice, it breaks down in predictable ways:

- **Attachment handling** doubles the complexity — you must download from Zammad, upload to Zendesk's Upload API, retrieve a token, and attach it to the correct comment.
- **Rate limiting** on Zendesk's side means a 50K-ticket migration can take days if not properly batched and throttled.
- **Data validation** across two different schemas requires automated comparison tooling that most teams build ad-hoc and never reuse.
- **Edge cases** — suspended users, empty comment bodies, inline images, merged tickets — each require specific handling that only surfaces during testing.
- **Engineering opportunity cost** — every sprint your team spends on migration scripts is a sprint not spent on product.

A script that works for 100 tickets will often fail at 100,000 tickets due to undocumented API behaviors, rate limit throttling, and malformed historical data.

**Common DIY failure modes:**

- **Overwritten timestamps:** Using the standard Zendesk Tickets API instead of the Import API timestamps every historical ticket with the date of the migration.
- **Broken inline images:** Zammad stores inline images as CID references. If these aren't extracted, uploaded to Zendesk, and re-linked in the HTML body, you end up with broken image icons in every historical ticket.
- **Suspended account rejections:** Migrating tickets tied to agents who no longer work at the company can cause Zendesk to reject the payload entirely.

If you're evaluating build vs. buy, [the comparison is worth reading](https://clonepartner.com/blog/blog/in-house-vs-outsourced-data-migration/).

## Pre-Migration Planning

Before writing a single line of migration code, audit your Zammad instance and define the scope of the move.

### Data Audit Checklist

- [ ] **Tickets:** Total count, date range, status distribution (open, closed, pending)
- [ ] **Articles per ticket:** Average and max — this determines Zendesk API call volume (see estimation formula above)
- [ ] **Users:** Total customers, agents, and admins. Identify suspended or inactive users
- [ ] **Organizations:** Count, membership sizes, custom fields
- [ ] **Custom object attributes:** List all custom fields on tickets, users, and organizations. Note data types (text, select, date, boolean, integer)
- [ ] **Tags:** Collect all tags in use across tickets
- [ ] **Knowledge Base:** Category count, answer count, languages, attachments
- [ ] **Attachments:** Total size, largest individual attachment (Zendesk limit: 50 MB/file), file types
- [ ] **Groups:** Current group structure and routing rules
- [ ] **Zammad version:** Check `GET /api/v1/version` — API behavior varies across major versions

### Data Residency & Compliance

For organizations subject to GDPR, HIPAA, or other data protection frameworks:

- **Data transfer mechanisms:** If your Zammad instance is hosted in the EU and your Zendesk instance is US-based, ensure Standard Contractual Clauses (SCCs) or equivalent transfer mechanisms are in place before moving personal data.
- **Encryption in transit:** Both Zammad and Zendesk APIs support TLS 1.2+. Verify your migration scripts enforce HTTPS and do not write unencrypted PII to intermediate storage (local disk, staging databases).
- **Data minimization:** Filter out data you don't need before extraction. Migrating PII you intend to delete anyway increases regulatory exposure.
- **Right to erasure:** If you have pending deletion requests in Zammad, process them before migration. Don't migrate data that should have been deleted.
- **Data Processing Agreements:** Confirm your Zendesk DPA covers the data categories you're migrating. Zendesk publishes its DPA at [zendesk.com/company/agreements-and-terms/data-processing-agreement](https://www.zendesk.com/company/agreements-and-terms/data-processing-agreement/).

### Define Migration Scope

Not everything needs to move. Common exclusions:

- **Spam and test tickets** — filter by tag or status before extraction
- **Tickets older than 2–3 years** — unless regulatory requirements dictate otherwise
- **System-generated articles** — Zammad creates system notes for status changes and group reassignments. Decide during scoping whether to import them as internal notes or skip them
- **Audit trails and time accounting** — Zendesk doesn't import these; archive separately
- **Unused custom fields** — delete or ignore before mapping to Zendesk

### Zendesk Sandbox Provisioning

Test migrations require a Zendesk sandbox environment:

- **Zendesk Professional and Enterprise plans** include a sandbox (called "Premium Sandbox" on Enterprise). It mirrors your production configuration.
- **Team and Growth plans** do not include a sandbox. You can request a trial instance from Zendesk sales, or create a separate Zendesk trial account for testing.
- Sandboxes have the same API rate limits as your production plan tier.

### Migration Strategy

| Strategy | Description | When to Use |
|---|---|---|
| **Big bang** | Migrate everything in a single cutover window | Small datasets (< 10K tickets), tolerance for brief downtime |
| **Phased** | Migrate by group, date range, or ticket status in stages | Mid-size datasets, need to validate each batch |
| **Incremental (big bang + delta sync)** | Migrate historical data first, then sync new data in near-real-time until cutover | Large datasets, zero-downtime requirement |

For most large Zammad-to-Zendesk moves, the **incremental approach** works best: migrate closed/archived tickets first (using Zendesk's `archive_immediately` flag), validate, then run a final delta migration on cutover weekend to catch tickets created or updated since the initial sync. For the delta pass, Zammad's webhook system can trigger extraction of newly created or updated tickets, feeding them into the same transform-and-load pipeline.

## Data Model & Object Mapping

This is the most important section. Get the mapping wrong and everything downstream breaks.

A **crosswalk** is a lookup table that maps IDs from the source system to IDs in the target system (e.g., `{zammad_user_id: 42 → zendesk_user_id: 98765}`). You build it during user/org creation and reference it when constructing ticket payloads.

### Core Object Mapping

| Zammad Object | Zendesk Object | Notes |
|---|---|---|
| Ticket | Ticket | 1:1 mapping. Use Ticket Import API (`/api/v2/imports/tickets`) or batch endpoint (`/api/v2/imports/tickets/create_many`) |
| Ticket Article | Ticket Comment | Map `internal` flag → `public: false`. Map article `type` and `sender` to determine comment author |
| User (customer) | User (end-user) | Create via Users API before importing tickets |
| User (agent) | User (agent) | Must exist in Zendesk before ticket import — tickets reference `assignee_id` and `author_id` |
| Organization | Organization | Create via Organizations API. Map `organization_id` on users |
| Group | Group | Create manually or via API. Map `group_id` on tickets |
| Tag | Tag | Applied to tickets during import |
| Knowledge Base Category | Help Center Section | Requires Zendesk Guide. Map category hierarchy |
| Knowledge Base Answer | Help Center Article | HTML content migration. Handle embedded images |
| Custom Object Attribute | Custom Ticket/User/Org Field | Must create fields in Zendesk first with matching data types |

> [!WARNING]
> **Critical:** Zendesk's Ticket Import API requires that all referenced users (`requester_id`, `assignee_id`, `author_id`) exist before ticket creation. Always create users and organizations first. Decide whether to provision paid agent seats for former employees or map their historical tickets to a generic "Legacy Agent" account.

### Ticket State Mapping

Zammad states do not align perfectly with Zendesk. You must implement a translation layer:

| Zammad State | Zendesk Status | Notes |
|---|---|---|
| `new` | `new` | Direct map |
| `open` | `open` | Direct map |
| `pending reminder` | `pending` | Map `pending_time` to Zendesk's due date if applicable |
| `pending close` | `pending` | Collapsed with pending reminder |
| `closed` | `closed` | See immutability warning below |
| `merged` | `closed` | Add internal note or tag referencing original ticket number |

> [!WARNING]
> Zendesk strictly enforces the `closed` status. Once a ticket is set to `closed` via the API, it can never be updated again. Ensure all comments, tags, and attachments are correct in the payload before setting this status.

### Field-Level Mapping: Tickets

| Zammad Field | Zendesk Field | Transformation |
|---|---|---|
| `number` | `external_id` | Preserves original ticket number for agent cross-reference |
| `title` | `subject` | Direct map |
| `state.name` | `status` | See state mapping table above |
| `priority.name` | `priority` | Map: `1 low` → `low`, `2 normal` → `normal`, `3 high` → `high` |
| `group.name` | `group_id` | Lookup group ID in Zendesk by name via crosswalk |
| `owner_id` | `assignee_id` | Map Zammad user ID → Zendesk user ID via crosswalk table |
| `customer_id` | `requester_id` | Map Zammad user ID → Zendesk user ID via crosswalk table |
| `created_at` | `created_at` | Direct map (ISO 8601). Must use Import API to set |
| `updated_at` | `updated_at` | Direct map |
| `close_at` | `solved_at` | Only for closed tickets |
| `tags` | `tags` | Array → comma-separated or array |

### Field-Level Mapping: Articles → Comments

| Zammad Article Field | Zendesk Comment Field | Transformation |
|---|---|---|
| `body` | `html_body` | Zammad stores HTML. Requires inline image URL rewriting |
| `internal` | `public` | Invert: `internal: true` → `public: false` |
| `created_by_id` | `author_id` | Map via user crosswalk |
| `created_at` | `created_at` | Direct map |
| `attachments` | `uploads` | Download from Zammad, upload to Zendesk, attach via token |

### Handling Custom Fields

Zammad lets you define custom object attributes on tickets, users, organizations, and groups. Before migration:

1. **Export your Zammad object definitions** via `GET /api/v1/object_manager_attributes` to list all custom fields with their data types and options ([docs.zammad.org](https://docs.zammad.org/en/latest/api/object.html))
2. **Create matching fields in Zendesk** — ticket fields, user fields, or organization fields. Note that Zendesk field types may not map 1:1 (e.g., Zammad's `richtext` has no direct equivalent in Zendesk custom fields; convert to `textarea` or `text`)
3. **Map picklist values** — if Zammad uses a `select` or `tree_select` field, ensure every option value exists in the Zendesk dropdown. Zammad's `tree_select` (hierarchical) must be flattened to a single-level dropdown in Zendesk or encoded with a delimiter (e.g., `Parent::Child`)

> [!NOTE]
> Zendesk supports custom objects, but they're plan-gated with limits: 3, 5, 30, or 50 custom objects depending on plan, up to 100 fields per object, and up to 5 or 10 lookup relationship fields per object. ([support.zendesk.com](https://support.zendesk.com/hc/en-us/articles/5914453843994-Understanding-custom-objects))

## Migration Architecture

The architecture follows a standard Extract, Transform, Load (ETL) pattern, but requires specific sequencing to maintain relational integrity.

### Data Flow

```
┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Zammad     │     │   Transform      │     │    Zendesk      │
│   REST API   │────▶│   Layer          │────▶│   Import API    │
│   /api/v1/   │     │   (Python/Node)  │     │   /api/v2/      │
└─────────────┘     └──────────────────┘     └─────────────────┘
     Extract              Transform                Load

  • Tickets            • Field mapping          • Users/Orgs API
  • Articles           • Status translation     • Ticket Import API
  • Users              • User ID crosswalk      • Upload API
  • Organizations      • Attachment download    • Help Center API
  • KB Content         • HTML sanitization
  • Attachments        • CID image rewriting
```

### Dependency Sequence

Relational integrity requires loading objects in this order:

1. Extract and load **Organizations**
2. Extract and load **Users** (map to Organizations)
3. Extract and load **Groups**
4. Extract **Tickets** with their **Articles** and **Attachments**
5. Upload **Attachments** to Zendesk (get tokens)
6. Load **Tickets** with comments and attachment tokens via Ticket Import API
7. Migrate **Knowledge Base** content (see KB section below)

### Zammad API: Extraction Details

Zammad's API follows REST conventions at `/api/v1/`. Key endpoints for extraction:

- **Tickets:** `GET /api/v1/tickets?page={n}&per_page={n}` — returns up to 100 tickets per page
- **Ticket Articles:** `GET /api/v1/ticket_articles/by_ticket/{ticket_id}` — all articles for a ticket
- **Users:** `GET /api/v1/users?page={n}&per_page={n}`
- **Organizations:** `GET /api/v1/organizations?page={n}&per_page={n}`
- **Groups:** `GET /api/v1/groups`
- **Tags:** `GET /api/v1/tags?object=Ticket&o_id={ticket_id}`
- **Object Attributes:** `GET /api/v1/object_manager_attributes`
- **Attachments:** `GET /api/v1/ticket_attachment/{ticket_id}/{article_id}/{attachment_id}`
- **Knowledge Base:** `GET /api/v1/knowledge_bases` (categories and answers accessible via sub-endpoints)

Authentication: Token-based (`Authorization: Token token={your_token}`) is recommended over basic auth. ([docs.zammad.org](https://docs.zammad.org/en/latest/api/intro.html?highlight=expand))

> [!NOTE]
> **Pagination limit:** Zammad enforces a hard maximum of 100 objects per page. You cannot raise this limit. Use `page` and `per_page` parameters and iterate until the response returns fewer items than `per_page`. Adding `expand=true` includes related object names inline, reducing follow-up lookups.

### Zendesk API: Loading Details

- **Users:** `POST /api/v2/users/create_or_update`
- **Organizations:** `POST /api/v2/organizations/create_or_update`
- **Ticket Import (single):** `POST /api/v2/imports/tickets` — supports multiple comments per ticket, timestamp overrides, and `archive_immediately` for closed tickets ([developer.zendesk.com](https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_import/))
- **Ticket Import (batch):** `POST /api/v2/imports/tickets/create_many` — accepts up to 100 tickets per request, returns a `job_status` URL for polling completion
- **Attachments:** `POST /api/v2/uploads` — returns a token to reference in comment payloads
- **Help Center Articles:** `POST /api/v2/help_center/sections/{section_id}/articles`

**Rate limits by plan:**

| Zendesk Plan | Requests/Minute |
|---|---|
| Team | 200 |
| Growth | 400 |
| Professional | 700 |
| Enterprise | 2,500 |

The Ticket Import endpoint shares the account-level rate limit. For a Professional plan, you can create ~700 tickets per minute under ideal conditions. In practice, attachment uploads and user lookups consume part of that budget.

> [!WARNING]
> **Zendesk Ticket Import API constraints:** Triggers don't fire on imported tickets. SLA metrics (first reply time, resolution time) are not calculated for imported tickets. No side conversation support on imported tickets. Requesters must be active — suspended users will cause the import to fail with a `422 Unprocessable Entity`. ([support.zendesk.com](https://support.zendesk.com/hc/en-us/articles/6696005837082-How-can-I-migrate-information-from-another-platform-into-Zendesk))

> [!TIP]
> If you're building a new migration utility in mid-2026 or later, prefer Zendesk OAuth for authentication. Zendesk has announced that API tokens will be permanently deactivated on **April 30, 2027**. ([developer.zendesk.com](https://developer.zendesk.com/documentation/api-basics/authentication/oauth-migration/))

## Step-by-Step Migration Process

### Step 1: Extract Data from Zammad

Paginate through all endpoints and store the raw JSON responses. Use a staging database (PostgreSQL, MongoDB, or local JSON files) to prevent re-querying Zammad during transformation failures. Encrypt staging storage if it contains PII.

```python
import requests

ZAMMAD_URL = "https://your-zammad.example.com"
ZAMMAD_TOKEN = "your-api-token"
headers = {"Authorization": f"Token token={ZAMMAD_TOKEN}"}

def extract_all(endpoint, per_page=100):
    page = 1
    all_records = []
    while True:
        resp = requests.get(
            f"{ZAMMAD_URL}/api/v1/{endpoint}",
            headers=headers,
            params={"page": page, "per_page": per_page, "expand": "true"}
        )
        resp.raise_for_status()
        data = resp.json()
        if not data:
            break
        all_records.extend(data)
        if len(data) < per_page:
            break
        page += 1
    return all_records

tickets = extract_all("tickets")
users = extract_all("users")
orgs = extract_all("organizations")
```

For articles and attachments, iterate per ticket:

```python
def extract_articles(ticket_id):
    resp = requests.get(
        f"{ZAMMAD_URL}/api/v1/ticket_articles/by_ticket/{ticket_id}",
        headers=headers
    )
    resp.raise_for_status()
    return resp.json()

def download_attachment(ticket_id, article_id, attachment_id):
    resp = requests.get(
        f"{ZAMMAD_URL}/api/v1/ticket_attachment/{ticket_id}/{article_id}/{attachment_id}",
        headers=headers
    )
    resp.raise_for_status()
    return resp.content  # binary data
```

### Step 2: Build the User Crosswalk

Create users and organizations in Zendesk first. Store the mapping: `{zammad_user_id: zendesk_user_id}`.

```python
ZENDESK_URL = "https://your-instance.zendesk.com"
ZENDESK_AUTH = ("admin@company.com/token", "your-zendesk-token")

def create_zendesk_user(zammad_user):
    payload = {
        "user": {
            "name": f"{zammad_user['firstname']} {zammad_user['lastname']}",
            "email": zammad_user["email"],
            "role": "end-user",  # or "agent" based on Zammad role
            "external_id": str(zammad_user["id"])
        }
    }
    resp = requests.post(
        f"{ZENDESK_URL}/api/v2/users/create_or_update",
        auth=ZENDESK_AUTH,
        json=payload
    )
    resp.raise_for_status()
    return resp.json()["user"]["id"]
```

### Step 3: Transform and Load Tickets

For each Zammad ticket, build the Zendesk Ticket Import payload with all comments:

```python
def build_zendesk_ticket(zammad_ticket, articles, user_map, group_map):
    comments = []
    for article in articles:
        comment = {
            "author_id": user_map.get(article["created_by_id"]),
            "html_body": rewrite_inline_images(
                article.get("body", ""),
                article,
                zammad_ticket["id"]
            ),
            "public": not article.get("internal", False),
            "created_at": article["created_at"]
        }
        # Handle attachments if present
        if article.get("attachments"):
            tokens = upload_attachments(article, zammad_ticket["id"])
            comment["uploads"] = tokens
        # Zendesk rejects empty comment bodies
        if not comment["html_body"] or not comment["html_body"].strip():
            comment["html_body"] = "[Attachment only]"
        comments.append(comment)

    status_map = {
        "new": "new", "open": "open",
        "pending reminder": "pending", "pending close": "pending",
        "closed": "closed", "merged": "closed"
    }

    return {
        "ticket": {
            "subject": zammad_ticket.get("title", "No subject"),
            "requester_id": user_map.get(zammad_ticket["customer_id"]),
            "assignee_id": user_map.get(zammad_ticket.get("owner_id")),
            "group_id": group_map.get(
                zammad_ticket["group_id"],
                DEFAULT_FALLBACK_GROUP_ID  # for tickets without group assignment
            ),
            "status": status_map.get(zammad_ticket.get("state"), "open"),
            "external_id": str(zammad_ticket["number"]),
            "created_at": zammad_ticket["created_at"],
            "updated_at": zammad_ticket["updated_at"],
            "tags": zammad_ticket.get("tags", []),
            "comments": comments
        }
    }
```

### Step 4: Upload Attachments

```python
def upload_attachments(article, ticket_id):
    tokens = []
    for att in article["attachments"]:
        # Check Zendesk's 50 MB per-file limit
        if att.get("size", 0) > 50 * 1024 * 1024:
            log.warning(f"Skipping oversized attachment {att['filename']} "
                       f"({att['size']} bytes) on ticket {ticket_id}")
            continue
        content = download_attachment(ticket_id, article["id"], att["id"])
        resp = requests.post(
            f"{ZENDESK_URL}/api/v2/uploads?filename={att['filename']}",
            auth=ZENDESK_AUTH,
            headers={"Content-Type": att.get("preferences", {}).get(
                "Content-Type", "application/octet-stream"
            )},
            data=content
        )
        resp.raise_for_status()
        tokens.append(resp.json()["upload"]["token"])
    return tokens
```

### Step 5: Import with Rate Limit Handling

```python
import time
import logging

log = logging.getLogger(__name__)

def import_ticket(ticket_payload, max_retries=5):
    for attempt in range(max_retries):
        resp = requests.post(
            f"{ZENDESK_URL}/api/v2/imports/tickets.json",
            auth=ZENDESK_AUTH,
            json=ticket_payload
        )
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 60))
            log.warning(f"Rate limited. Retry-After: {retry_after}s")
            time.sleep(retry_after)
            continue
        if resp.status_code == 422:
            # Common causes: suspended user, missing required field,
            # empty comment body, invalid group_id
            log.error(f"422 Unprocessable Entity: {resp.json()}")
            # Example error response:
            # {"error": "RecordInvalid",
            #  "description": "Validation failed: Requester is suspended"}
            return None
        resp.raise_for_status()
        return resp.json()
    raise Exception(f"Failed after {max_retries} retries")

# Production implementation requirements beyond this pseudocode:
# - Checkpointing: persist last-processed ticket ID to resume after crashes
# - Crosswalk persistence: store {zammad_id: zendesk_id} mappings in a database
# - Delta sync: track tickets modified after initial extraction timestamp
# - Structured logging: log every success/failure with ticket IDs for audit
# - Idempotency: use external_id to prevent duplicate imports on re-runs
```

### Step 6: Validate the Migration

After loading, run validation checks:

- **Record counts:** Compare ticket, user, and organization counts between source and target
- **Field spot-checks:** Sample 50–100 tickets and compare field values side by side
- **Attachment verification:** Confirm attachments are accessible and correctly linked
- **Relationship integrity:** Verify that tickets point to the correct requester, assignee, and organization
- **Timestamp accuracy:** Confirm `created_at` timestamps match original Zammad values
- **Internal notes:** Verify internal notes are not visible to end-users in the Help Center

## Knowledge Base Migration

The Knowledge Base is often mentioned as a line item but requires its own migration workflow. Zammad's Knowledge Base uses a **category → answer** hierarchy with built-in multilingual support via `translation_ids`. Zendesk Guide uses a **category → section → article** three-level hierarchy.

### Mapping Structure

| Zammad KB Object | Zendesk Guide Object | Notes |
|---|---|---|
| Knowledge Base | Help Center | 1:1; Zendesk Guide must be enabled |
| Category (top-level) | Category | `POST /api/v2/help_center/categories` |
| Category (nested) | Section | `POST /api/v2/help_center/categories/{id}/sections` |
| Answer | Article | `POST /api/v2/help_center/sections/{id}/articles` |
| Translation | Article Translation | Separate article per locale, or use `POST /api/v2/help_center/articles/{id}/translations` |

### Implementation Steps

1. **Extract KB structure:** `GET /api/v1/knowledge_bases` returns the knowledge base with nested categories. Extract answers via category endpoints.
2. **Create category/section hierarchy** in Zendesk Guide before creating articles.
3. **Migrate article HTML content:** Zammad KB answers store HTML bodies. Download any embedded images, upload them to Zendesk via the Attachments API or host them externally, and rewrite `src` attributes in the HTML.
4. **Handle translations:** For each Zammad answer with multiple translations, create the article in the default locale first, then add translations via the Zendesk Translations API. Zendesk requires that the Help Center has the target locale enabled before translations can be added.
5. **Verify rendering:** Check that tables, code blocks, and formatted content render correctly in Zendesk Guide — CSS differences between platforms can break visual layouts.

### KB Migration Limitations

- Zammad KB supports **draft** and **published** states per translation; Zendesk Guide uses **draft** and **published** per article. Ensure draft articles don't accidentally publish.
- Zendesk Guide article body size limit is **65,535 characters** (approximately 64 KB of HTML).
- Internal-only KB articles in Zammad should map to articles in a restricted Zendesk Guide section (requires Zendesk Guide Professional or Enterprise for user segment restrictions).

## Edge Cases & Challenges

These are the issues that surface mid-migration and cost teams days.

### Inline Images (CID References)

Zammad frequently stores images pasted into emails as inline CID (Content-ID) attachments. The HTML body references them with `<img src="cid:image001.png@01D...">`. Your script must:

1. Parse the HTML body of each Zammad article
2. Identify `cid:` references in `src` attributes
3. Match each CID to the corresponding attachment in the article's attachment list
4. Download the attachment binary from Zammad
5. Upload it to Zendesk's Upload API
6. Rewrite the `src` attribute to the Zendesk-hosted URL

If you skip this, every historical ticket with inline images will display broken image icons.

### Suspended Users

Zendesk's Ticket Import API requires that requesters and submitters are **active** users. If a former customer or ex-employee was created and then suspended, the import will fail with:

```json
{
  "error": "RecordInvalid",
  "description": "Validation failed: Requester is suspended"
}
```

**Workaround:** Pre-check all users after creation. Unsuspend users before import, or remap their tickets to a placeholder account.

### Empty Comment Bodies

Zammad can create articles with no body (e.g., an email with only an attachment). Zendesk's API rejects empty comment bodies with a `422`. **Workaround:** Insert a placeholder like `" [Attachment only]"` for empty bodies.

### Merged Tickets

Zammad supports ticket merging. There is no way to import pre-merged tickets via the Zendesk API — you cannot reserve ticket IDs to recreate cross-references. **Workaround:** Import merged tickets as closed tickets with an internal note or tag referencing the original ticket number (stored in `external_id`).

### Missing Group Assignment

Zammad allows tickets to exist without an explicitly assigned group in certain configurations. Zendesk requires a group for every ticket. Assign a default fallback group in your ETL logic for orphaned tickets.

### Multi-Organization Users

If a Zammad user belongs to multiple organizations, Zendesk supports this — but you must enable the multi-organization feature in Zendesk settings before running your user import. Otherwise the API will reject secondary organization mappings. ([docs.zammad.org](https://docs.zammad.org/en/pre-release/api/organization.html))

### Semantic Flattening of Article Types

Zammad article types like `email`, `phone`, `note`, `sms`, `chat`, and social types will not always survive as distinct behaviors in Zendesk comments. The channel metadata gets flattened to public/private comments with an author. Consider preserving the original article type as a tag (e.g., `zammad_type:phone`) or in the comment body prefix (e.g., `[Phone Call]`) if this context matters to your team.

### Duplicate Records from Re-Runs

If you run the migration script multiple times (test runs + final run), you'll create duplicate tickets. Use `external_id` on tickets and `create_or_update` for users to enable idempotent operations. Before the final run, bulk-delete all test data from the sandbox or production instance.

### Very Large Tickets

Zendesk tickets can contain up to **5,000 comments**. Tickets above that limit need splitting, archiving, or a partial-history strategy. In Zammad, long-running tickets with extensive back-and-forth or automated system notes can exceed this. Check your max articles-per-ticket count during the data audit. ([developer.zendesk.com](https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_comments/))

### Zendesk 429 Rate Limit Response

When you hit rate limits, Zendesk returns:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 42

{
  "error": "TooManyRequests",
  "description": "You have exceeded the rate limit. Please retry after 42 seconds."
}
```

The `Retry-After` header value is in seconds. Build your retry logic around this header, not hardcoded sleep values.

## Limitations & Constraints

Be explicit about what you will lose or compromise in this migration:

- **SLA metrics:** Zendesk does not calculate SLA metrics on imported tickets. Tag imported tickets and exclude them from SLA reports. ([support.zendesk.com](https://support.zendesk.com/hc/en-us/articles/6696005837082-How-can-I-migrate-information-from-another-platform-into-Zendesk))
- **Comment immutability:** Once a comment is created in Zendesk, it cannot be edited via the API. If your transformation script injects the wrong text, you must delete the entire ticket and re-import it.
- **API throttling:** Zendesk's rate limits are strictly enforced. Migrating 500,000+ tickets will take several days purely due to API speed limits. Plan your cutover window accordingly. (See time estimation section above.)
- **Side conversations:** The Ticket Import API does not support side conversations. Convert them to internal comments or skip them.
- **Time tracking:** Zammad's time accounting data doesn't have a native equivalent in Zendesk. Store as a custom field or internal note.
- **Ticket audit logs:** Zammad's detailed change history (who changed what, when) does not import into Zendesk. Archive separately (e.g., export to CSV or a data warehouse).
- **Large attachments:** Zendesk's ticket attachment limit is **50 MB per file**. Oversized files need an external archive or link-out strategy. ([support.zendesk.com](https://support.zendesk.com/hc/en-us/articles/4408832757146-Allowing-attachments-in-tickets))
- **Satisfaction ratings:** Zammad doesn't have built-in CSAT. If you've built custom satisfaction tracking, it won't map to Zendesk's native CSAT.
- **Custom objects:** Zendesk's Custom Object architecture is structurally different from Zammad's custom attributes and uses a separate API endpoint (`/api/v2/custom_objects/records`). Plan gating and field limits apply.
- **Knowledge Base translations:** Zammad's KB supports translations natively with `translation_ids`. Zendesk Guide also supports multilingual content, but the translation model differs and requires separate article creation per locale. (See KB migration section.)

## Validation & Testing

Never execute a full migration without rigorous testing.

### Pre-Migration Testing

Run at least two test migrations before the final cutover:

1. **Small sample test:** Migrate 50–100 tickets to a Zendesk sandbox to validate field mapping, status translation, and attachment handling
2. **Full volume test:** Migrate all data to a Zendesk sandbox to validate timing, rate limit handling, and edge cases

### Post-Migration Validation Checklist

- [ ] Total ticket count matches (source vs target)
- [ ] Total user count matches
- [ ] Total organization count matches
- [ ] Sample 100 tickets: verify subject, status, requester, assignee, group
- [ ] Sample 20 tickets with attachments: verify files are accessible
- [ ] Verify internal notes are not visible to end-users
- [ ] Verify `created_at` timestamps match original Zammad timestamps
- [ ] Check for orphaned tickets (no requester, no group)
- [ ] Verify knowledge base articles render correctly with formatting intact
- [ ] Confirm custom field values populated correctly
- [ ] Verify inline images display correctly (no broken CID references)
- [ ] Check that `external_id` values match original Zammad ticket numbers

### Incremental Validation

Do not wait until 100,000 tickets are imported to check for errors. Validate the data after the first 100, 1,000, and 10,000 records. Automated spot-check scripts that compare source and target field values are more reliable than manual checks at scale.

### Rollback Plan

Zendesk doesn't offer a one-click rollback. Plan for it:

- **Tag all imported data** with a migration identifier (e.g., `migration_zammad_2026`)
- **Use Zendesk's bulk delete APIs** to remove imported tickets if needed (note: Zendesk permanently deletes tickets after 30 days in the deleted tickets view)
- **Keep Zammad running in read-only mode** until validation is complete
- **Don't cancel your Zammad subscription or decommission infrastructure** until post-migration QA is fully signed off

For a detailed post-migration QA process, see our [Post-Migration QA Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/).

## Post-Migration Tasks

Once data is loaded, the configuration work begins.

### Rebuild Automations and Triggers

Zammad's triggers, macros, and SLAs don't migrate. You must recreate them in Zendesk:

- **Triggers:** Map Zammad trigger conditions and actions to Zendesk trigger equivalents
- **Macros:** Recreate agent macros for common actions
- **SLAs:** Define new SLA policies based on your existing response/resolution targets
- **Business hours:** Configure schedules and holidays
- **Views:** Build agent views that match your Zammad overviews

Do not turn automations on blindly against partially validated data — imported tickets don't fire triggers, but newly created tickets will. For guidance on rebuilding automations, see [How to Migrate Automations, Macros & Workflows](https://clonepartner.com/blog/blog/how-to-migrate-automations-macros-workflows/).

### Reconnect Channels

- Update DNS records (MX or forwarding rules) to route support emails to Zendesk
- Reconnect API integrations (Jira, Shopify, Salesforce, etc.)
- Verify web widget and chat configurations
- Update any customer-facing links that pointed to Zammad's customer portal

### User Training

Zammad's UI is fundamentally different from Zendesk's workspace. Run agent training sessions on Zendesk's interface, macros, and views before cutover. Assign a migration liaison who can triage issues in the first two weeks post-go-live.

### Monitoring

- Watch for tickets still arriving in Zammad after cutover (redirect email channels)
- Monitor Zendesk for data inconsistencies reported by agents
- Track CSAT scores post-migration to detect service quality changes
- Monitor API error logs for any failed imports that need manual remediation

## Best Practices

1. **Back up everything before migration.** Take a full database snapshot of your self-hosted Zammad instance (or export all data via API for cloud instances) before starting.
2. **Run test migrations.** At least two — one small sample, one full volume against a Zendesk sandbox.
3. **Create users and organizations before tickets.** Zendesk's Ticket Import API requires valid user IDs for requester, assignee, and comment authors.
4. **Use `archive_immediately: true` for closed tickets.** This bypasses the normal ticket lifecycle and places imported closed tickets directly in the archive, reducing clutter in agent views.
5. **Tag imported tickets.** Add a tag like `migrated_from_zammad` to all imported tickets. This lets you filter, report on, and if necessary bulk-delete imported data.
6. **Preserve original ticket numbers.** Store Zammad's ticket `number` in Zendesk's `external_id` field so agents can cross-reference old ticket numbers.
7. **Handle rate limits gracefully.** Build exponential backoff with jitter into your migration script. Read the `Retry-After` header — don't hard-code sleep timers.
8. **Validate incrementally.** Don't wait until the full migration is complete to start checking data. Validate after each batch.
9. **Freeze configuration.** Implement a change freeze on Zammad two weeks before migration. No new custom fields, groups, or macros should be created during this window.
10. **Use the batch import endpoint.** `POST /api/v2/imports/tickets/create_many` accepts up to 100 tickets per request and significantly reduces total API calls compared to single-ticket imports.

## Sample Data Mapping Reference

Use this as a baseline to build your crosswalk mapping sheet:

| Zammad Field | Zammad Type | Zendesk Field | Zendesk Type | Notes |
|---|---|---|---|---|
| `ticket.number` | string | `ticket.external_id` | string | Cross-reference key |
| `ticket.title` | string | `ticket.subject` | string | Direct map |
| `ticket.state.name` | enum | `ticket.status` | enum | Translate values (see state mapping above) |
| `ticket.priority.name` | enum | `ticket.priority` | enum | Map 1–4 scale |
| `ticket.group.name` | relation | `ticket.group_id` | integer | Lookup by name |
| `ticket.customer_id` | integer | `ticket.requester_id` | integer | Via user crosswalk |
| `ticket.owner_id` | integer | `ticket.assignee_id` | integer | Via user crosswalk |
| `ticket.created_at` | datetime | `ticket.created_at` | datetime | ISO 8601 |
| `ticket.close_at` | datetime | `ticket.solved_at` | datetime | Only for closed tickets |
| `article.body` | HTML | `comment.html_body` | HTML | Sanitize inline image URLs |
| `article.internal` | boolean | `comment.public` | boolean | Invert value |
| `article.created_by_id` | integer | `comment.author_id` | integer | Via user crosswalk |
| `article.created_at` | datetime | `comment.created_at` | datetime | ISO 8601 |
| `article.type` | string | (no equivalent) | — | Preserve as tag or body prefix |
| `article.sender` | string | (no equivalent) | — | Use to determine `author_id` context |
| `user.firstname + lastname` | strings | `user.name` | string | Concatenate |
| `user.email` | string | `user.email` | string | Direct map |
| `user.organization_id` | integer | `user.organization_id` | integer | Via org crosswalk |
| `organization.name` | string | `organization.name` | string | Direct map |
| `organization.domain` | string | `organization.domain_names` | array | Wrap in array |

> Planning a Zammad to Zendesk migration? Book a 30-minute technical scoping call with our migration engineering team. We'll map your data model, estimate timelines, and flag risks — no commitment required.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate full ticket history from Zammad to Zendesk?

Yes, but only via API. Zendesk's Ticket Import API (/api/v2/imports/tickets) lets you create tickets with multiple comments, preserving timestamps and author information. CSV imports only work for users and organizations, not ticket conversation history.

### Does Zendesk have a built-in Zammad importer?

No. There is no native migration path between Zammad and Zendesk. Zammad documents a Zendesk → Zammad migrator, but there is no official reverse. You must extract data via Zammad's REST API and load it using Zendesk's Ticket Import API, or use a managed migration service.

### What are Zendesk's API rate limits for ticket imports?

Zendesk rate limits depend on your plan: 200 req/min (Team), 400 (Growth), 700 (Professional), 2,500 (Enterprise). The Ticket Import endpoint shares the account-level limit. Exceeding it returns a 429 error with a Retry-After header.

### Will inline images transfer from Zammad to Zendesk?

Not automatically. Zammad stores inline images as CID (Content-ID) references. Your migration script must parse the HTML body, download the CID image attachments, upload them to Zendesk, and rewrite the HTML source links to point to the new Zendesk URLs.

### Are SLA metrics preserved when importing tickets into Zendesk?

No. Zendesk does not calculate SLA metrics on imported tickets — first reply time, resolution time, and similar metrics will be blank. Zendesk recommends tagging imported tickets and excluding them from SLA reports.
