Skip to content

Crisp to Enchant Migration: A Technical Guide

Technical guide to migrating from Crisp to Enchant: API constraints, object mapping, rate limits, attachment handling, timestamp limitations, and edge cases covered step by step.

Raaj Raaj · · 21 min read
Crisp to Enchant Migration: A Technical Guide
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Crisp to Enchant Migration: A Technical Guide

Migrating from Crisp to Enchant means moving from a conversation-centric live chat platform to a ticket-based omnichannel shared inbox. Crisp centers everything around Conversations tied to a Website workspace, with People profiles and free-form Segments. Enchant organizes work around Tickets belonging to Inboxes, with Customers, Contacts, and Labels driving categorization.

There is no one-click migration path. You need to extract data from Crisp's REST API, transform it to match Enchant's data model, and write it via Enchant's REST API — or use Enchant's managed import service for read-only archive data. The right approach depends on whether migrated records need to behave like normal working tickets.

This guide covers both paths, with full technical detail for the API-led migration: object mapping, API constraints, attachment handling, idempotency patterns, error handling, failure modes, and validation.

Last verified against Crisp REST API V1 and Enchant REST API v1: July 2025. API details change — always cross-reference the Crisp API documentation and Enchant API documentation before implementing.

Why This Is Not a 1:1 Migration

Crisp is conversation-centric. A Crisp conversation is anchored on a session_id and carries metadata like inbox_id, people_id, operator routing, state, and a message stream that includes text, note, file, animation, audio, picker, field, carousel, and event types. (docs.crisp.chat)

Enchant is ticket-centric. An Enchant ticket groups replies and private notes around a customer, inbox, assignee, labels, and state. In the product UI, tickets can represent email, chat, SMS, WhatsApp, phone, and social activity. In the public API, ticket creation is narrower: POST /tickets only supports type=email. (dev.enchant.com)

The profile mismatch is equally significant. Crisp People profiles store personal fields, company details, segments, notepad content, and arbitrary key-value data. Enchant customers are flatter: first name, last name, a summary text field, and contacts limited to email, Twitter, or phone. A clean migration preserves the operational minimum in Enchant and keeps the rest in a CRM, data warehouse, or custom sidebar integration. Enchant supports showing backend data on the customer profile in real time, which is often better than flattening everything into a long summary blob.

Teams typically move to Enchant when they need:

  • Structured ticket management: Enchant's discrete states (open, hold, closed, snoozed, archived) give more workflow control than Crisp's simpler conversation states.
  • Per-inbox isolation: Enchant provides inbox-level security permissions, labels, rules, and folders — useful for teams managing multiple brands or departments.
  • Email-first support: Enchant treats email as a first-class citizen, while Crisp's email handling is layered on top of its messaging core.
  • Phone channel support: Enchant has native Twilio-based phone channels with call recording and queuing.

Managed Import vs. API-Led Migration

Enchant offers two paths for getting external data in, and they produce very different outcomes.

Approach Best when Trade-offs
Enchant managed import You want old Crisp history inside Enchant for lookup after cutover Imported tickets are read-only — cannot be assigned, replied to, relabeled, or included in reports or live folders. The import requires your team to stop using the source platform first; there is no catch-up import for data added after the initial load. (help.enchant.com)
API-led recreation You need migrated records to act like normal working tickets You build the mapping, attachment replay, and validation yourself. Enchant's public API only creates email tickets.

Enchant stages a 100-ticket sample in a temporary account before the full managed import load, which helps spot formatting problems — but it does not change the archive-only behavior of the final result.

Warning

Do not route open work through the managed import. If a ticket still needs replies, reassignment, reporting visibility, or label changes after cutover, it must be recreated through the API. (help.enchant.com)

The rest of this guide focuses on API-led migration, which is the path that produces fully functional Enchant tickets.

Define Your Migration Scope

Before writing any code, categorize what needs to move and how.

What to Migrate via the API

