---
title: "Crisp to Gorgias Migration Guide (2026): APIs, Mapping & Edge Cases"
slug: crisp-to-gorgias-migration-guide-2026-apis-mapping-edge-cases
date: 2026-06-04
author: Raaj
categories: [Migration Guide, Gorgias]
excerpt: "A technical guide to migrating from Crisp to Gorgias: API constraints, data model mapping, rate limits, edge cases, and step-by-step ETL architecture for 2026."
tldr: "Crisp doesn't support conversation CSV exports — you must use the REST API. Map chat sessions to tickets, always set sent_datetime on Gorgias imports, and budget for rate limit throttling on both sides."
canonical: https://clonepartner.com/blog/crisp-to-gorgias-migration-guide-2026-apis-mapping-edge-cases/
---

# Crisp to Gorgias Migration Guide (2026): APIs, Mapping & Edge Cases


Migrating from Crisp to Gorgias is an API project, not a CSV import. Crisp does not support native CSV exports for conversation data — every message, attachment, and agent note must be extracted through the Crisp REST API, structurally transformed from a chat-session model to a ticket-thread model, and loaded into Gorgias while respecting tight rate limits on both sides.([help.crisp.chat](https://help.crisp.chat/en/article/how-to-export-contact-profiles-g1h8jm/))

E-commerce brands typically initiate this move when they outgrow Crisp's chat-first widget architecture and need Gorgias's deep Shopify, BigCommerce, or Magento integrations. For a broader look at the business drivers behind the switch, see [Why Top E-commerce Teams are Migrating to Gorgias](https://clonepartner.com/blog/blog/why-migrating-to-gorgias/).

This guide covers the exact API constraints, object mapping, edge cases, and step-by-step architecture you need before writing your first line of migration code.

## Why E-commerce Teams Move from Crisp to Gorgias

Crisp is a chat-first customer messaging platform. It works well for startups running lean support across chat, email, and social. But as e-commerce teams scale on Shopify or BigCommerce, they hit a ceiling: Crisp lacks native deep e-commerce integrations, revenue-per-ticket attribution, and the advanced ticket routing that Gorgias provides out of the box.([gorgias.com](https://www.gorgias.com/apps/shopify?utm_source=openai))

The core driver is almost always **Shopify integration depth**. Gorgias lets agents view, edit, refund, and cancel orders directly inside the ticket sidebar without switching tabs. Crisp has no equivalent. For DTC brands where the majority of support tickets are order-related, this alone justifies the switch.

Other triggers include:

- **Automation and rules:** Gorgias's rule engine auto-tags, auto-closes, and auto-responds based on intent detection and order data — capabilities that require custom bot scripting in Crisp.
- **Revenue tracking:** Gorgias attributes revenue to support interactions, which matters for teams justifying headcount.
- **AI agents:** Gorgias offers AI-resolved ticket automation billed per resolution, replacing the need for separate chatbot tooling.

For a technical deep dive into Gorgias's architecture, see [Mastering Gorgias: The 2026 Technical Guide](https://clonepartner.com/blog/blog/ultimate-guide-gorgias-2026/).

## The Data Model Mismatch: Chat Sessions vs. Tickets

This is where most DIY migrations break. **Crisp is chat-centric. Gorgias is ticket-centric.** The two platforms model the same customer interaction in fundamentally different ways.

In Crisp, every interaction lives under a **Conversation** identified by a `session_id`. Messages — from a visitor, operator, or bot — are children of that session. A single conversation can span weeks or months if the visitor returns to the same chat widget session. Crisp conversations can contain up to approximately 10,000 messages before the API rejects additions.([docs.crisp.chat](https://docs.crisp.chat/references/rest-api/v1/))

In Gorgias, the atomic unit is a **Ticket**. Each ticket represents a discrete support interaction, contains one or more messages, and is linked to exactly one customer. Tickets carry a status — either Open or Closed (Gorgias uses only two statuses) — and can be tagged, assigned to agents, and associated with e-commerce order data.

**The translation problem:** A single long-running Crisp conversation may need to become one Gorgias ticket, or you may want to split it into multiple tickets based on resolution state. There is no automatic mapping. You must make an architectural decision before you start loading data.

The status model changes too. Crisp supports `pending`, `unresolved`, and `resolved`. Gorgias only has `open` and `closed`. If you need exact historical semantics, Crisp's `pending` state usually has to survive as a tag or custom field rather than a native status.([docs.crisp.chat](https://docs.crisp.chat/references/rest-api/v1/))

### Object Mapping Table

| Crisp Object | Crisp API Path / Field | Gorgias Object | Gorgias API Path / Field | Notes |
|---|---|---|---|---|
| People Profile | `/website/{id}/people/profiles` | Customer | `POST /api/customers` | Match on email. Create if not exists. Overflow profile data goes into `customer.data`. |
| Conversation | `/website/{id}/conversations` (by `session_id`) | Ticket | `POST /api/tickets` | One conversation = one ticket (typical). Store `session_id` in `external_id` for traceability. |
| Message (text) | `/website/{id}/conversation/{session_id}/messages` | Message | `POST /api/tickets/{id}/messages` | Set `sent_datetime` to prevent Gorgias from re-sending. |
| Message (note) | `type: "note"` | Internal Note | `channel: "internal-note"` | Gorgias internal notes are a message channel. |
| Message (file) | `type: "file"` with `content.url` | Attachment | Upload via `POST /api/upload`, attach to message `body_html` | Must download from Crisp, re-upload to Gorgias. |
| Segments | `segments []` on People Profile | Tags | `tags: [{"name": "..."}]` on Ticket | Pipe-separated in Crisp, array in Gorgias. Normalize names before load. |
| Operator | Workspace operators | User/Agent | `POST /api/users` | Map by email. Handle inactive operators explicitly. |
| Conversation State | `resolved` / `unresolved` / `pending` | Ticket Status | `open` / `closed` | Gorgias only has two states. Map `pending` to `open` + a preservation tag. |
| Conversation Metas | `get_conversation_metas()` | Ticket `meta` + `external_id` | JSON key-value on ticket | Crisp people `notepad` has no first-class Gorgias equivalent — store in `customer.data` or emit a synthetic internal note. |

## Why Native CSV Export Fails for Crisp Conversations

This is the first thing teams discover — and it stops most non-technical migration plans dead.

**Crisp's dashboard allows CSV export for contact profiles only, not for conversation data.** Crisp's official documentation states explicitly that conversation exports are not available as a native CSV from the Crisp app.([help.crisp.chat](https://help.crisp.chat/en/article/how-to-export-contact-profiles-g1h8jm/)) The contact CSV export is hard-capped at 200,000 profiles. If you have more, the UI export fails entirely and you must use the `GET /website/{website_id}/people/profiles` endpoint to paginate through your customer base.

This means there is no "export all, import all" path. You must use the Crisp REST API to extract conversations and messages programmatically, or use a third-party tool that does it for you.

> [!WARNING]
> Do not assume you can export Crisp chat history from the dashboard. Conversation data requires API extraction. Plan your migration timeline accordingly — API extraction at scale takes days, not hours.([help.crisp.chat](https://help.crisp.chat/en/article/how-to-export-contact-profiles-g1h8jm/))

## API Constraints: Rate Limits and Pagination

Both Crisp and Gorgias enforce rate limits that directly constrain migration throughput. Misunderstanding these limits is the number one cause of failed DIY migration scripts.

### Crisp API Rate Limits

Crisp uses a **multi-level rate-limit system** based on client IP and user identifier. There are four layers:

| Limiter | Scope | Applies To |
|---|---|---|
| **Load Balancers** | IP Address | All tiers |
| **API Global** | IP + User ID | `user` and `website` tiers |
| **API Route** | IP + User ID (per-route) | `user` and `website` tiers |
| **Plugin Quota** | Plugin ID (daily) | `plugin` tier only |

Plugin tokens are **exempt from API Global and API Route limits** but subject to a daily quota that resets every 24 hours. For migration, you want a plugin token with a production-tier quota. Development tokens have lower quotas and can only target a single trusted workspace. Crisp does not publicly document exact numeric limits — they state the limits are "quite permissive" but reserve the right to change them.

When rate-limited, Crisp returns `429 Too Many Requests` or `420 Enhance Your Calm`. Your extraction script must implement exponential backoff and respect any `Retry-After` headers in the response.

### Gorgias API Rate Limits

Gorgias uses a **leaky bucket algorithm** with account-scoped limits that vary by authentication method and plan tier:([developers.gorgias.com](https://developers.gorgias.com/v1.1/reference/limitations?utm_source=openai))

| Auth Type | Rate Limit |
|---|---|
| **OAuth2 apps** | 80 requests per 20-second window |
| **API key integrations** | 40 requests per 20-second window |
| **Starter/Basic plans** | ~2 req/s |
| **Pro/Advanced plans** | ~10 req/s |
| **Enterprise** | Custom (negotiable) |

When the limit is exceeded, the API returns `429 Too Many Requests`. Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Your migration script **must** read the `Retry-After` header and implement exponential backoff.

### Gorgias Pagination

Gorgias uses **cursor-based pagination** across all list endpoints (offset-based pagination was removed in February 2024). The `limit` parameter defaults to 30 items and has a strict maximum of 100 per request. You follow `next_cursor` values until they return `null`. Do not attempt to batch 500 messages in a single payload — the API will reject it.

Gorgias list endpoints **do not support server-side date filtering**, so any filtering by creation or update timestamp must happen client-side after fetching.([developers.gorgias.com](https://developers.gorgias.com/reference/list-customers))

> [!WARNING]
> Failure to engineer a resilient retry mechanism for Gorgias's rate limits is the leading cause of incomplete data migrations. For recovery strategies, see [Helpdesk Migration Failed? The Engineer's Rescue Guide](https://clonepartner.com/blog/blog/helpdesk-migration-failed-the-engineers-rescue-guide/).

## Migration Approaches: Choose the Least-Wrong Path

There are five paths to evaluate. The right choice depends on your engineering bandwidth, data volume, and tolerance for risk.

### 1. Native CSV Export/Import (Contact Seeding Only)

Export contact profiles from Crisp, clean the file, and manually create customers and tags in Gorgias. This is **only viable for customer seeding** or very small archives where conversation history is intentionally out of scope. Crisp does not natively export conversations as CSV, so you lose thread structure, message authorship, attachments, and note continuity.([help.crisp.chat](https://help.crisp.chat/en/article/how-to-export-contact-profiles-g1h8jm/))

### 2. Custom API-to-API Script (Python/Node.js)

Write extraction scripts against the Crisp REST API, transform the data locally, and push it into Gorgias via their REST API.

**When to use:** You have a dedicated engineer with 2–4 weeks to build, test, and validate. Your dataset is small enough (under 50K conversations) that rate limits won't extend the migration to weeks.

**Pros:** Full control over transformation logic, timestamp preservation via `sent_datetime`, and edge case handling. No third-party dependency.

**Cons:** You must build retry logic, rate limit handling, pagination, attachment download/re-upload, deduplication, and idempotency yourself. Error recovery is entirely on you.

### 3. Third-Party Self-Serve Tools (e.g., Help Desk Migration)

Self-serve platforms like Help Desk Migration offer a UI-based mapping interface. You connect both accounts, map fields, run a demo migration, and then execute.([help-desk-migration.com](https://help-desk-migration.com/gorgias/?utm_source=openai))

**When to use:** You want a faster path and your data is relatively standard — contacts, conversations, messages without complex custom metadata or heavy attachment structures.

**Pros:** Faster setup, no code required, demo migration included for validation.

**Cons:** Less control over transformation logic. May not handle edge cases well (inline images, bot messages, custom data fields). Pricing scales with record count. Help Desk Migration documents caveats around inline images, large attachments, and comment-heavy tickets.

Briskport takes a different approach: it exports Crisp to PostgreSQL in real time with historical retrieval in beta, making it better as an extraction layer than a direct Crisp-to-Gorgias loader.([briskport.com](https://briskport.com/?utm_source=openai))

### 4. Custom ETL Pipeline (Warehouse-Backed)

Land Crisp data into a warehouse or object store, normalize it there, then run a controlled load into Gorgias with workers, checkpoints, and replay support.

**When to use:** Enterprise compliance requirements, auditability needs, or when you need repeated rehearsal runs before cutover.

**Pros:** Best observability and repeatability. **Cons:** More infrastructure than most teams expect, and still constrained by target API write limits.

### 5. Middleware Platforms (Make, Zapier)

Trigger on new or updated objects and push them into Gorgias with no-code flows. Crisp has a verified Make app, and Zapier exposes Gorgias `Create Ticket` and `Create Ticket Message` actions.([make.com](https://www.make.com/en/integrations/crisp?utm_source=openai))

**When to use:** Very small deltas, temporary coexistence during cutover, or post-migration automations. **Not a viable primary migration path** for historical data.

**Cons:** Weak replay, weak idempotency, poor ergonomics for high-volume history, and ordering issues. Make's Gorgias connector is community-maintained rather than vendor-official.

### Comparison Table

| Factor | Custom Script | Self-Serve Tool | Custom ETL | Managed Service |
|---|---|---|---|---|
| Engineering effort | 2–4 weeks | Days | 2–4 weeks | Zero |
| Attachment handling | Manual build | Varies | Manual build | Included |
| Timestamp preservation | Full control | Depends on tool | Full control | Full control |
| Rate limit management | Manual build | Built-in | Manual build | Built-in |
| Edge case handling | Manual | Limited | Manual | Full |
| Cost | Engineering salary | Per-record pricing | Infrastructure + salary | Fixed project fee |
| Best for | Small data, strong dev team | Standard data, fast timeline | Enterprise compliance | Large/complex data, no dev bandwidth |

> [!TIP]
> **Small business (under 10K conversations, under 5K contacts):** A self-serve tool or careful custom script works. **Enterprise (100K+ conversations, attachments, multiple operators):** A managed service or a very well-tested custom ETL pipeline is the only safe bet.

## Step-by-Step API Migration Architecture

Here is the technical workflow for an API-based migration, whether you build it yourself or want to understand what a managed service does under the hood.

### Step 1: Pre-Migration Audit and Configuration Freeze

Before touching an API key, audit your Crisp workspace. Count total contacts, conversations, messages, and attachments. Identify spam, old closed sessions nobody searches, and inactive operators. Purge unnecessary data before migration to reduce API load and speed up the transfer.

Freeze your Crisp configuration during the migration window — no new segments, no operator changes, no new custom fields. On the Gorgias side, freeze or scope rules during the import window. Gorgias rules apply across all stores and channels, and API-channel tickets can still trigger some rule behavior like auto-tagging.([docs.gorgias.com](https://docs.gorgias.com/en-US/rules-101-81746?utm_source=openai))

Generate a Crisp plugin token via the Marketplace (development for testing, production for the real run). Generate a Gorgias API key from Settings → REST API. Plugin tokens give you higher daily quotas on Crisp.

### Step 2: Extract from Crisp

**Contacts/People Profiles:**
Paginate through `GET /v1/website/{website_id}/people/profiles` using page numbers. Extract email, name, segments, and custom data for each profile.

**Conversations:**
Paginate through `GET /v1/website/{website_id}/conversations` page by page. Crisp uses classic page-number pagination for conversation lists. For each conversation, capture the `session_id`, state (resolved/unresolved/pending), assigned operator, and metadata.

**Messages per Conversation:**
For each `session_id`, call `GET /v1/website/{website_id}/conversation/{session_id}/messages`. Messages include type (`text`, `file`, `note`, `animation`, etc.), content, timestamp, and sender identity (`user` or `operator`). Message paging uses `timestamp_before`.([docs.crisp.chat](https://docs.crisp.chat/references/rest-api/v1/))

**Attachments:**
Download file attachments from Crisp immediately during extraction. Crisp stores files on AWS S3, and URLs will eventually expire after your account is closed. For email-origin messages, use the original-message endpoint if you need source HTML and headers rather than a simplified body.

```python
# Pseudo-code: Extract conversations from Crisp
import crisp_api

client = crisp_api.Crisp()
client.authenticate_tier("plugin", IDENTIFIER, KEY)

page = 1
all_conversations = []

while True:
    convos = client.website.list_conversations(WEBSITE_ID, page)
    if not convos:
        break
    all_conversations.extend(convos)
    page += 1

# For each conversation, fetch messages and files
for convo in all_conversations:
    session_id = convo["session_id"]
    messages = client.website.get_messages_in_conversation(
        WEBSITE_ID, session_id
    )
    convo["messages"] = messages
```

### Step 3: Transform

This is where the data model translation happens:

1. **Map People Profiles → Gorgias Customers:** Match on email. Build a lookup dict of `crisp_people_id → gorgias_customer_id` after creating or matching customers in Gorgias.
2. **Map Conversations → Tickets:** Each Crisp conversation becomes one Gorgias ticket. Set `channel` to `"chat"` and `via` to `"api"`. Map `resolved` → `closed`, `unresolved`/`pending` → `open`. Store `session_id` in `external_id` for idempotency and audit trail.
3. **Map Messages → Ticket Messages:** For each message, set `sent_datetime` to the original Crisp message timestamp. Set `from_agent` based on the Crisp `from` field (`operator` = true, `user` = false).
4. **Map Segments → Tags:** Crisp segments are pipe-separated strings on the people profile. Convert each segment to a `{"name": "segment_value"}` object in the Gorgias ticket tags array.
5. **Map Notes → Internal Notes:** Crisp messages with `type: "note"` become Gorgias messages with `channel: "internal-note"`.
6. **Generate Ticket Subjects:** Crisp conversations don't have a "subject" field. Generate one from the first message or a truncated preview. Gorgias enforces a **998-character limit** on ticket subjects.

> [!CAUTION]
> **If you omit `sent_datetime` on imported messages, Gorgias will attempt to send them to your customers.** This is the single most common and destructive mistake in Gorgias migrations. Always set `sent_datetime` for every historical message. Also consider setting `imported: true` on messages to explicitly flag them as historical.([developers.gorgias.com](https://developers.gorgias.com/reference/create-ticket))

### Step 4: Load into Gorgias

Dependency order matters. A stable sequence is: create customer → create ticket with one seed message → append remaining messages in chronological order → upload and attach files → apply tags → assign agent if mapped.

Gorgias ticket creation requires a `messages` array and caps that array at 500 items, so a one-message seed plus append loop is safer than trying to cram a long Crisp session into a single create-ticket payload.([developers.gorgias.com](https://developers.gorgias.com/reference/create-ticket))

**Create Customers first:**
```python
# POST https://{domain}.gorgias.com/api/customers
payload = {
    "email": "customer@example.com",
    "name": "Jane Doe",
    "channels": [{"type": "email", "address": "customer@example.com"}]
}
```

**Create Tickets with a seed message:**
```python
# POST https://{domain}.gorgias.com/api/tickets
payload = {
    "customer": {"email": "customer@example.com"},
    "channel": "chat",
    "via": "api",
    "status": "closed",
    "external_id": "crisp_session_abc123",
    "tags": [{"name": "migrated-from-crisp"}, {"name": "vip-segment"}],
    "messages": [{
        "channel": "chat",
        "via": "api",
        "from_agent": False,
        "sender": {"email": "customer@example.com"},
        "body_text": "Hi, I need help with my order.",
        "body_html": "<p>Hi, I need help with my order.</p>",
        "sent_datetime": "2025-06-15T14:22:00+00:00",
        "created_datetime": "2025-06-15T14:22:00+00:00"
    }]
}
```

**Append subsequent messages:**
```python
# POST https://{domain}.gorgias.com/api/tickets/{ticket_id}/messages
message_payload = {
    "channel": "chat",
    "via": "api",
    "from_agent": True,
    "sender": {"email": "agent@yourstore.com"},
    "body_text": "Sure, let me look into that.",
    "sent_datetime": "2025-06-15T14:25:00+00:00"
}
```

### Step 5: Handle Attachments

Crisp stores file attachments as URLs within the message `content` object. Crisp also exposes a separate conversation file-listing endpoint. To migrate attachments:

1. Download the file from the Crisp URL
2. Upload to Gorgias via `POST /api/upload?type=attachment`
3. Embed the returned Gorgias URL in the message `body_html`

This is an N+2 API call per attachment (download, upload, create message), which makes attachment-heavy migrations significantly slower. Upload files before creating the message that references them.

### Step 6: Rate Limit Handling

Your script must intercept `429` status codes on both sides. Here is a structural example:

```python
import time
import requests

def gorgias_request(method, url, **kwargs):
    """Make a Gorgias API request with automatic retry on 429."""
    while True:
        response = requests.request(method, url, **kwargs)
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 5))
            time.sleep(retry_after)
            continue
        response.raise_for_status()
        return response.json()
```

Log every source-to-target ID pair — customer, ticket, message, attachment, and operator. If the job dies halfway through, resume by `external_id` lookup and manifest state, not by blind re-creation.

## Edge Cases That Break Migrations

These are the failure modes that cause silent data loss or agent pain. If your migration plan does not account for each of these, it is incomplete.

### Inactive or Deleted Operators

Crisp conversations reference operator user IDs. If that operator no longer exists in Gorgias, the API will reject the payload when you try to assign a historical message to them. You have two choices: create a deactivated placeholder agent (set `active: false` after creation), or attribute those messages to a generic "Legacy Agent" sender. Gorgias documents that if an internal note sender is not an existing user, the note can appear as sent by `Gorgias Bot`.([developers.gorgias.com](https://developers.gorgias.com/docs/create-a-ticket-using-api))

Pre-create matching users if authorship matters. Preserve the original Crisp operator identity in message metadata either way.

### Duplicate Customer Records

Crisp identifies contacts primarily by email but also supports anonymous sessions with no email. Gorgias deduplicates on email and will return `409` conflicts for identifiers already in use.([developers.gorgias.com](https://developers.gorgias.com/reference/list-customers)) Anonymous Crisp conversations either get dropped or assigned to a placeholder customer — decide this before migration, not during.

Crisp also often creates duplicate profiles if a user chats anonymously and later provides an email. Deduplicate these records based on email before pushing them to Gorgias.

### Inline Images vs. File Attachments

Crisp has distinct message types: `file` (explicit attachment) and `text` with embedded image URLs. Both need different handling on the Gorgias side. Images pasted directly into Crisp chats are embedded as markdown or HTML `<img>` tags. Your script must parse the message body, extract the image URL, download the asset, upload it to Gorgias, and rewrite the HTML body with the new Gorgias-hosted URL. Text messages with inline image URLs render as broken links in Gorgias unless you download and re-host the images.

### Gorgias Ticket Subject Length

Gorgias enforces a **998-character limit** on ticket subjects. Crisp conversations don't have a "subject" field — you need to generate one, typically from the first message or a truncated preview.

### Conversation Message Limits

Crisp caps conversations at roughly 10,000 messages. If you have conversations approaching this limit, they may need to be split into multiple Gorgias tickets.

### Bot Messages and System Events

Crisp logs chatbot interactions, automated triggers, and system events as messages. Decide whether to import these as messages, internal notes, or skip them entirely. Importing bot-generated "typing" indicators as ticket messages clutters the Gorgias UI.

### Payload Size Limits

Gorgias can reject oversized payloads with `413` errors, and message `body_text` or `body_html` is limited to 1 MB. Keep one-to-one message granularity and queue writes rather than batching.([developers.gorgias.com](https://developers.gorgias.com/v1.1/reference/errors?utm_source=openai))

### Rich Message Types

Crisp supports carousels, pickers, and field message types that have no Gorgias equivalent. These must be converted to plain text or HTML.

For more on diagnosing broken migrations, see [Helpdesk Migration Failed? The Engineer's Rescue Guide](https://clonepartner.com/blog/blog/helpdesk-migration-failed-the-engineers-rescue-guide/).

## Validation and Testing

Never run a migration without a validation plan. Here is the minimum.

### Record Count Comparison
- Total conversations in Crisp vs. total tickets in Gorgias
- Total contacts in Crisp vs. total customers in Gorgias
- Total messages per conversation vs. messages per ticket (sample 50 randomly)
- Total attachments expected vs. attachments that resolve

### Field-Level Validation
- Timestamps match original Crisp values (check `created_datetime` on the Gorgias ticket and message)
- Customer email matches across systems
- Tags and segments mapped correctly
- Attachments are accessible (URLs resolve)
- Internal notes remain hidden from the customer view

### Sampling Strategy
Pull 5% of migrated tickets at random. For each, manually compare the Gorgias ticket against the original Crisp conversation. Verify message order, sender attribution, and note visibility. Focus sampling on ugly records: long threads, email-origin conversations, deleted operators, and sessions with file attachments.

### Agent UAT
Have 2–3 support agents review their own historical conversations in Gorgias. They will spot issues — wrong agent attribution, missing context, broken formatting — that automated checks miss.

### Rollback Plan
Before migration, tag all imported records with `migrated-from-crisp`. If validation fails, you can bulk-delete by tag in Gorgias and re-run. Store the Crisp `session_id` in Gorgias's `external_id` field for traceability.

## Post-Migration Tasks

Getting the data in is half the job. Here is what comes after.

- **Rebuild automations:** Crisp triggers, bots, and routing rules do not migrate. Rebuild them as Gorgias Rules from scratch. Map each Crisp automation to its Gorgias equivalent before cutover. Recreate only the workflows that still make sense in a ticket-first model.([docs.gorgias.com](https://docs.gorgias.com/en-US/rules-101-81746?utm_source=openai))
- **Connect e-commerce stores:** Wire up your Shopify, BigCommerce, or Magento store to Gorgias and test order actions end to end. This is usually the whole reason for the migration — do not save it for last.
- **Swap the widget:** Replace the Crisp chat widget with the Gorgias chat widget on your storefront. Coordinate this with DNS changes if you use email forwarding.
- **Agent training:** Gorgias's two-status model (Open/Closed) is simpler than most helpdesks, but the macro system, views, and rule engine require onboarding. Make sure agents understand that Crisp sessions are now tickets, that `pending` may live as a tag, and that imported notes may show mapped users.
- **Monitor for 2 weeks:** Watch for duplicate customer creation, missing attachments, rules touching imported history, and any Crisp conversations that arrived during the cutover window.

For a structured approach to keeping support running during migration, see [Zero-Downtime Helpdesk Migration](https://clonepartner.com/blog/blog/zero-downtime-helpdesk-migration/). For the broader post-migration launch sequence, see the [Helpdesk Migration Checklist](https://clonepartner.com/blog/blog/helpdesk-migration-checklist/).

## What You'll Lose in the Move

Some data does not survive the platform boundary:

- **Crisp chatbot flow state:** Bot conversation trees, triggers, and automation scenarios must be rebuilt in Gorgias's rule engine from scratch.
- **Visitor browsing data:** Crisp tracks page views and visitor activity. Gorgias does not store pre-conversation browsing behavior.
- **Custom Crisp plugins and widgets:** Crisp Marketplace plugins have no Gorgias equivalent. Evaluate replacements from the Gorgias app store.
- **Conversation threading within a single session:** If a Crisp conversation used threads (splitting topics within one session), Gorgias has no equivalent. You will need to flatten or split.
- **Rich message types:** Crisp carousels, pickers, and field message types must be converted to plain text or HTML.
- **Crisp people notepad:** No first-class equivalent in Gorgias's customer create API. Store it in `customer.data` or emit a synthetic internal note on the first migrated ticket.

## Migration Script Reference (Python)

For teams building in-house, here is the structural skeleton. This is not production-ready code — it is an architecture reference.

```python
import time
import requests
from crisp_api import Crisp

# --- Config ---
CRISP_WEBSITE_ID = "your-website-id"
GORGIAS_DOMAIN = "yourstore.gorgias.com"
GORGIAS_API_KEY = "your-api-key"
GORGIAS_EMAIL = "admin@yourstore.com"

crisp = Crisp()
crisp.authenticate_tier("plugin", "<identifier>", "<key>")

def gorgias_post(endpoint, payload):
    """POST to Gorgias with retry on 429."""
    url = f"https://{GORGIAS_DOMAIN}/api/{endpoint}"
    auth = (GORGIAS_EMAIL, GORGIAS_API_KEY)
    while True:
        resp = requests.post(url, json=payload, auth=auth)
        if resp.status_code == 429:
            wait = int(resp.headers.get("Retry-After", 5))
            time.sleep(wait)
            continue
        resp.raise_for_status()
        return resp.json()

# --- Phase 1: Extract & Create Customers ---
page = 1
customer_map = {}  # crisp_email -> gorgias_customer_id
while True:
    profiles = crisp.website.list_people_profiles(CRISP_WEBSITE_ID, page)
    if not profiles:
        break
    for profile in profiles:
        email = profile.get("email")
        if not email:
            continue
        gorgias_customer = gorgias_post("customers", {
            "email": email,
            "name": profile.get("person", {}).get("nickname", ""),
        })
        customer_map[email] = gorgias_customer["id"]
    page += 1

# --- Phase 2: Extract Conversations & Load as Tickets ---
page = 1
while True:
    conversations = crisp.website.list_conversations(CRISP_WEBSITE_ID, page)
    if not conversations:
        break
    for convo in conversations:
        session_id = convo["session_id"]
        meta = crisp.website.get_conversation_metas(
            CRISP_WEBSITE_ID, session_id
        )
        messages = crisp.website.get_messages_in_conversation(
            CRISP_WEBSITE_ID, session_id
        )
        # Transform messages
        gorgias_messages = []
        for msg in messages:
            gorgias_msg = {
                "channel": "internal-note" if msg["type"] == "note" else "chat",
                "via": "api",
                "from_agent": msg["from"] == "operator",
                "body_text": msg.get("content", ""),
                "sent_datetime": msg["timestamp"],  # CRITICAL
            }
            if msg["from"] == "operator":
                gorgias_msg["sender"] = {
                    "email": meta.get("assigned_operator_email", "bot@yourstore.com")
                }
            else:
                gorgias_msg["sender"] = {
                    "email": meta.get("email", "unknown@example.com")
                }
            gorgias_messages.append(gorgias_msg)

        # Create ticket with first message, append rest
        if gorgias_messages:
            ticket = gorgias_post("tickets", {
                "customer": {"email": meta.get("email", "unknown@example.com")},
                "channel": "chat",
                "via": "api",
                "status": "closed" if convo.get("state") == "resolved" else "open",
                "external_id": session_id,
                "tags": [{"name": s} for s in meta.get("segments", [])],
                "messages": [gorgias_messages[0]],
            })
            for msg in gorgias_messages[1:]:
                gorgias_post(f"tickets/{ticket['id']}/messages", msg)
    page += 1

print("Migration complete. Run validation checks.")
```

> [!NOTE]
> This script is a structural reference. Production code must add: error logging per record, idempotency checks (skip if `external_id` exists), attachment download/upload, and a progress checkpoint system so you can resume after failures.

## Best Practices Checklist

- [ ] **Back up Crisp data** to a local JSON dump before starting any writes to Gorgias
- [ ] **Run a test migration** with 100–1,000 conversations first — validate every field before scaling
- [ ] **Set `sent_datetime`** on every imported message without exception
- [ ] **Store `session_id` in `external_id`** on Gorgias tickets for audit trail and idempotency
- [ ] **Tag all imported records** with `migrated-from-crisp` for easy identification and rollback
- [ ] **Monitor Gorgias rate limit headers** — read the `Retry-After` value, do not guess
- [ ] **Handle anonymous contacts** — decide skip vs. placeholder before migration, not during
- [ ] **Coordinate widget swap** with your development team — the delta between bulk migration and widget cutover is where data gets lost
- [ ] **Freeze Crisp configuration** during migration — no new segments, no operator changes
- [ ] **Freeze or scope Gorgias rules** during import to prevent auto-actions on historical data
- [ ] **Rebuild Gorgias rules and macros** manually — they do not transfer from Crisp
- [ ] **Log every source-to-target ID pair** for resumability and debugging

## When to Use a Managed Migration Service

Build in-house when your dataset is small, your conversations are text-only, and you have an engineer who can commit 2–4 weeks without derailing your product roadmap. Outsource when:

- You have **100K+ conversations** and rate limit math means your script will run for weeks
- Your conversations contain **thousands of file attachments** requiring download/re-upload pipelines
- You need **zero data loss** and cannot afford to debug edge cases in production
- Your engineering team is **shipping product** and cannot absorb a multi-week infrastructure project
- You need **delta migration** — a point-in-time sync of conversations that arrived between the initial migration run and the final widget cutover

The hidden cost of DIY is not the script — it is the debugging. Rate limit failures at 3 AM, orphaned messages from a crashed script, duplicate customers from a retry loop without idempotency. These edge cases consume far more engineering time than the initial build.

### How ClonePartner Handles Crisp to Gorgias Migrations

ClonePartner runs engineer-led, zero-downtime helpdesk migrations. For Crisp to Gorgias, here is what that means in practice:

- **Full API extraction** that bypasses Crisp's 200K contact CSV export limit by pulling directly from the REST API with managed quotas.
- **Structural transformation** from Crisp's chat-session model to Gorgias's ticket-thread model, preserving original timestamps, agent attribution, and attachment links.
- **Rate limit management** with built-in exponential backoff against both Crisp and Gorgias `429` responses — no data is dropped.
- **Delta migration** at cutover: we run a point-in-time delta sync to capture conversations that arrived between the initial bulk migration and the widget swap. This closes the gap without the complexity of a real-time sync pipeline.
- **Validation and QA** with record-count comparison, field-level sampling, and agent-facing UAT before signoff.

The typical Crisp-to-Gorgias migration completes in days, not weeks. Your engineering team stays focused on product.

> Migrating from Crisp to Gorgias and don't want to burn engineering weeks on rate limit debugging and attachment re-hosting? Book a 30-minute call and we'll scope your migration — timeline, data volume, and edge cases included.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I export Crisp conversations as CSV?

No. Crisp only supports CSV export for contact profiles, capped at 200,000. Conversation and message data must be extracted via the Crisp REST API or a third-party tool that uses it.

### What are the Gorgias API rate limits for data migration?

Gorgias uses a leaky bucket algorithm. OAuth2 apps get 80 requests per 20-second window; API key integrations get 40 requests per 20 seconds. Exceeding the limit returns a 429 error with a Retry-After header you must respect.

### Will Gorgias resend imported messages to customers?

Yes, if you don't set the sent_datetime field. Any message created via the Gorgias API without sent_datetime is treated as new and Gorgias will attempt to deliver it. Always set sent_datetime for historical imports.

### How do Crisp conversations map to Gorgias tickets?

Each Crisp conversation (identified by session_id) typically becomes one Gorgias ticket. Messages become ticket messages, segments map to tags, and private notes become internal-note channel messages. Crisp's three-state model (pending/unresolved/resolved) must be flattened to Gorgias's two states (open/closed).

### Can Make or Zapier migrate all historical Crisp chats to Gorgias?

Not well. Both platforms expose useful connectors, but they lack the replay logic, idempotency, and throughput needed for high-volume historical backfills. They are better suited for small deltas or post-migration automations.
