---
title: "SurveySparrow to Zendesk Migration: The Technical Guide"
slug: surveysparrow-to-zendesk-migration-the-technical-guide
date: 2026-08-05
author: Raaj
categories: [Zendesk, Migration Guide, Help Desk]
excerpt: "A technical guide to migrating tickets from SurveySparrow to Zendesk — covering API extraction, data mapping, import constraints, and the edge cases that cause silent data loss."
tldr: "SurveySparrow to Zendesk migration requires custom API work — no native importer exists. Extract via SurveySparrow API v3, transform to Zendesk's schema, load via Ticket Import API. Users must exist before ticket import."
canonical: https://clonepartner.com/blog/surveysparrow-to-zendesk-migration-the-technical-guide/
---

# SurveySparrow to Zendesk Migration: The Technical Guide


# SurveySparrow to Zendesk Migration: The Technical Guide

*Last verified against SurveySparrow API v3 and Zendesk Ticket Import API as of July 2025.*

Migrating from SurveySparrow Ticket Management to Zendesk means translating a **feedback-first ticketing system** — where tickets spawn from survey responses, NPS detractors, and form submissions — into a **relational, enterprise helpdesk** built around ticket lifecycles, organizations, groups, SLA policies, and automation triggers. These two systems operate on fundamentally different architectural assumptions.

There is no native migration path between these platforms. Zendesk does not offer a pre-built SurveySparrow connector, and SurveySparrow's native export tools (Excel/JSON) do not format data in a way Zendesk can ingest without heavy transformation. The existing SurveySparrow-Zendesk integration on the Zendesk Marketplace is designed for triggering surveys from Zendesk ticket events — not for migrating historical ticket data. Automation platforms like Zapier and n8n can connect both APIs for real-time workflows, but neither supports bulk historical migration of thousands of tickets with threaded comments and attachments.

Every migration requires extracting data via SurveySparrow's REST API (v3), transforming the payload to match Zendesk's ticket and user schema, and loading it through Zendesk's Ticket Import API (`POST /api/v2/imports/tickets`). This guide covers the full technical path: API constraints, data model mapping, extraction methods, step-by-step execution, timeline estimation, and the edge cases that cause silent failures.

> [!WARNING]
> **Use the Ticket Import endpoint, not the standard one.** When migrating historical data to Zendesk, you must use the Ticket Import API (`POST /api/v2/imports/tickets`). The standard Ticket Create endpoint (`POST /api/v2/tickets.json`) overwrites all historical timestamps with the current date and sets the API user as the author of every comment. The Import endpoint also does not trigger business rules or automations, which is what you want during a migration.