The bulk of your operational data moves through API-to-API transfer:

  • Operators → Users: Crisp workspace operators become Enchant users (agents).
  • People → Customers & Contacts: Crisp People profiles map to Enchant Customers, with email, phone, and social identifiers becoming Enchant Contacts.
  • Conversations → Tickets: Each Crisp conversation becomes an Enchant ticket.
  • Messages → Messages: Text and note messages become Enchant replies or notes.
  • Files → Attachments: Files shared in Crisp conversations must be downloaded and re-uploaded as base64-encoded attachments.

What to Configure Manually

Certain structural elements should be set up by hand in Enchant before loading data:

  • Inboxes and Channels: Enchant organizes work by inbox, each with its own channels. Decide your inbox structure upfront — it determines where migrated tickets land. (help.enchant.com)
  • Labels: Enchant labels are scoped per inbox. Pre-create them before importing tickets so you can assign label_ids during migration.
  • Users: Enchant doesn't expose a user creation endpoint in its public API — users must be invited through the admin interface.
  • Rules and Folders: Enchant's automation rules and custom folders have no Crisp equivalents. Build these fresh.
  • Canned Responses: Crisp message shortcuts can be exported as CSV and recreated manually in Enchant.

What to Archive or Leave Behind

  • Bot conversations with no human reply: If you used Crisp's chatbot heavily, you may have thousands of conversations that never reached a human agent. These rarely add value in Enchant. In practice, workspaces with active chatbot deployments see 40–70% of their conversation volume fall into this category — audit your Crisp analytics before assuming everything needs to move.
  • Crisp-specific metadata: Visitor browsing events, geolocation data, and real-time widget events have no Enchant equivalent.
  • Campaign messages: Crisp marketing campaigns don't map to anything in Enchant. Archive separately if needed for compliance.
  • Anonymous sessions without email: Crisp allows visitors to chat without providing contact information. These conversations cannot be linked to an Enchant customer in any meaningful way. Estimate their share before scoping the migration — in many workspaces, 20–40% of chat sessions never capture an email address.
Tip

Should you migrate everything? Not necessarily. If you have years of chat-only conversations auto-resolved by bots, leave them in Crisp as a read-only archive. Focus your migration budget on conversations that have operational or compliance value.

API Constraints on Both Sides

Understanding the rate limits and quirks of each API is non-negotiable before you start building.

Crisp API (Source)

  • Authentication: Plugin tokens are recommended for migration-scale work. Register a plugin on the Crisp Marketplace to get production tokens. You'll need at minimum the website:conversation:sessions scope for conversations and website:people:profiles for People data.
  • Operator identity in messages: Crisp messages carry a from field that includes the operator's user_id. To resolve which human agent sent each message, call GET /website/{website_id}/operators/list to build a user_id → name/email lookup before extracting conversations. This endpoint is not prominently documented alongside the messages API but is essential for attribution — without it, you cannot identify which agent wrote each reply.
  • Rate limits: Plugin tokens use a daily request quota system rather than per-second limits. Exceeding your daily quota means you're rate-limited until the next day. Large conversation exports may require a quota increase through the Marketplace. Crisp returns 429 Too Many Requests or 420 Enhance Your Calm when rate-limited. (docs.crisp.chat)
  • Pagination: Conversations are listed with page_number and per_page (up to 50 per page). Messages within a conversation are paginated by timestamp using timestamp_before, timestamp_after, or timestamp_around — do not assume a thread can be pulled in one call.
  • Data access: Crisp does not offer a native CSV export for conversation history in the app. Contact profiles can be exported as CSV, but full conversation data requires the API. (help.crisp.chat)

Enchant API (Target)

  • Authentication: Bearer token via the API app installed from Enchant's settings. Each token gives unrestricted access to all account data.
  • Rate limits: 100 credits per minute for the entire account across all endpoints, users, and tokens. There's also a burst limit of 6 requests per second. Embedding related resources (e.g., ?embed=messages) costs an additional credit per embed type — avoid unnecessary embeds during write-heavy migration runs. (dev.enchant.com)
  • Ticket type constraint: Only tickets of type email can be created via the API. Chat, WhatsApp, SMS, and social conversations from Crisp all become email-type tickets.
  • Timestamps: The public API does not accept custom created_at timestamps on ticket or message creation. Migrated records receive the current system time, not the original Crisp timestamp. (dev.enchant.com)
  • Pagination: 0–100 results per page (default 10). For collections exceeding 10,000 records, use since_created_at instead of page for iteration.
