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.
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.
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_idsduring 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.
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:sessionsscope for conversations andwebsite:people:profilesfor People data. - Operator identity in messages: Crisp messages carry a
fromfield that includes the operator'suser_id. To resolve which human agent sent each message, callGET /website/{website_id}/operators/listto build auser_id → name/emaillookup 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 Requestsor420 Enhance Your Calmwhen rate-limited. (docs.crisp.chat) - Pagination: Conversations are listed with
page_numberandper_page(up to 50 per page). Messages within a conversation are paginated by timestamp usingtimestamp_before,timestamp_after, ortimestamp_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
emailcan be created via the API. Chat, WhatsApp, SMS, and social conversations from Crisp all becomeemail-type tickets. - Timestamps: The public API does not accept custom
created_attimestamps 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_atinstead ofpagefor iteration.
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 nickname → first_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:
- Export all unique segments from Crisp using the
listSuggestedConversationSegmentsendpoint. - Normalize segment names (lowercase, trim whitespace) to avoid duplicates.
- Create corresponding labels in each target Enchant inbox.
- 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.comIf 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 locallyDownload 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:
- Download the file from Crisp's CDN URL (done in Step 3).
- Base64-encode the file content. Note: base64 encoding increases payload size by approximately 33%.
- Upload to Enchant:
POST /api/v1/attachments
Content-Type: application/json
{
"name": "screenshot.png",
"type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAA..."
}- Use the returned
attachment_idwhen creating the message. Each attachment can only be associated with one message.
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.
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 responseKey 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:
- Check the log for the
crisp_session_id. - If status is
complete, skip entirely. - If status is
ticket_created, skip ticket creation and proceed to messages and state update. - If status is
customer_created, skip customer creation and proceed to ticket creation. - 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
closedin Enchant and unresolved ones areopen. - Error log review: Review every non-
2xxresponse 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:
- Run the bulk migration.
- Capture deltas via Crisp webhooks during the migration window.
- Pause incoming on Crisp (remove the widget, update email forwarding).
- Replay the captured deltas into Enchant.
- Route new channels to Enchant.
- 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.