For related context, see our [Zendesk Migration Checklist](https://clonepartner.com/blog/blog/zendesk-migration-checklist/) and [Freshdesk to Zendesk Migration Guide](https://clonepartner.com/blog/blog/freshdesk-to-zendesk-migration-guide/). If you are evaluating other target platforms for your data, see our technical guides for migrating SurveySparrow to [Missive](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-missive-migration-technical-guide/), [Dixa](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-dixa-migration-guide/), or [HappyFox](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-happyfox-migration-guide/).

## SurveySparrow vs Zendesk: Architecture Differences

Before writing extraction scripts, understand how the data models diverge. A 1:1 mapping attempt will result in orphaned tickets and lost survey context.

**SurveySparrow Ticket Management** is a lightweight ticketing layer sitting on top of a survey and feedback platform. Tickets carry a subject, description (plain text and HTML), priority, status, assignee, team, custom fields, template ID, requester (contact), source, and a threaded comment stream. Tickets are typically created automatically via workflows — triggered by low NPS scores, survey responses meeting certain criteria, or manual creation. The context of *why* a ticket exists (e.g., a customer rated your service 2/10 on an NPS survey) is often stored as metadata or injected as the first message in the ticket thread. The system includes SLA tracking with `first_response_due` and `resolution_due` timestamps.

**Zendesk Support** enforces a strict relational hierarchy. Every ticket must tie to a recognized User (the requester), and every User can belong to an Organization. Tickets have groups, assignees, collaborators (CCs/followers), tags, custom fields, ticket forms, multiple comment types (public replies, internal notes), SLA policies, macros, triggers, automations, views, and a full audit trail. Zendesk supports multi-brand, multi-channel, and complex routing.

| Concept | SurveySparrow | Zendesk |
|---|---|---|
| **Ticket ID** | Numeric ID | Numeric ID (auto-assigned on import) |
| **Requester** | Contact (name + email) | User (must pre-exist before import) |
| **Assignee** | Agent (user) | Agent (user in a group) |
| **Team** | Team object | Group |
| **Priority** | Low / Medium / High / Urgent | low / normal / high / urgent |
| **Status** | Open / Pending / Resolved / Closed | new / open / pending / hold / solved / closed |
| **Comments** | Threaded via `/v3/tickets/{id}/comments` | Comments array with `author_id`, `public` flag, `created_at` |
| **Custom fields** | Key-value object on ticket | `custom_fields` array with `id` + `value` pairs |
| **Attachments** | Via comment attachments | Upload first → token in comment |
| **SLA** | `first_response_due`, `resolution_due` | SLA Policies (not preserved on import) |
| **Organizations** | No equivalent | Organization object |
| **Tags** | No native tag system | Tags array on ticket |
| **Ticket forms** | Templates | Ticket Forms |
| **Source/Channel** | Source object | `via` channel (web, email, api, etc.) |

Because Zendesk enforces referential integrity, you cannot import a ticket if the `requester_id` or `assignee_id` does not already exist in the system.

> [!NOTE]
> **SLA data won't transfer.** Zendesk states that metrics and SLAs are not supported for imported tickets — running SLAs on imported tickets produces incomplete and inaccurate data. Re-establish SLA policies natively in Zendesk after migration.

## API Constraints and Rate Limits

Both platforms enforce strict API limits that dictate your migration throughput.

### SurveySparrow API (v3)

SurveySparrow's API rate limits vary by plan but typically cap at **100 requests per minute**. The `GET /v3/tickets` endpoint supports pagination with a max of 100 records per page, filterable by `requester_id`, `assignee_id`, `team_id`, `priority`, `status`, and date ranges (`created_date.gte`, `created_date.lte`, `updated_date.gte`, `updated_date.lte`). You must extract the base ticket data, then make subsequent calls to `GET /v3/tickets/{id}/comments` to retrieve the thread history. A single ticket with comments requires **at least two API calls** to fully extract — one for the ticket, one for the comment thread. A contact lookup adds a third.

**Minimum API call calculation for extraction:**

| Operation | Calls per Ticket | Calls for 5,000 Tickets |
|---|---|---|
| Base ticket data | 1 | 5,000 |
| Comment thread | 1 | 5,000 |
| Attachment download | Varies | 1,000–10,000+ |
| **Total (no attachments)** | **2** | **10,000+** |

At 100 requests/minute, 10,000 extraction calls take a minimum of 100 minutes of pure API time, before any retry delays. Build retry logic with exponential backoff from the start.

> [!WARNING]
> **API v1 was deprecated on December 31, 2024.** Ensure all extraction scripts use v3 endpoints. SurveySparrow's v3 API uses OAuth 2.0 or API token-based authentication.

### Zendesk API (v2)

Zendesk's rate limits are tied to your plan tier:

| Plan | Rate Limit | Minimum time for 10,000 tickets |
|---|---|---|
| Team | 200 req/min | ~50 min (tickets only) |
| Growth / Professional | 400 req/min | ~25 min |
| Enterprise | 700 req/min | ~15 min |
| Enterprise Plus | Up to 2,500 req/min | ~4 min |

The Ticket Import endpoint is resource-intensive. While Zendesk offers a Bulk Ticket Import endpoint (`POST /api/v2/imports/tickets/create_many`) that accepts up to 100 tickets per request, keep payloads under 2MB. For complex tickets with many comments and large attachments, the single Ticket Import endpoint avoids payload size errors and timeout failures.

**When to use bulk vs. single import:**

- **Use bulk (`create_many`)** when tickets have ≤5 comments, no attachments, and payloads are well under 2MB. Bulk import reduces API call count by up to 100x.
- **Use single import** when tickets have many comments, inline images, or attachments. A single ticket with 20 comments and 3 attachments can easily approach 2MB on its own.
- **Hybrid approach:** Use bulk for clean, simple tickets (typically resolved/closed historical records) and single-ticket import for open/pending tickets that are actively used by agents.

**Rate limit stacking** is a real trap: attachment uploads, user creation, and ticket imports all share the same rate limit pool. A script that uploads attachments and imports tickets simultaneously burns through limits faster than expected. On a Professional plan (400 req/min), uploading 5 attachments per ticket while bulk-importing 100 tickets per call means you exhaust your rate limit on attachments alone after ~80 tickets.

## Decision Framework: Self-Serve vs. Professional Migration

Before writing any code, evaluate your migration profile against these criteria:

**Self-serve is viable when:**
- Ticket volume is under 5,000
- Fewer than 5 custom field types (no complex dropdowns or dependent fields)
- Comment threads average fewer than 10 comments per ticket
- No inline images in comments (attachments only)
- All requesters have known email addresses (no anonymous submissions)

**Professional migration is warranted when:**
- Ticket volume exceeds 5,000
- Anonymous survey respondents constitute more than 5% of tickets
- Attachment volume exceeds 10GB
- Agents are actively working tickets during migration (live delta sync required)
- Custom field types include dropdowns with 50+ options requiring tag mapping

**Time cost model for self-serve estimation:**

At 400 req/min (Professional plan) with 3 API calls per ticket during extraction and 1 API call per ticket during import (bulk batches of 100):

- 5,000 tickets × 3 extraction calls = 15,000 calls = ~38 minutes extraction
- 5,000 tickets ÷ 100 per batch = 50 import calls = ~1 minute import (no attachments)
- Each attachment upload = 1 additional API call; 2 attachments per ticket average = 10,000 additional calls = ~25 minutes

A clean 5,000-ticket migration with minimal attachments takes approximately **2–3 hours of API time** if nothing fails. With retries, edge cases, and validation, budget **2–3 days of engineering effort** total.

## Data Extraction: Getting Tickets Out of SurveySparrow

You have three extraction paths, each with trade-offs.

### Option 1: SurveySparrow REST API v3 (Recommended)

The API gives you the most complete and structured data. Key endpoints:

- **List tickets:** `GET /v3/tickets` — paginated, max 100 per page
- **Get single ticket:** `GET /v3/tickets/{id}` — full detail including custom fields
- **Get ticket comments:** `GET /v3/tickets/{id}/comments` — conversation history
- **Ticket fields:** `GET /v3/ticket-fields` — custom field definitions
- **Contacts:** `GET /v3/contacts` — requester data
- **Users:** `GET /v3/users` — agent/team member data

```bash
# Fetch tickets page by page
curl --request GET \
  --url 'https://api.surveysparrow.com/v3/tickets?limit=100&page=1' \
  --header 'Authorization: Bearer <your_api_token>'

# Fetch comments for a specific ticket
curl --request GET \
  --url 'https://api.surveysparrow.com/v3/tickets/12345/comments' \
  --header 'Authorization: Bearer <your_api_token>'
```

### Option 2: Native Export (Settings UI)

SurveySparrow supports ticket export via **Settings → Ticket Management → Export Data** in Excel (xlsx) and JSON formats. A separate CSV export is available from the Ticket List View, reflecting your current filters and column layout. If the CSV data exceeds the downloadable limit, it's automatically split into multiple files.

**Limitations:**
- Exports capture ticket metadata and the initial description, but **threaded comments and conversation history are typically not included**
- Attachment content is not included — you get metadata at best
- No programmatic scheduling for ticket exports

For any migration where conversation history matters (and it almost always does), the API is the only viable extraction path.

### Option 3: Hybrid Approach

Use the native JSON export for ticket metadata (subject, status, priority, dates, custom fields, requester info), then supplement with API calls for comments and attachments only. This reduces extraction API calls by roughly 50% (eliminating the base ticket call per record) while preserving conversation fidelity.

## Data Mapping Strategy

Mapping fields correctly is the most time-consuming phase. This is where most migration bugs live.

### Status Mapping

SurveySparrow's default statuses differ from Zendesk's hardcoded status logic. Zendesk relies heavily on the `status` field for SLA timers and automation rules.

| SurveySparrow Status | Zendesk Status | Notes |
| :--- | :--- | :--- |
| Open | new / open | Map to `new` if unassigned; `open` if assigned |
| Pending | pending | Zendesk `pending` implies waiting on the requester |
| Resolved | solved | Zendesk automatically moves `solved` to `closed` after a set period |
| Closed | closed | Closed tickets in Zendesk are **immutable** — cannot be updated via API |

### Priority Mapping

| SurveySparrow Priority | Zendesk Priority |
|---|---|
| Low | low |
| Medium | normal |
| High | high |
| Urgent | urgent |

Note the **Medium → normal** mapping. This trips up scripts that attempt a straight lowercase conversion — `"medium".lower()` does not equal `"normal"` and will fail field validation silently or throw a 422.

### Preserving Survey Context

SurveySparrow tickets often contain survey response data that agents need for resolution. Zendesk has no native "Survey Response" object. Two options:

1. **Custom Field:** Create a multi-line text custom field in Zendesk (e.g., "Original Survey Context") and map the survey payload into it.
2. **Internal Note:** Inject the survey response data as the first private comment (internal note) on the Zendesk ticket during import.

We recommend the **internal note approach**. It keeps survey context immediately visible in the agent's conversation thread without cluttering the ticket sidebar with large custom field values, and it timestamps the context at the original ticket creation time.

### Custom Field Translation

Pay close attention to dropdown and multi-select fields. In Zendesk, dropdown options are represented by **tags**. When importing a ticket, you don't pass the string value of the dropdown — you pass the associated tag. Build a translation matrix mapping SurveySparrow dropdown string values to Zendesk dropdown tags before writing your import script.

**Custom field type compatibility matrix:**

| SurveySparrow Type | Zendesk Type | Transformation Required |
|---|---|---|
| Text | Text | None |
| Textarea | Textarea / Multi-line | None |
| Dropdown | Tagger (dropdown) | String value → tag lookup |
| Checkbox | Checkbox | Boolean → boolean |
| Number | Decimal / Integer | Type check only |
| Date | Date | ISO 8601 format normalization |
| Multi-select | Multi-select | Each value → tag |

For type mismatches (e.g., a text field in SurveySparrow mapping to a dropdown in Zendesk), normalize and validate values before import. Invalid tag values cause silent field nullification — the ticket imports successfully but the field is blank.

### Requester and Agent Resolution

Match SurveySparrow contacts to Zendesk users by **email address**. Build a lookup table of `surveysparrow_contact_email → zendesk_user_id` before starting the load. Unmatched agents are a hard blocker — every comment needs a valid `author_id`.

If SurveySparrow doesn't expose a `public`/`private` flag on comments, decide a default. We typically import all SurveySparrow comments as public unless there's a clear internal marker (e.g., a comment tag, assigned agent as author with no customer reply following).

## Step-by-Step Migration Execution

A production-grade migration follows a strict sequence. Deviating from this order will result in API rejections due to missing relational dependencies.

### Step 1: Audit Your SurveySparrow Data

Before writing any code, inventory what you have:

- **Total ticket count** by status (Open, Pending, Resolved, Closed)
- **Custom fields** — all custom ticket fields and their types
- **Templates** — which ticket templates are in use
- **Teams** — map to Zendesk Groups
- **Agents** — all agents and their email addresses
- **Contacts** — unique requester count, percentage with no email address
- **Attachment volume** — estimate total size in GB
- **Comment density** — average and maximum comments per ticket
- **Anonymous ticket percentage** — tickets with no associated contact email

This inventory determines your migration complexity and whether self-serve is viable.

### Step 2: Set Up the Zendesk Target Structure

Before importing any tickets:

- **Create custom ticket fields** mapping to SurveySparrow custom fields. Note Zendesk's field IDs — you'll need them for the import payload. Create a dedicated field (e.g., `surveysparrow_ticket_id`) to store the original ID for cross-referencing.
- **Create Groups** matching your SurveySparrow Teams.
- **Create or import Users** — both agents and end-user requesters. Agents need Zendesk licenses. Requesters can be created via the Users API or CSV bulk import (up to 2,000 rows per file). Deduplicate contacts by email before import — if you've been using the SurveySparrow-Zendesk survey integration, some contacts may already exist in Zendesk with different metadata.
- **Set up Ticket Forms** if mapping SurveySparrow Templates.
- **Create migration tracking tags** (e.g., `migrated-from-surveysparrow`).

```json
// Example Zendesk User Creation Payload
{
  "user": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "role": "end-user",
    "verified": true
  }
}
```

Store the mapping of `SurveySparrow_Contact_ID` → `Zendesk_User_ID` in a local database or mapping file. Every subsequent API call depends on this lookup table being complete and accurate.

### Step 3: Extract Data from SurveySparrow

Build an extraction script that:

1. Paginates through `GET /v3/tickets?limit=100` to collect all tickets
2. For each ticket, fetches comments via `GET /v3/tickets/{id}/comments`
3. Downloads any attachments referenced in comments
4. Exports contacts via `GET /v3/contacts` for requester matching
5. Exports custom field definitions via `GET /v3/ticket-fields`

Store extracted data in an intermediate format (JSON files per ticket batch). This decouples extraction from loading and lets you restart the load phase without re-extracting.

### Step 4: Handle Attachments

Attachments cannot be passed as URLs in the Zendesk Ticket Import payload. Process them sequentially:

1. Download the attachment from the SurveySparrow URL to your local server.
2. Upload the file to Zendesk via `POST /api/v2/uploads?filename={file_name}`.
3. Zendesk returns an `upload_token`.
4. Attach this token to the specific comment in your ticket import payload.

> [!NOTE]
> **Upload tokens expire after 3 days.** Do not upload all attachments weeks in advance. Upload them as part of the ticket processing loop — upload attachments for a ticket immediately before importing that ticket.

### Step 5: Execute the Ticket Import

Construct the Zendesk Ticket Import payload. This endpoint lets you pass an array of `comments`, preserving the original `author_id` and `created_at` timestamps.

```json
{
  "ticket": {
    "subject": "NPS Detractor Follow-up",
    "description": "Customer reported issue via NPS survey",
    "requester_id": 12345,
    "assignee_id": 67890,
    "group_id": 111,
    "status": "closed",
    "priority": "high",
    "created_at": "2024-03-15T09:30:00Z",
    "updated_at": "2024-03-16T14:22:00Z",
    "solved_at": "2024-03-16T14:22:00Z",
    "tags": ["nps-detractor", "migrated-from-surveysparrow"],
    "custom_fields": [
      {"id": 9999, "value": "original_ss_ticket_42"}
    ],
    "comments": [
      {
        "author_id": 12345,
        "created_at": "2024-03-15T09:30:00Z",
        "value": "I rated your service 3/10 because..."
      },
      {
        "author_id": 67890,
        "created_at": "2024-03-15T11:45:00Z",
        "public": false,
        "value": "Internal: Escalating to retention team"
      },
      {
        "author_id": 67890,
        "created_at": "2024-03-16T14:22:00Z",
        "value": "We've applied a credit to your account.",
        "uploads": ["token_abc123"]
      }
    ]
  }
}
```

For batch imports, implement retry logic that handles the full range of error responses the Import API returns:

```python
import requests
import time

ZENDESK_SUBDOMAIN = "yourcompany"
ZENDESK_EMAIL = "[email protected]"
ZENDESK_TOKEN = "your_api_token"

def import_ticket_batch(tickets):
    url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/imports/tickets/create_many"
    payload = {"tickets": tickets}
    response = requests.post(
        url,
        json=payload,
        auth=(f"{ZENDESK_EMAIL}/token", ZENDESK_TOKEN),
        headers={"Content-Type": "application/json"}
    )
    
    if response.status_code == 429:
        # Rate limited: respect the Retry-After header
        retry_after = int(response.headers.get("Retry-After", 60))
        time.sleep(retry_after)
        return import_ticket_batch(tickets)
    
    elif response.status_code == 422:
        # Unprocessable Entity: schema error, missing required field, or invalid value
        # Log the full response body — it contains per-field error details
        error_detail = response.json()
        raise ValueError(f"Payload schema error: {error_detail}")
    
    elif response.status_code == 404:
        # Missing user ID: a requester_id or author_id doesn't exist in Zendesk
        # Check your user mapping table — the contact was not pre-created
        raise ValueError(f"User not found — check requester/author ID mapping")
    
    elif response.status_code == 413:
        # Payload too large: split the batch into smaller chunks
        mid = len(tickets) // 2
        left = import_ticket_batch(tickets[:mid])
        right = import_ticket_batch(tickets[mid:])
        return {"left": left, "right": right}
    
    elif response.status_code not in (200, 201):
        raise RuntimeError(f"Unexpected error {response.status_code}: {response.text}")
    
    return response.json()
```

**Key error codes to handle explicitly:**

| HTTP Status | Meaning in Migration Context | Resolution |
|---|---|---|
| 422 | Invalid field value, missing required field, malformed timestamp | Check field mapping; validate before sending |
| 404 | `requester_id` or `author_id` doesn't exist | Pre-create users; check mapping table |
| 413 | Payload exceeds 2MB | Split batch; move to single-ticket import |
| 429 | Rate limit exceeded | Respect `Retry-After` header |
| 503 | Zendesk service unavailable | Retry with exponential backoff |

Use `archive_immediately: true` for tickets imported with a `closed` status if your volume exceeds 750,000 tickets or you want to keep the active ticket queue clean. Zendesk recommends this flag for large historical imports to avoid impacting active queue performance.

### Step 6: Delta Sync and Cutover

Ticket migrations take time. Agents continue working in SurveySparrow while the initial load runs, which means tickets are being created and updated in parallel. Delta sync must handle three scenarios:

**Scenario 1: New tickets created during migration**
Query SurveySparrow for tickets created after your initial extraction cutoff timestamp using `created_date.gte`. These don't exist in Zendesk yet — import them fresh.

**Scenario 2: Existing tickets updated during migration**
Query SurveySparrow for tickets modified after your cutoff using `updated_date.gte`. Cross-reference against your migration log using `surveysparrow_ticket_id`. If the ticket already exists in Zendesk, append new comments using `PUT /api/v2/tickets/{id}.json`. Note: you cannot use the Import endpoint for updates — only for creation.

**Scenario 3: Tickets modified in both systems simultaneously**
This is the hardest case and arises if agents begin working in both systems during cutover. Your conflict resolution strategy:
- **Timestamp wins:** Compare the `updated_at` timestamp on the SurveySparrow record against the `updated_at` on the Zendesk record. The more recently updated record wins.
- **SurveySparrow always wins (safer):** Until formal cutover, treat SurveySparrow as the system of record. Any update in Zendesk during migration is likely a test or mistake.
- **Flag for manual review:** If both records have been updated within the same 1-hour window, flag the ticket for human review rather than auto-resolving.

> [!WARNING]
> **Define a hard cutover timestamp before starting delta sync.** Announce to agents: "After [date/time], SurveySparrow is read-only." Without a hard cutover, you risk chasing an infinite update loop where agents modify tickets in SurveySparrow faster than you can sync them.

Once the delta is clear (no tickets modified in SurveySparrow after the cutover timestamp), update your DNS/routing rules and support channel configurations to point to Zendesk.

### Step 7: Validate the Migration

Never trust load logs alone. Run post-migration validation:

- **Count check:** Total tickets imported vs. total tickets extracted, broken down by status
- **Spot-check 50+ tickets** across different statuses, priorities, and date ranges
- **Verify comment threads** are intact with correct chronological ordering and author attribution
- **Verify attachments** are accessible and render correctly in the Zendesk agent interface
- **Confirm custom field values** populated correctly, especially dropdown fields
- **Check that all requesters** resolve to the correct Zendesk users
- **Validate timestamps** — `created_at` and `updated_at` on imported tickets should match source data
- **Test search** — verify imported tickets are indexed and searchable in Zendesk

## Timeline and Effort Estimation

| Ticket Volume | API Time (Professional plan) | Engineering Effort | Recommended Approach |
|---|---|---|---|
| < 1,000 | 1–3 hours | 1–2 days | Self-serve with scripts |
| 1,000–10,000 | 3–12 hours | 3–7 days | Scripts with careful planning |
| 10,000–50,000 | 1–3 days | 2–4 weeks | Managed migration recommended |
| 50,000+ | 3–10+ days | 4–8 weeks | Professional migration service |

The main timeline drivers:

- **Comment density:** A ticket with 20 comments requires at least 20 additional attachment/author resolution checks; extraction API time scales linearly with average thread length
- **Attachment volume:** File uploads to Zendesk are throttled separately and are the single slowest operation — budget 1 API call per file regardless of batch size
- **Custom field complexity:** Dropdown and checkbox fields require a validated tag-mapping table; building and testing this typically takes 1–3 days for complex schemas
- **User deduplication:** Matching contacts across systems by email, resolving duplicates, and handling contacts with no email takes significant validation time proportional to contact volume

## Edge Cases and Common Failure Modes

Even with a well-tested script, platform-specific quirks will cause errors. Plan for the following:

**Suspended requesters block import.** If a requester email exists in Zendesk but is suspended (often from spam filters), the import API rejects the ticket with a 422. Either unsuspend the user, merge them with an active account, or map the ticket to a generic "System User" account to preserve the data.

**No empty comment bodies.** The API rejects comments with empty text, returning a 422. If a SurveySparrow comment is attachment-only with no text, add a placeholder like `" [Attachment]"` to prevent rejection.

**Inline images break after decommission.** SurveySparrow comments may contain inline images hosted on SurveySparrow's CDN. If you only migrate the HTML text, these images break when the SurveySparrow account is closed. Parse the HTML, download the `<img>` `src` files, upload them to Zendesk as attachments, and rewrite the HTML `src` attributes to reference the new Zendesk CDN URLs before importing.

**Closed tickets are immutable in Zendesk.** Once you import a ticket with `closed` status, you cannot update it via the API — attempts return a 422. If you need to append a missed comment or fix a custom field, you must delete the ticket and re-import it. Test your mapping logic on a sample of 50–100 closed tickets before importing the full historical closed dataset.

**Anonymous survey respondents lack a requester.** Some SurveySparrow tickets are auto-generated from anonymous survey responses with no associated contact email. Zendesk requires a requester for every ticket. Create a fallback user (e.g., `anonymous-survey@yourcompany.com`) for these orphaned tickets and tag them `anonymous-requester` for later review.

**Ticket IDs don't carry over.** Zendesk auto-assigns new IDs to imported tickets. Always store the original SurveySparrow ticket ID in a custom field for cross-referencing. Communicate this to agents before go-live — any internal references or integrations keyed to SurveySparrow ticket IDs will break.

**Timestamps must be between 1970 and present.** Values outside this range get silently rounded or rejected by the Zendesk Import API. Validate all date fields during the transformation step.

**HTML rendering differences.** SurveySparrow's `description_html` may contain inline styles, non-standard tags, or entity encoding that Zendesk's `html_body` doesn't render identically. Test with a representative sample of 20–30 tickets before committing to a full import, and apply HTML sanitization as needed.

**Invalid tag values silently nullify dropdown fields.** If a Zendesk dropdown tag value doesn't exactly match the configured option tag (case-sensitive), the ticket imports successfully but the field is blank. Validate all dropdown values against the Zendesk field configuration before the import run.

## When Not to Migrate Everything

Not every SurveySparrow ticket belongs in Zendesk. Consider leaving behind:

- **Auto-generated test tickets** from workflow development
- **Duplicate tickets** spawned from repeat survey submissions
- **Tickets older than your retention policy** — filter your extraction using `created_date.gte`
- **Tickets with no resolution or conversation** — one-line tickets with no comments, no assigned agent, and no custom field values may not justify the import cost
- **Anonymous tickets with no resolution** — if the requester is unknown and the ticket was never resolved, it adds noise without value

A selective migration reduces API call volume, accelerates the import timeline, and produces a cleaner Zendesk instance. For most teams, applying a `created_date.gte` filter of 12–24 months covers the overwhelming majority of operationally relevant tickets.

## Making the Migration Stick

A SurveySparrow-to-Zendesk migration is architecturally straightforward but operationally dense. The flat, feedback-oriented ticket model maps cleanly to Zendesk's schema — you're moving into a more capable system, not a constrained one. The hard parts are user matching, comment thread extraction (which requires per-ticket API calls), attachment handling at scale, the compounding effect of rate limits across all three operations running simultaneously, and delta sync conflict resolution during cutover.

The migration itself is a one-time cost. What matters more is getting it right: preserving conversation history, maintaining correct timestamps, handling all four error code categories gracefully, and ensuring every ticket is traceable back to its source via the `surveysparrow_ticket_id` custom field. Cut corners on validation and you'll spend weeks cleaning up data integrity issues after go-live.

For teams with fewer than 5,000 tickets and minimal custom fields, a self-serve approach with scripts is realistic — budget 2–3 days of engineering time for a clean dataset and a week for anything complex. Beyond that threshold, the combination of rate limit management, user deduplication, attachment pipeline, delta sync conflict resolution, and edge case handling adds enough complexity that the engineering cost of self-serve typically exceeds the cost of professional assistance.

> Need to migrate to Zendesk without the downtime? ClonePartner's engineering team handles complex, API-driven data migrations. Book a technical scoping call to discuss your ticket volume, custom field complexity, and cutover timeline.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate tickets from SurveySparrow to Zendesk automatically?

No. There is no native migration tool, built-in importer, or verified third-party connector for SurveySparrow-to-Zendesk ticket migration. The SurveySparrow Zendesk integration is designed for survey triggers, not historical data migration. You need custom scripts using SurveySparrow's API v3 for extraction and Zendesk's Ticket Import API for loading.

### Does Zendesk's Ticket Import API preserve original timestamps?

Yes. The Ticket Import API (POST /api/v2/imports/tickets) lets you set created_at, updated_at, and solved_at on tickets, plus created_at on individual comments. The standard ticket creation endpoint overwrites all timestamps with the current date. Timestamps cannot be set before 1970 or in the future.

### What happens to survey responses attached to SurveySparrow tickets?

Survey responses do not map natively to Zendesk. Extract the response data and inject it as a private internal note on the Zendesk ticket during import. This preserves context for agents directly in the conversation thread.

### How long does a SurveySparrow to Zendesk migration take?

Expect 2–5 days for under 1,000 tickets, 1–2 weeks for 1,000–10,000, and 2–4 weeks for 10,000–50,000 tickets. Timeline depends on comment density (each ticket requires a separate API call for comments), attachment volume, and custom field complexity.

### Will SLA data transfer from SurveySparrow to Zendesk?

No. Zendesk states that metrics and SLAs are not supported for imported tickets. Running SLAs on imported tickets produces incomplete and inaccurate data. Re-establish SLA policies natively in Zendesk after the migration.