Warning

The 6 req/sec burst limit on Enchant is the real bottleneck. Even if you stay under 100 credits/minute, hitting more than 6 requests in a single second triggers a 429. Build a request queue with per-second throttling, not just per-minute averaging.

Object Mapping: Crisp → Enchant

Crisp Object Enchant Object Notes
Website (workspace) Account 1:1 mapping
Operator User Map by email. Create users in Enchant before importing tickets.
People profile Customer Map nicknamefirst_name / last_name. Split on first space.
People email Contact (type: email) Direct mapping
People phone Contact (type: phone) Direct mapping
Conversation Ticket (type: email) Only email type supported via API
Conversation state Ticket state See state mapping below
Segments Labels Crisp segments are workspace-wide; Enchant labels are per-inbox
Conversation custom data Customer summary Flatten key-value pairs into text
Message (text) Message (type: reply) Map direction based on author
Message (note) Message (type: note) Direct mapping
Message (file) Attachment + Message Download, base64-encode, upload to Enchant
audio, picker, field, carousel, event, animation No direct equivalent Flatten into a note body or archive externally

Conversation State Mapping

Crisp State Enchant State Notes
pending open Awaiting first response — maps to needing agent attention
unresolved open Active conversations
resolved closed Completed conversations
N/A hold No direct Crisp equivalent
N/A snoozed Enchant supports snoozing with a snoozed_until timestamp

Segment → Label Mapping

Crisp segments are free-form, case-sensitive strings attached to conversations or People profiles. Enchant labels are structured objects scoped per inbox with their own IDs.

Before migration:

  1. Export all unique segments from Crisp using the listSuggestedConversationSegments endpoint.
  2. Normalize segment names (lowercase, trim whitespace) to avoid duplicates.
  3. Create corresponding labels in each target Enchant inbox.
  4. Build a lookup map: crisp_segment_string → enchant_label_id.

Step-by-Step Migration Sequence

Order matters. Enchant enforces referential integrity — you can't assign a ticket to a user that doesn't exist yet.

Step 1: Create Users and Labels in Enchant

Invite all Crisp operators as users in Enchant via the admin interface. Build a mapping table:

{
  "crisp_operator_id": "enchant_user_id",
  "a1b2c3d4-...": "501efc",
  "e5f6g7h8-...": "501edd"
}

If a Crisp operator no longer exists or won't have an Enchant account, create a "Legacy Agent" fallback user. When mapping historical messages from departed agents, prepend the message body with [Originally sent by: John Doe] to preserve context.

Pre-create all labels in each target inbox. Store the resulting label_id values for use when importing tickets.

Step 2: Migrate People → Customers

Migrate your customer base before tickets. Tickets in Enchant must be associated with a Customer object.

Idempotency first — check before creating. Before sending a POST /api/v1/customers, check whether the customer already exists:

GET /api/v1/customers?contacts.type=email&contacts.value=jane@example.com

If the response returns a non-empty data array, use the existing customer_id rather than creating a duplicate. This check is mandatory — migration scripts restart, and without idempotency guards you will create duplicate customer records that are difficult to clean up post-migration.

For each new Crisp People profile, create an Enchant Customer with associated Contacts:

POST /api/v1/customers
Content-Type: application/json
 
{
  "first_name": "Jane",
  "last_name": "Doe",
  "summary": "Migrated from Crisp. Crisp people_id: a1b2c3d4-... | Account ID: ACC-9912",
  "contacts": [
    {
      "type": "email",
      "value": "jane@example.com"
    }
  ]
}

Name splitting: Crisp stores nickname as a single string. Split on the first space — first token becomes first_name, everything else becomes last_name.

Custom data: Crisp People profiles are much richer than Enchant's customer model. Include the Crisp people_id and any critical external identifiers in the summary field — this is your audit trail if you need to correlate records later. Push everything else to your CRM or a custom integration rather than flattening it all into a long text blob.

Store the mapping: crisp_people_id → enchant_customer_id.

Step 3: Export Conversations and Messages from Crisp

A practical extraction flow:

GET /website/{website_id}/operators/list  → build operator lookup map
list conversations (page_number, per_page=50) → session_ids
for each session_id:
  GET conversation metadata (state, segments, metas, timestamp)
  page conversation/messages (timestamp_before / timestamp_after)
  GET conversation/files
  for each file URL: download binary immediately, store locally

Download files during extraction, not later. Crisp CDN URLs for files may expire after a retention period. If you extract message metadata now and plan to download files in a second pass, you risk finding broken URLs. Store binaries to local disk or object storage immediately.

Conversation listing is paged with per_page between 20 and 50. Message history is paged by timestamp — do not assume a thread can be pulled in one call. Partial exports from incomplete pagination are the most common self-inflicted error.

Filter out messages you don't need. Crisp supports message types beyond text and notes: animation, audio, picker, field, carousel, and event. These have no Enchant equivalent — flatten them into note bodies with the raw payload, or archive them externally. (docs.crisp.chat)

Step 4: Handle Attachments

Attachments are the most common failure point in helpdesk migrations. Enchant requires attachments to be uploaded as base64-encoded data before being linked to a message:

  1. Download the file from Crisp's CDN URL (done in Step 3).
  2. Base64-encode the file content. Note: base64 encoding increases payload size by approximately 33%.
  3. Upload to Enchant:
POST /api/v1/attachments
Content-Type: application/json
 
{
  "name": "screenshot.png",
  "type": "image/png",
  "data": "iVBORw0KGgoAAAANSUhEUgAA..."
}
  1. Use the returned attachment_id when creating the message. Each attachment can only be associated with one message.
Warning

File size mismatch: Crisp allows some image uploads up to 12MB. Enchant caps total attachments per message at 10MB. (help.crisp.chat) Combined with the ~33% base64 overhead, a 7.5MB raw file will exceed 10MB after encoding. You need a preprocessing rule for oversized files: resize images, upload to external storage and link in the message body, or catch the error and log it for manual review.

Step 5: Create Tickets with Messages in Enchant

Idempotency — check before creating. Before creating a ticket, check whether it already exists using your migration log. A practical pattern: store a mapping of crisp_session_id → enchant_ticket_id in a local database or file after every successful create. On restart, skip session IDs that already have an entry.

For each Crisp conversation, construct the Enchant ticket payload. The first batch of messages can be included inline with ticket creation, reducing separate API calls:

{
  "type": "email",
  "subject": "Chat with Jane Doe - 2025-01-15 [crisp:abc123]",
  "customer_id": "51c3d1",
  "user_id": "501efc",
  "inbox_id": "533bcd",
  "messages": [
    {
      "type": "reply",
      "direction": "in",
      "from_name": "Jane Doe",
      "from": "jane@example.com",
      "body": "Hi, I need help with my account.",
      "htmlized": false
    },
    {
      "type": "reply",
      "direction": "out",
      "to": "jane@example.com",
      "body": "Hi Jane, happy to help! What's going on?",
      "htmlized": false,
      "user_id": "501efc"
    }
  ]
}

Add remaining messages via POST /api/v1/tickets/{ticket_id}/messages. Then set the final state and labels via PATCH /api/v1/tickets/{ticket_id}.

Subjects: Crisp conversations don't have subject lines. Enchant tickets require one. Embed the Crisp session_id in the subject using a bracketed tag (e.g., [crisp:abc123]) so you can search for a migrated ticket by its original Crisp ID without querying your mapping database. Also generate a human-readable prefix like Chat with [Customer Name] - [Date].

Channel labeling: Since all Crisp conversations become email-type tickets regardless of original channel, add a label like migrated-from-chat or migrated-from-whatsapp so your team can distinguish them in Enchant.

Original timestamp preservation: Enchant does not accept custom created_at timestamps via the API. Include the original Crisp timestamp in the ticket subject and as the first line of an internal note:

{
  "type": "note",
  "body": "Migration metadata:\nOriginal Crisp session_id: abc123\nOriginal created_at: 2024-03-15T10:23:00Z\nOriginal channel: chat\nOriginal agent: jane.agent@company.com"
}

This preserves audit context without relying solely on your migration database.

Danger

Enchant does not support setting custom created_at timestamps via the API. Migrated tickets will show the migration date, not the original conversation date. The subject-tag and migration-note patterns above are the only workarounds available through the public API. (dev.enchant.com)

Rate Limit Strategy

With Enchant's 100 credits/minute and 6 requests/second limits, you need a deliberate throttling strategy.

For a workspace with 10,000 conversations averaging 5 messages each:

  • ~10,000 customer creates (or lookups)
  • ~10,000 ticket creates
  • ~40,000 message creates
  • ~5,000 attachment uploads (estimated)
  • ~10,000 ticket updates (state, labels)

That's roughly 75,000 API calls to Enchant. At a sustainable 80 requests/minute (leaving headroom), that's approximately 15–16 hours of migration time. At 50,000 conversations, the same math yields 75–80 hours — plan for a multi-day window with checkpoint resumption.

Token Bucket Rate Limiter (Python pseudocode)

import time
import threading
 
class RateLimiter:
    def __init__(self, per_second=6, per_minute=100):
        self.per_second = per_second
        self.per_minute = per_minute
        self.second_tokens = per_second
        self.minute_tokens = per_minute
        self.last_second = time.time()
        self.last_minute = time.time()
        self.lock = threading.Lock()
 
    def acquire(self):
        with self.lock:
            now = time.time()
            # Refill second bucket
            elapsed_s = now - self.last_second
            if elapsed_s >= 1.0:
                self.second_tokens = self.per_second
                self.last_second = now
            # Refill minute bucket
            elapsed_m = now - self.last_minute
            if elapsed_m >= 60.0:
                self.minute_tokens = self.per_minute
                self.last_minute = now
            # Wait if either bucket is empty
            if self.second_tokens < 1:
                time.sleep(1.0 - (now - self.last_second))
                self.second_tokens = self.per_second
                self.last_second = time.time()
            if self.minute_tokens < 1:
                time.sleep(60.0 - (now - self.last_minute))
                self.minute_tokens = self.per_minute
                self.last_minute = time.time()
            self.second_tokens -= 1
            self.minute_tokens -= 1
 
limiter = RateLimiter(per_second=6, per_minute=95)  # 95 leaves headroom
 
def enchant_post(url, payload):
    limiter.acquire()
    response = requests.post(url, json=payload, headers=headers)
    if response.status_code == 429:
        reset = int(response.headers.get("Rate-Limit-Reset", 60))
        time.sleep(reset)
        return enchant_post(url, payload)  # retry once
    return response

Key points: enforce both constraints independently, leave headroom (95 instead of 100), and always read Rate-Limit-Reset before sleeping on a 429 rather than sleeping a fixed duration.

Avoid embed parameters during writes. Each ?embed= costs an additional credit. Never add embed parameters to POST or PATCH requests — only use them when you genuinely need the embedded data in the response.

Error Handling Reference

Enchant returns standard HTTP status codes. These are the errors you will encounter during migration writes:

Status Condition Recovery
400 Bad Request Missing required field, invalid type, malformed payload Log the full request body and response. Fix the payload. Do not retry without changes.
401 Unauthorized Invalid or expired bearer token Regenerate the token in Enchant settings. Update all request headers.
403 Forbidden Token lacks access to the resource (wrong account) Verify the token corresponds to the correct Enchant account.
404 Not Found Referenced customer_id, user_id, inbox_id, or label_id does not exist The referenced object wasn't created yet. Check your mapping table and creation order.
422 Unprocessable Entity Payload is valid JSON but fails business logic validation (e.g., email format, attachment size) Log and inspect. Common causes: malformed email in from field, attachment data not valid base64, message body is empty.
429 Too Many Requests Rate limit exceeded Read Rate-Limit-Reset header. Sleep that duration. Retry with exponential backoff (max 3 retries).
5xx Server Error Enchant-side error Retry with exponential backoff. After 3 failures, log and skip — resume after the migration completes.

Sample 400 response body for a missing customer_id:

{
  "error": {
    "status": 400,
    "message": "customer_id is required"
  }
}

Sample 422 for an empty message body:

{
  "error": {
    "status": 422,
    "message": "body can't be blank"
  }
}

Log every non-2xx response with the full request payload. This log is your post-migration repair dataset.

Idempotency Patterns

In a 15–80 hour migration window, your script will restart. Without idempotency guards, restarts create duplicate records.

Maintain a local checkpoint database (SQLite is sufficient):

CREATE TABLE migration_log (
  crisp_session_id TEXT PRIMARY KEY,
  enchant_ticket_id TEXT,
  crisp_people_id TEXT,
  enchant_customer_id TEXT,
  status TEXT,  -- 'pending', 'customer_created', 'ticket_created', 'complete', 'error'
  error_detail TEXT,
  updated_at TIMESTAMP
);

Before each create operation:

  1. Check the log for the crisp_session_id.
  2. If status is complete, skip entirely.
  3. If status is ticket_created, skip ticket creation and proceed to messages and state update.
  4. If status is customer_created, skip customer creation and proceed to ticket creation.
  5. If status is error, log and skip — process these manually after the bulk run.

For customers, the Enchant API lookup (GET /customers?contacts.type=email&contacts.value=...) is your server-side idempotency check. Always run it before POST /customers. If you get a hit, record the existing ID in your log.

For tickets, there is no equivalent server-side lookup by external reference. The [crisp:session_id] tag embedded in the subject can be used as a search key via GET /tickets?q=[crisp:abc123] — but the local checkpoint database is faster and more reliable.

Failure Mode Taxonomy

These are the top failure modes encountered in production Crisp-to-Enchant migrations, in descending order of frequency:

1. Incomplete Pagination (Most Common)

Symptom: Migrated tickets are missing messages. Customer reports that a conversation looks truncated. Cause: The Crisp message pagination loop exited early — either on a first-page-only assumption or a silent API error mid-pagination. Detection: After migration, compare message counts between your local extraction database and Enchant tickets. Any ticket with a message count delta greater than 1 needs investigation. Recovery: Re-extract messages for affected session_ids and append missing ones. Because Enchant assigns current timestamps, ordering may be disrupted — prepend each recovered message body with the original Crisp timestamp.

2. Expired Crisp File URLs

Symptom: Attachment upload fails with 400 or 404 when fetching from Crisp CDN. Cause: Files were not downloaded during extraction — the download was deferred to the load phase, by which time CDN URLs had expired. Detection: Any attachment POST that fails with a fetch error before base64 encoding. Recovery: None retroactively. If the Crisp workspace is still accessible, re-fetch from the Crisp files API. Otherwise the file is lost. Prevention: Download all binaries during Step 3 extraction, before the load phase begins.

3. Duplicate Customer Records

Symptom: The same customer appears twice in Enchant with different ticket histories. Cause: Script restarted without idempotency checks, or two Crisp People profiles share the same email address. Detection: Run GET /customers?contacts.type=email&contacts.value=X for a sample of migrated emails. Any response with data.length > 1 is a duplicate. Recovery: Identify the canonical record, merge ticket associations manually, delete the duplicate via DELETE /customers/{id}.

4. Agent Attribution Failures

Symptom: 404 on ticket or message create; all historical replies land on the "Legacy Agent" fallback regardless of original author. Cause: Departed operators were not mapped before migration began; the user_id referenced in the payload doesn't exist in Enchant. Detection: Count messages attributed to the Legacy Agent fallback. If it exceeds the known headcount of departed agents, the operator lookup map is incomplete. Recovery: Identify which crisp_operator_id values have no Enchant mapping, update the mapping table, and re-attribute affected messages by re-POSTing with the corrected user_id. (Enchant does not support updating an existing message's user_id — you must delete and re-create affected messages.)

5. Burst Limit Violations

Symptom: Intermittent 429 errors even though per-minute credit consumption appears within limits. Cause: The per-minute throttle was implemented correctly but the per-second burst limit (6 req/s) was ignored. Batched async requests fire simultaneously. Detection: Log request timestamps with millisecond precision. Inspect windows where more than 6 requests are sent within any 1-second interval. Recovery: Implement the token bucket described in the Rate Limit Strategy section. Retry failed requests with exponential backoff.

6. HTML/Markdown Rendering Corruption

Symptom: Message bodies display raw HTML tags or broken markdown in Enchant. Cause: Crisp messages containing markdown were imported with htmlized: true, or HTML messages were imported with htmlized: false. Detection: Spot-check 20–30 migrated tickets in the Enchant UI for messages containing links, bold text, or code snippets. Recovery: Re-POST affected messages with the correct htmlized value. Since Enchant doesn't support message updates, you must delete the malformed message and re-create it.

Edge Cases and Technical Gotchas

Conversations Without an Email Contact

Crisp allows anonymous chat sessions where the visitor never provides an email. Enchant requires a customer_id to create a ticket, and customers need at least a name to be useful.

For anonymous conversations, either create a placeholder customer (e.g., anonymous-{session_id}@placeholder.local) or skip them if they don't have operational value. In many workspaces, 20–40% of chat sessions fall into this category — quantify this before scoping the migration.

Chatbot Messages

Crisp chatbot interactions generate messages marked as automated. Enchant has no concept of a bot user. Two options:

  • Attribute bot messages to a designated "Bot" user in Enchant.
  • Import them as internal notes to distinguish them from human agent replies.

HTML vs. Markdown

Crisp messages can contain markdown or HTML depending on the channel. Enchant's message body supports HTML when htmlized is set to true. Convert Crisp markdown to HTML before importing, or set htmlized: false for plain text. Test rendering with a sample before running the full migration — code snippets, bolding, and inline links can break if the payload isn't sanitized.

Interactive Message Types

Crisp supports picker, field, carousel, audio, animation, and event message types that have no Enchant equivalent. Enchant only exposes reply and note at the message layer. Interactive content must be flattened into text/HTML in a note body, or archived externally. (docs.crisp.chat)

Departed Agents

Over time, agents leave your company. Their Crisp accounts may be deleted or deactivated. When migrating historical tickets, Enchant needs a valid user_id to attribute old replies. If you attempt to map a reply to a user who doesn't exist in Enchant, the API returns a 404. Use the Legacy Agent fallback user created in Step 1 and prepend the message body with [Originally sent by: John Doe] to keep the historical attribution visible.

What You Lose in the Migration

Crisp Feature Status in Enchant
Browsing events / page views Lost — no equivalent
Visitor geolocation Lost — no equivalent
Custom conversation data (key-value) Partial — flattened to customer summary text
Chatbot flow history Lost — no equivalent
Campaign message history Lost — no equivalent
Original created_at timestamps Lost via API — system-set on import
Conversation channel type Degraded — all become email via API
Real-time widget events Lost — no equivalent
Satisfaction ratings Lost — no direct import path
Interactive messages (picker, carousel, etc.) Degraded — must be flattened to text
Anonymous session history Lost or placeholder — no linkable customer identity

If preserving original timestamps is a hard requirement, Enchant's managed import service may support historical dates — but imported tickets become read-only. A hybrid approach (active tickets via API, historical archive via managed import) can sometimes close the gap.

Validation and Cutover

Don't trust row counts alone — validate at the thread level.

  • Customer count: Compare Crisp People profiles exported against Enchant Customers created.
  • Ticket count: Total in-scope Crisp conversations should match Enchant tickets.
  • Message count per ticket: Spot-check 20–30 tickets for message order and completeness. Flag any where Enchant count differs from Crisp by more than 1.
  • Attachment integrity: Download a sample of migrated attachments and compare file size and content against Crisp originals. Pay special attention to files near the 10MB/12MB boundary.
  • Label assignment: Verify segment-to-label mapping on a random sample.
  • User attribution: Check that agent messages are attributed to the correct Enchant user, not the fallback. Quantify how many messages landed on the Legacy Agent — if higher than the known departed headcount, the operator map is incomplete.
  • State accuracy: Verify that resolved Crisp conversations are closed in Enchant and unresolved ones are open.
  • Error log review: Review every non-2xx response logged during migration. Categorize by error type and assess whether each category needs a repair run.
  • Idempotency check: Re-run your customer lookup query on a sample of 50 migrated emails. Confirm no duplicates exist.

Cutover Sequence

For low-downtime cutovers, use a delta window. Crisp Website Hooks can send real-time JSON events like message:send on all plans, letting you run the bulk export first and then replay last-minute changes before switching routing. (docs.crisp.chat)

This matters because Enchant's managed import requires source data to stop changing and doesn't support a catch-up pass.

A practical cutover sequence:

  1. Run the bulk migration.
  2. Capture deltas via Crisp webhooks during the migration window.
  3. Pause incoming on Crisp (remove the widget, update email forwarding).
  4. Replay the captured deltas into Enchant.
  5. Route new channels to Enchant.
  6. Verify Enchant inbox routing: send a test email to each configured inbox address and confirm it creates a ticket in the correct inbox with the expected label and assignee rules.

When to Call In Help

This migration is manageable for small workspaces (under 1,000 conversations) with a developer who can write and maintain a custom script. It gets complicated when:

  • You have 50,000+ conversations and the migration window spans multiple days of API calls.
  • Your Crisp workspace has heavy custom data that needs non-trivial transformation logic.
  • You need to preserve original timestamps and Enchant's managed import constraints don't fit.
  • You're running both platforms in parallel and need continuous sync during a transition period.
  • Your workspace has a high proportion of anonymous sessions or bot conversations that require classification logic before you can determine what to migrate.

At ClonePartner, we handle migrations with constraints like these — building custom extraction, transformation, and loading pipelines that manage rate limits, edge cases, and validation automatically. If the scope exceeds what a single engineer can comfortably own, we're here to help.

For teams considering other destinations alongside Enchant, our guides on Crisp to Gorgias and Crisp to Intercom cover the same source extraction with different target APIs. For similar target-side considerations, see our guides on migrating from Intercom to Enchant or Freshdesk to Enchant.

Frequently Asked Questions

Can I migrate Crisp conversations to Enchant as normal working tickets?
There is no native one-click migration. You need to extract data from Crisp's REST API, transform it to match Enchant's ticket/customer model, and write it via Enchant's REST API. Enchant's managed import is an alternative, but imported tickets are read-only and cannot be assigned, replied to, or included in reports.
Does Enchant support importing non-email conversations from Crisp?
No. Enchant's API only allows creating tickets of type 'email'. Chat, WhatsApp, SMS, and social conversations from Crisp will all be imported as email-type tickets. Use labels like 'migrated-from-chat' to preserve the original channel context.
Can I preserve original timestamps when migrating to Enchant?
The Enchant API does not accept custom created_at timestamps — tickets and messages receive the current system time. To preserve historical context, include original dates in ticket subjects and internal notes. Enchant's managed import may support historical timestamps, but those tickets become read-only.
What are Enchant's API rate limits for migration?
Enchant allows 100 credits per minute across the entire account with a burst limit of 6 requests per second. A 10,000-conversation migration with 5 messages each requires roughly 75,000 API calls, taking approximately 15–16 hours at a sustainable pace.
How do Crisp segments map to Enchant?
Crisp segments map to Enchant labels. Segments are free-form, case-sensitive strings; labels are structured objects scoped per inbox. Pre-create labels in Enchant before migration, normalize segment names to avoid duplicates, and build a lookup map from segment strings to label IDs.

More from our Blog