Podium to Crisp Migration: A Technical Guide
Technical guide to migrating from Podium to Crisp. Covers API constraints, data mapping, conversation import, field transformations, and edge cases for contacts, messages, and attachments.
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
Last verified: July 2025. Tested against Podium API v4 and Crisp REST API V1.
TL;DR: Podium → Crisp Migration
Podium is a customer interaction platform built around SMS messaging, reviews, and payments for local businesses. Crisp is a chat-first customer messaging platform for startups and SMBs. They share almost no structural overlap. Podium Contacts map to Crisp People Profiles. Podium Conversations become Crisp Conversations, but the session model is fundamentally different. Podium Locations map to Crisp Segments or custom data keys. Reviews, Payments, and Campaigns have no native Crisp equivalent — archive or discard them. There is no native migration path. Expect a custom API-based ETL pipeline as the only reliable approach for full-fidelity migration.
What Is a Podium to Crisp Migration?
A Podium to Crisp migration is the process of extracting contacts, conversations, message history, tags, custom fields, and metadata from Podium's messaging and review management platform and loading them into Crisp as People Profiles, Conversations, Messages, Segments, and custom data — while preserving conversation history and contact relationships.
This is not a like-for-like platform swap. Podium is an all-in-one customer interaction platform designed for local businesses, centered on SMS/text messaging, webchat, Google and Facebook review management, text-based payments, and marketing campaigns. Crisp is a chat-first customer messaging platform with a shared inbox, CRM, chatbot builder, and knowledge base — designed for startups and SaaS companies managing support across live chat, email, and social channels.
The data models are different at the core. Podium operates on a location-based, multi-channel messaging model where conversations are tied to physical business locations. Crisp operates on a website-based, session-centric model where conversations belong to a single workspace ("website"). A native "click-to-migrate" button does not exist between these two systems. Every migration decision requires deliberate transformation, not simple field copying.
For related platform migrations, see:
- Podium to HappyFox Migration: For teams evaluating ticketing-first destinations.
- Podium to Freshservice Migration: For teams moving toward ITSM.
- Crisp to Gorgias Migration: For the reverse direction or downstream e-commerce moves.
- Dixa to Crisp Migration: For teams migrating other platforms into Crisp.
Why Teams Move from Podium to Crisp
The shift is typically driven by operational needs rather than a single feature gap:
- Cost compression. Podium's pricing targets multi-location local businesses and currently routes buyers to a custom quote flow; published estimates place entry pricing at $399+/month. Crisp offers a free tier (2 seats), a Pro tier at $25/month per workspace (4 seats), and an Unlimited tier at $95/month per workspace (20 seats). For a 5-person support team running a single workspace, Crisp Unlimited costs ~$1,140/year vs. Podium's estimated $4,800+/year. (crisp.chat)
- Chat-first architecture. Teams that interact primarily through website live chat and email — rather than SMS and review management — find Crisp's chat widget, chatbot builder, and shared inbox more aligned to their workflow.
- Developer accessibility. Crisp provides official API wrappers for Python, Node.js, Ruby, and Go, plus an open-source conversation import tool. Podium's API requires a developer account application and approval process, which can take 2–5 business days.
- Simplified CRM. Crisp bundles a lightweight CRM (contacts, segments, custom data, company attributes) directly into the messaging platform. Teams over-served by Podium's review and payment features but under-served on contact management often land here.
- Agency and SaaS use cases. Crisp's multi-website management and plugin architecture appeal to agencies managing multiple client workspaces — a use case Podium's location-based model doesn't natively support.
Data Model Differences: Podium vs. Crisp
Understanding the structural mismatch is the single most important pre-migration step. These platforms were built for different jobs.
| Podium Concept | Crisp Equivalent | Notes |
|---|---|---|
| Organization | Workspace (Website) | 1:1 if single-org; multi-org requires multiple Crisp workspaces |
| Location | Segment / Custom Data | Crisp has no native "location" object. Use segments or a podium_location custom data key |
| Contact | People Profile | Direct mapping. Email is the dedupe key in Crisp |
| Contact Tags | Segments | Podium tags → Crisp segments. Crisp does not publish a hard limit on segments per workspace or per contact, but performance degrades above ~500 segments per workspace in practice |
| Contact Custom Fields | Custom Data (key-value) | Podium allows up to 8 typed custom fields with picklist validation; Crisp uses free-form key-value pairs with no type enforcement |
| Conversation | Conversation | Structural mapping, but session models differ significantly |
| Message (SMS/Email/Webchat) | Message (text/file/note) | Message types require transformation |
| Internal Note | Note message (type: "note") |
Only available on Crisp paid plans (Pro and above) |
| Review | ❌ No equivalent | Archive externally or discard |
| Payment | ❌ No equivalent | Archive externally or discard |
| Campaign | Campaign (Unlimited plan) | Manual rebuild required; no migration path for campaign content |
| Automation | Bot / Triggers | Manual rebuild required |
| Team Member / Agent | Operator | No programmatic bulk creation — operators must be invited via the Crisp dashboard or API individually |
Reviews and Payments have no Crisp equivalent. If your Podium usage is heavily centered on review management or text-to-pay, Crisp is not a drop-in replacement for those functions. Archive this data before decommissioning Podium.
Migration Approaches
There are five viable methods for moving data from Podium to Crisp. Each has different trade-offs depending on your data volume, engineering bandwidth, and fidelity requirements.
1. CSV Export/Import (Contacts Only)
How it works: Export contacts from Podium as CSV (Contacts page → Export), then import into Crisp via Contacts → Actions → Import Contact Profiles. Crisp maps CSV columns to Crisp fields during import. Podium's current contact export includes Name, Phone, Email, Contact Status, Address, Tags, and Date Added. (podium.com)
When to use it: Small datasets, contacts-only migration, no conversation history needed. Also useful as a preload before a later API-based history backfill.
Pros: Zero code required. Fast for small contact lists. Crisp handles column mapping interactively.
Cons: No conversations, no message history, no attachments. Crisp CSV import handles contacts only. Phone-only Podium contacts are a problem because Crisp's CSV import requires email. (help.crisp.chat)
Scalability: Works for up to ~50K contacts. Crisp imports of 20K+ profiles can take several minutes based on observed behavior. Crisp's documented contact export limit is 200,000 profiles per workspace, which gives an upper-bound signal on import capacity.
Complexity: Low.
2. API-Based Migration (Custom ETL Pipeline)
How it works: Extract data via Podium's REST API (v4), transform it into Crisp's data structures, then load into Crisp via the REST API. This is the only method that preserves conversation history, message threading, and contact relationships with full fidelity.
When to use it: Any migration where you need conversation history, attachments, internal notes, or full relational fidelity. Also supports delta sync via Podium's updated_at filtering and message webhooks. (docs.podium.com)
Pros: Full fidelity — contacts, conversations, messages, notes, metadata. Scriptable, repeatable, auditable.
Cons: Requires developer resources (80–160 engineer-hours for mid-size datasets of 10K–50K conversations). Podium API rate limit: 60 requests per minute per OAuth app, plus an undocumented daily envelope. Podium developer account requires application and approval. No official Podium SDK — you're working with raw REST calls.
Throughput estimate: At 60 requests/minute with 100 contacts per page, extracting 50K contacts takes ~8.3 hours of wall-clock time for extraction alone. Conversation extraction is slower because each conversation requires additional API calls for messages. A dataset of 50K conversations with an average of 15 messages each requires ~800K total API calls — approximately 9.3 days at the 60 req/min limit. Batching, parallelism across OAuth apps, and FTP extraction can reduce this significantly.
Scalability: Enterprise-ready with proper batching and rate limit management.
Complexity: High.
3. Crisp's Official Conversation Import Tool
How it works: Crisp maintains an open-source Node.js tool (crisp-import-conversations) that imports conversation JSON/JSONL/CSV/XML files into Crisp workspaces. It includes built-in adapters for several platforms.
Supported adapters: Gorgias, GrooveHQ, Help Scout, Tidio, WHMCS, Zendesk. No Podium adapter exists. You would need to write a custom adapter or transform Podium data into the generic Crisp conversation JSON schema before import.
The adapter interface requires implementing a read() method that yields conversation objects with this structure:
{
"email": "customer@example.com",
"subject": "Conversation subject",
"messages": [
{
"from": "user",
"type": "text",
"content": "Message body",
"date": "2024-01-15T10:30:00Z"
}
]
}A skeleton Podium adapter would read from pre-extracted Podium JSON files and yield conversations in this format.
Pros: Maintained by Crisp — handles conversation creation, message ordering, and participant assignment. Handles deduplication via message fingerprints.
Cons: No Podium adapter — you must build a transformer. Adapter stability is not guaranteed (Crisp's own warning: "Provided adapters may break anytime"). Does not handle contact profile creation — only conversations. Still requires Podium data extraction via API or FTP separately.
Scalability: Good for bulk conversation import once data is pre-transformed.
Complexity: Medium–High.
4. Middleware Platforms (Zapier, Make, n8n)
How it works: Use an integration platform to connect Podium triggers to Crisp actions. Zapier supports both Podium and Crisp as connectable apps.
When to use it: Ongoing sync of new contacts or conversations after the historical backfill. Not suitable for bulk historical migration.
Pros: No code for basic contact sync. Good for post-migration ongoing data flow.
Cons: Cannot bulk-migrate historical data — operates on triggers (new records only). Per-task pricing adds up quickly at scale (Zapier charges $0.01–$0.03 per task depending on plan). No conversation history migration. Limited field mapping flexibility. Zapier rate limits compound with Podium and Crisp API limits.
Scalability: Only viable for ongoing sync, not historical data.
Complexity: Low (for ongoing sync). Not viable for historical migration.
5. Podium FTP Raw Data Export + Custom Pipeline
How it works: Podium offers raw data access via FTP for Account Owners through their Data Share marketplace integration. This provides flat-file access to tables including contacts, conversations, conversation items, review invites, and feedback interactions. You extract these files, transform them, and load into Crisp via API.
When to use it: Large datasets where API-based extraction would take too long due to rate limits, or when you need access to data fields not exposed via the REST API.
Pros: Access to raw data tables with full field coverage. Bypasses API rate limits for extraction. Includes fields like conversation_originated_from and conversation_first_webchat_url not easily available via API.
Cons: Requires Account Owner to request FTP access from Podium. Data arrives as flat files — you need to reconstruct relationships (contact ↔ conversation ↔ messages) using UID foreign keys. Loading into Crisp still requires the Crisp API (no bulk import endpoint for conversations). FTP data may have delivery delays of 24–48 hours.
Scalability: Best for large datasets (100K+ conversations).
Complexity: High.
Migration Approach Comparison
| Method | Contacts | Conversations | Messages | Attachments | Complexity | Best For |
|---|---|---|---|---|---|---|
| CSV Export/Import | ✅ | ❌ | ❌ | ❌ | Low | Small contact-only migrations |
| API-Based ETL | ✅ | ✅ | ✅ | ✅ | High | Full-fidelity migration at any scale |
| Crisp Import Tool | ❌ (contacts separate) | ✅ | ✅ | ✅ | Medium–High | Bulk conversation import with custom adapter |
| Middleware (Zapier/Make) | ✅ (new only) | ❌ | ❌ | ❌ | Low | Ongoing sync post-migration |
| FTP Raw Export + Pipeline | ✅ | ✅ | ✅ | Partial | High | Enterprise-scale extraction |
Recommendations:
- Small business, contacts only: CSV export/import. Done in an afternoon.
- Small business, full history: API-based ETL, optionally paired with Crisp's import tool.
- Enterprise, one-time migration: FTP extraction + API-based ETL pipeline, or a managed migration service.
- Ongoing sync after migration: Zapier or Make for new contact/conversation flow.
Pre-Migration Planning
Data Audit
Before writing any migration code, audit your Podium data:
- Contacts: Total count, completeness (how many have email vs. phone only? In typical Podium accounts, 30–60% of contacts are phone-only)
- Conversations: Total count, average messages per conversation, date range
- Locations: Number of Podium locations, mapping strategy to Crisp
- Custom Fields: Which of the 8 possible custom fields are in use?
- Tags: Full list of tags, mapping to Crisp segments
- Reviews: Volume, whether to archive or discard
- Payments: Volume, whether to archive or export separately
- Attachments: File types and sizes in conversations (Crisp enforces a ~10 MB upload limit)
- Automations: Any Podium automations that need rebuilding in Crisp
- Team Members: Full list of Podium agents, roles, and location assignments — needed to pre-provision Crisp operators
Identify Data to Leave Behind
Not everything should migrate:
- Stale contacts with no conversation in 12+ months
- Test conversations from internal testing
- Duplicate contacts (Podium allows phone-only and email-only records for the same person)
- Review invites and review metadata (no Crisp target)
- Payment transaction records (archive to your accounting system)
Define a cutoff date if historical depth isn't critical — "only migrate conversations from the last 24 months" is a common boundary.
Migration Strategy
| Strategy | Description | Risk | Best For |
|---|---|---|---|
| Big bang | Migrate everything in a single cutover window | Higher risk, shorter timeline | Small datasets (<10K conversations) |
| Phased | Migrate contacts first, then conversations by date range | Lower risk, longer timeline | Mid-size datasets |
| Incremental | Migrate in batches, validate each batch, delta sync at cutover | Lowest risk, longest timeline | Enterprise datasets, risk-averse teams |
For most Podium-to-Crisp migrations, a phased approach works best: contacts first (via CSV or API), then conversations in reverse-chronological batches. Podium's contacts endpoint supports updated_at filtering, which enables clean delta syncs for the final cutover. (docs.podium.com)
Field-Level Data Mapping
This is where migrations succeed or fail. Mapping the data correctly is the most critical phase.
Contact → People Profile
| Podium Field | Crisp Field | Transform |
|---|---|---|
name |
person.nickname |
Direct mapping (split first/last if needed) |
email |
email |
Direct mapping (dedupe key in Crisp) |
phone |
phone |
Direct mapping |
address |
person.geolocation (partial) |
Parse into city/country/region or store as custom data |
contactStatus |
segments [] |
Map status values to segment names |
tags [] |
segments [] |
Append to segments array |
customFields.* |
data.* |
Map each custom field to a Crisp custom data key |
createdAt |
N/A (Crisp auto-generates) | Store as podium_created_at custom data if needed |
locationUid |
data.podium_location |
Store as custom data key |
uid |
data.podium_contact_uid |
Store for audit trail and replay safety |
Conversation → Conversation
| Podium Field | Crisp Field | Transform |
|---|---|---|
conversation.uid |
session_id (auto-generated) |
Store Podium UID as data.podium_conversation_uid for cross-reference |
conversation.channel.type |
origin |
Map: phone → chat, email → email, webchat → chat |
conversation.startedAt |
Conversation created timestamp | Set via API |
conversation.assignedUser |
Conversation routing/assignment | Map to Crisp operator if agent exists; requires operator to be pre-provisioned in workspace |
closed |
Conversation state | Map to resolved or unresolved |
Message → Message
| Podium Field | Crisp Field | Transform |
|---|---|---|
| Message body (text) | content (type: text) |
Direct |
| Message sender (business) | from: "operator" |
Map Podium users to Crisp operators |
| Message sender (customer) | from: "user" |
Direct |
| Internal note | type: "note" |
Requires paid Crisp plan; dropped silently on Free |
| Attachment (file) | type: "file", content: { url, name, type } |
Re-host files before Podium URLs expire |
createdAt |
timestamp |
Pass original timestamp as Unix epoch (milliseconds); verify exact field name against current Crisp API docs |
Crisp custom data keys must be lowercase with no special characters except underscores and dashes. Transform any Podium custom field names accordingly before import. Example: Preferred Contact Method → preferred_contact_method.
Migration Architecture
The data flow follows a standard ETL pattern with platform-specific constraints on both sides.
Extract (Podium)
Two extraction paths:
Path A — API extraction:
- Authenticate via OAuth 2.0 at
https://api.podium.com/v4/ - Paginate through
GET /contactsto extract all contacts (cursor-based pagination, limit up to 100) - Paginate through
GET /conversationsto extract conversations with messages - Respect the 60 requests/minute rate limit — implement a token bucket or fixed-delay pacer
- The daily quota is undocumented in exact numbers; monitor cumulative call counts and watch for
429responses with longerRetry-Aftervalues (indicating envelope exhaustion vs. per-minute throttle) - Download attachments immediately during extraction — Podium attachment URLs expire after seven days (docs.podium.com)
Path B — FTP raw data export:
- Account Owner requests FTP access from Podium via the Data Share marketplace integration
- Download flat-file tables: contacts, conversations, conversation items
- Reconstruct relationships using UID foreign keys
- FTP data refreshes may lag by 24–48 hours; plan for a final API-based delta sync at cutover
Transform
- Normalize Podium contacts to Crisp People Profile schema
- Convert tags to segment strings
- Transform custom fields to lowercase key-value pairs
- Convert Podium messages to Crisp message format (text/note/file)
- Handle phone-only contacts (Crisp uses email as the primary identifier — contacts without email need special handling; see Edge Cases below)
- Deduplicate contacts on email
- Map channel types to Crisp
originvalues - Preserve source IDs for audit trail
- Convert timestamps to Unix epoch milliseconds (Crisp's expected format)
Load (Crisp)
- Create People Profiles via
POST /v1/website/{website_id}/people/profile - Update custom data via
PATCH /v1/website/{website_id}/people/data/{people_id} - Create conversations via the Crisp REST API
- Send messages into conversations with proper
from,type,origin, and timestamp fields - Crisp plugin tokens use a daily quota system — request higher quotas before migration if needed
- Conversations are limited to approximately 10,000 messages each
Sample Crisp API response from add_new_people_profile:
{
"error": false,
"reason": "added",
"data": {
"people_id": "cf75e3c9-43f0-4f5a-b921-8a78a205c025"
}
}Sample Crisp API response from send_message_in_conversation:
{
"error": false,
"reason": "dispatched",
"data": {
"fingerprint": 163048963200000
}
}The fingerprint field is used for deduplication — if you send the same message twice with the same fingerprint, Crisp ignores the duplicate.
Contact Crisp support before starting a bulk conversation import. They can block outgoing email notifications from imported conversations so your customers don't receive messages for historical data. Also note: creating a conversation alone does not make it visible in the Crisp Inbox until a message is sent with from value user. Seed each session with the earliest inbound user message when possible.
Step-by-Step Migration Process
Step 1: Set Up API Access
Podium:
- Apply for a developer account at
developer.podium.com - Wait for approval (typically 2–5 business days)
- Create an OAuth application with required scopes (contacts, conversations, messages)
- Complete OAuth flow to obtain access token
Crisp:
- Create a plugin on the Crisp Marketplace or generate a Website token from your workspace settings
- For migration volumes, request a production plugin token with higher daily quotas (default is ~5,000 requests/day; request 50,000+ for migration)
- Add your workspace as a trusted website
Operator provisioning:
Before importing conversations with agent assignments, ensure all Podium team members who handled conversations are provisioned as Crisp operators. Crisp operators can be invited via the dashboard (Settings → Operators) or via POST /v1/website/{website_id}/operators. Map Podium assignedUser UIDs to Crisp operator email addresses in a lookup table.
Step 2: Extract Contacts from Podium
import requests
import time
import json
PODIUM_BASE = "https://api.podium.com/v4"
ACCESS_TOKEN = "YOUR_PODIUM_ACCESS_TOKEN"
RATE_LIMIT_DELAY = 1.1 # Stay under 60 req/min
def extract_podium_contacts():
contacts = []
cursor = None
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
response = requests.get(
f"{PODIUM_BASE}/contacts",
headers={
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json"
},
params=params
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Retry-After: {retry_after}s")
time.sleep(retry_after)
continue
response.raise_for_status()
data = response.json()
contacts.extend(data.get("data", []))
cursor = data.get("metadata", {}).get("cursor")
if not cursor:
break
time.sleep(RATE_LIMIT_DELAY)
return contactsStep 3: Transform Contacts to Crisp Schema
def transform_contact(podium_contact):
segments = []
# Map tags to segments
for tag in podium_contact.get("tags", []):
segments.append(tag.get("name", "").lower().replace(" ", "-"))
# Map contact status to segment
status = podium_contact.get("contactStatus", "")
if status:
segments.append(f"status-{status.lower()}")
# Build custom data from Podium custom fields
custom_data = {}
for attr in podium_contact.get("attributes", []):
key = attr.get("label", "").lower().replace(" ", "_")
key = ''.join(c for c in key if c.isalnum() or c in ('_', '-'))
custom_data[key] = attr.get("value", "")
# Preserve Podium metadata for audit trail
custom_data["podium_uid"] = podium_contact.get("uid", "")
custom_data["podium_location"] = podium_contact.get("locationUid", "")
custom_data["podium_created_at"] = podium_contact.get("createdAt", "")
# Handle phone-only contacts
email = podium_contact.get("email")
phone = podium_contact.get("phone", "")
if not email and phone:
# Generate deterministic placeholder email for phone-only contacts
clean_phone = ''.join(c for c in phone if c.isdigit())
email = f"phone_{clean_phone}@podium-import.local"
custom_data["placeholder_email"] = "true"
custom_data["original_phone"] = phone
return {
"email": email,
"phone": phone,
"person": {
"nickname": podium_contact.get("name", "")
},
"segments": segments,
"data": custom_data
}Step 4: Load Contacts into Crisp
from crisp_api import Crisp
client = Crisp()
client.set_tier("plugin")
client.authenticate("YOUR_IDENTIFIER", "YOUR_KEY")
WEBSITE_ID = "YOUR_CRISP_WEBSITE_ID"
def load_contact(crisp_contact):
try:
# Create or update profile (email is dedupe key)
result = client.website.add_new_people_profile(
WEBSITE_ID,
crisp_contact
)
# Response: {"error": false, "reason": "added", "data": {"people_id": "..."}}
people_id = result.get("data", {}).get("people_id")
# Update custom data separately
if crisp_contact.get("data") and people_id:
client.website.update_people_data(
WEBSITE_ID,
people_id,
crisp_contact["data"]
)
return people_id
except Exception as e:
log_error(f"Failed to load contact {crisp_contact.get('email')}: {e}")
return NoneStep 5: Extract and Load Conversations
Conversation migration is the hardest part. For each Podium conversation, you need to:
- Create a new Crisp conversation
- Associate it with the correct People Profile
- Send each message in chronological order with correct sender attribution
- Mark the conversation with the appropriate state (resolved/pending)
def migrate_conversation(podium_conversation, contact_people_id):
# Create conversation in Crisp
conversation = client.website.create_new_conversation(
WEBSITE_ID,
{"from": "operator"}
)
session_id = conversation.get("session_id")
# Set conversation metadata
client.website.update_conversation_metas(
WEBSITE_ID,
session_id,
{
"nickname": podium_conversation.get("contact", {}).get("name"),
"email": podium_conversation.get("contact", {}).get("email"),
"data": {
"podium_conversation_uid": podium_conversation.get("uid"),
"podium_channel": podium_conversation.get("channel", {}).get("type")
}
}
)
messages = podium_conversation.get("messages", [])
# Sort messages chronologically
messages.sort(key=lambda m: m.get("createdAt", ""))
# Send each message in chronological order
for message in messages:
msg_type = "text"
sender = "user" if message.get("direction") == "inbound" else "operator"
# Handle internal notes
if message.get("isInternal"):
msg_type = "note"
sender = "operator"
# Handle attachments
if message.get("attachments"):
for attachment in message["attachments"]:
client.website.send_message_in_conversation(
WEBSITE_ID,
session_id,
{
"type": "file",
"content": {
"url": attachment.get("rehosted_url"), # Must be pre-rehosted
"name": attachment.get("filename", "attachment"),
"type": attachment.get("contentType", "application/octet-stream")
},
"from": sender,
"origin": "chat",
"timestamp": message.get("createdAt")
}
)
time.sleep(0.2)
if message.get("body"):
client.website.send_message_in_conversation(
WEBSITE_ID,
session_id,
{
"type": msg_type,
"content": message.get("body", ""),
"from": sender,
"origin": "chat",
"timestamp": message.get("createdAt")
}
)
time.sleep(0.2) # Respect rate limits
# Mark conversation as resolved if closed in Podium
if podium_conversation.get("closed"):
client.website.change_conversation_state(
WEBSITE_ID,
session_id,
"resolved"
)
return session_idTimestamp preservation is critical. When loading historical messages into Crisp, you must explicitly pass the original timestamp in the API payload. If omitted, Crisp will stamp every imported message with the current date and time, destroying your conversation timeline. Timestamps should be in Unix epoch milliseconds format. Verify the exact field name and expected format against the current Crisp API documentation.
Step 6: Handle Attachments
Download attachments from Podium's hosted URLs during extraction — not later. Podium attachment URLs expire after seven days, so even a small planning delay can break a naive attachment migration. (docs.podium.com)
import hashlib
def download_and_rehost_attachment(podium_url, s3_bucket):
"""Download from Podium, upload to S3, return new URL."""
response = requests.get(podium_url, stream=True)
response.raise_for_status()
content = response.content
file_hash = hashlib.sha256(content).hexdigest()
filename = f"podium_attachments/{file_hash}"
# Upload to S3 (or GCS, or any CDN)
s3_client.put_object(Bucket=s3_bucket, Key=filename, Body=content)
return f"https://{s3_bucket}.s3.amazonaws.com/{filename}"Upload files to Crisp's storage or a third-party bucket (S3, GCS), and reference the new URL in the Crisp message payload. Crisp has a default 10 MB limit for file uploads and restricts allowed file extensions. Files exceeding these limits will trigger errors — host them externally with a direct link instead.
Step 7: Validate
After loading, run validation checks against both platforms. See the Validation and Testing section below.
For error handling across all steps, maintain checkpoint tables for contact IDs, conversation IDs, message fingerprints, file hashes, and retry state. On 429, 420, and 5xx responses, back off exponentially and resume from checkpoints — not from the beginning.
Sample Crisp error responses to handle:
// 429 - Rate limited (per-minute, website token)
{"error": true, "reason": "rate_limited", "data": {}}
// 420 - Rate limited (daily quota, plugin token)
{"error": true, "reason": "policy_rejected", "data": {}}
// 404 - Resource not found
{"error": true, "reason": "not_found", "data": {}}When you receive a 420 (daily quota exhausted), the only option is to wait until the quota resets (midnight UTC). Build this into your pipeline scheduler.
Edge Cases and Challenges
Phone-Only Contacts
Podium is SMS-first. Many contacts have a phone number but no email address — in typical Podium accounts, 30–60% of contacts may be phone-only. Crisp uses email as the primary identifier for People Profiles. Contacts without email can still exist in Crisp, but they won't merge with future chat sessions unless email is later added. Decision point: assign a placeholder email (e.g., phone_5551234567@podium-import.local) or accept that these contacts will be orphaned from future Crisp sessions. If using placeholder emails, mark them with a placeholder_email: "true" custom data flag so agents can identify and clean them later.
Multi-Location Mapping
Podium organizations often have multiple Locations. Crisp has no native location hierarchy — each workspace is a flat entity. Options:
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| One Crisp workspace per location | Clean separation, independent data | Agents switch workspaces; no cross-location view | 5+ locations, semi-independent brands |
| Single workspace + segments | Unified view, simple setup | Requires discipline in segment naming | 1–4 locations, shared team |
| Single workspace + custom data | Flexible filtering, queryable | Not visible in default UI filters | Analytics-heavy teams |
For multi-workspace migrations, run parallel import pipelines with separate checkpoint tables per workspace. Use a manifest file mapping location_uid → workspace_id to route contacts and conversations to the correct destination.
Conversation Message Limits
Crisp limits conversations to approximately 10,000 messages. Podium conversations, especially long-running SMS threads, can exceed this. For conversations over 10,000 messages, split into multiple Crisp conversations (linked by a shared custom data key like podium_thread_group) or truncate to the most recent N messages.
Duplicate Contacts
Podium often creates duplicate contacts if a user texts from a new number, or has separate records for the same person across different locations. Crisp deduplicates on email — importing the same email twice updates the existing profile rather than creating a duplicate. Deduplicate during the transform phase by matching email addresses. Decide which location's data takes precedence. For contacts with the same email but different location UIDs, merge custom data and append all location tags to segments.
Attachment Re-Hosting
File attachments in Podium messages reference Podium-hosted URLs. Once your Podium account is deactivated, these URLs will break. Download all attachments during extraction, re-host them (S3, GCS, or Crisp's own bucket), and update the references in the Crisp message payload. Store the original Podium URL alongside the new URL in your checkpoint table for audit purposes.
Internal Notes
Podium internal notes (via @mentions) need to be converted to Crisp note-type messages (type: "note"). Notes are not available on Crisp's Free plan. If you're importing into a Free workspace, notes will be silently dropped — no error is returned.
Multi-Channel Origin Mapping
Podium groups SMS, Webchat, and Facebook messages. In Crisp, you must define the origin field for each message so the UI renders the correct channel icon. Mapping:
| Podium Channel | Crisp origin Value |
|---|---|
phone (SMS) |
chat |
webchat |
chat |
email |
email |
facebook |
chat (or omit — no native Facebook origin) |
Outbound-Only Conversations
Some Podium conversations are outbound-only (business sent messages, customer never replied). Crisp conversations don't appear in the Inbox until a message is sent with from value user. Options:
- Prepend a synthetic user message (e.g., " [Imported from Podium — no customer reply on record]") to make the conversation visible.
- Archive these threads as notes on the contact profile instead of standalone conversations.
- Accept that they remain invisible in the Inbox but queryable via API.
Crisp Webhook Configuration for Ongoing Sync
After migration, set up Crisp webhooks to capture real-time events for downstream systems. Configure via Settings → Webhooks or POST /v1/website/{website_id}/hooks. Key events to subscribe to:
message:send— new messages in conversationspeople:profile:created— new contact profilesconversation:resolved— conversation state changes
This replaces Podium's webhook infrastructure and ensures your CRM, analytics, or other downstream systems continue receiving event data post-migration.
API Constraints and Rate Limits
Podium API
| Constraint | Value |
|---|---|
| Base URL | https://api.podium.com/v4/ |
| Auth | OAuth 2.0 (Authorization Code) |
| Rate limit (per minute) | 60 requests per OAuth app |
| Daily envelope | Undocumented exact value; silent until breached. Manifests as 429 with unusually long Retry-After (hours, not seconds) |
| Pagination | Cursor-based, limit up to 100 per page |
| Developer account | Requires application + approval (2–5 business days) |
| Attachment URL expiry | 7 days |
| No official SDK | Raw REST calls only; community wrappers exist but are not maintained |
Crisp API
| Constraint | Value |
|---|---|
| Base URL | https://api.crisp.chat/v1/ |
| Auth | Website Token or Plugin Token (Basic auth over HTTPS) |
| Rate limit (Website token) | Multi-level: global + per-route; ~10,000 requests/day |
| Rate limit (Plugin token) | Daily quota system (no per-minute limit); ~5,000/day default, higher on request |
| Quota reset | Midnight UTC |
| Conversation message limit | ~10,000 messages per conversation |
| Contact capacity | 200,000 contacts per workspace |
| Error on rate limit | HTTP 429 (per-minute) or 420 (daily quota) |
| File upload limit | ~10 MB default; restricted file extensions |
| Official SDKs | Python (crisp-api), Node.js, Ruby, Go |
Plugin tokens are not subject to per-minute rate limits — they use a daily quota instead, which is better suited for migration workloads. Request higher quotas from Crisp before starting your import. For a migration of 50K conversations, request a quota of at least 50,000 requests/day to complete the import within a reasonable window.
Limitations and Constraints
Be explicit about what you will lose or compromise:
- No review data migration. Crisp has no review management feature. All Google/Facebook review data, review invites, and review responses must be archived externally (CSV export or database dump).
- No payment data migration. Podium Payments (text-to-pay) has no Crisp equivalent. Export payment records to your accounting system before decommissioning.
- No campaign content migration. Podium's text marketing campaigns cannot be imported into Crisp's campaign feature. Rebuild them manually — this includes recipient lists, message templates, and scheduling rules.
- Simplified contact model. Podium supports up to 8 typed custom fields with picklist validation. Crisp's custom data is key-value pairs with no type enforcement — you keep the values but lose field-level validation and picklist constraints.
- Location hierarchy flattened. Multi-location structures must be collapsed into segments or custom data keys. Cross-location reporting requires external BI tooling.
- Automation rebuilds required. Podium automations and workflows must be manually recreated as Crisp bot scenarios or triggers. No export/import format exists for automation logic between the two platforms.
- No pipeline or deal objects. Crisp does not publish first-class opportunity or deal endpoints. If your team uses Podium contact status or custom fields as a pipeline, that data stays in an external CRM or gets downgraded to segments or custom data in Crisp.
Validation and Testing
Record Count Validation
After migration, compare counts:
| Object | Podium Count | Crisp Count | Delta | Acceptable? |
|---|---|---|---|---|
| Contacts | Query Podium API | Crisp Contacts page or GET /v1/website/{id}/people/profiles |
Should be 0 or explained (e.g., deduplicated) | ✅/❌ |
| Conversations | Query Podium API | Crisp API GET /v1/website/{id}/conversations (paginate to count) |
May differ due to splits/merges/filters | ✅/❌ |
| Messages | Sum across conversations | Sum across conversations | Within 1% | ✅/❌ |
| Segments | Count Podium tags | Crisp Contacts → Segments filter | Should match tag count | ✅/❌ |
Field-Level Validation
Sample 50–100 contacts at random and verify:
- Name, email, phone match source
- Custom data keys populated correctly (check for key name transformation errors)
- Segments applied correctly (no missing tags, no extra segments)
- Conversation messages in correct chronological order
- Sender identities (Agent vs. Customer) render correctly in Crisp UI
- Attachments accessible (URLs resolve, files download correctly)
- Internal notes visible (if on paid plan) or logged as skipped (if on Free plan)
- Placeholder email contacts flagged correctly with
placeholder_emailcustom data
UAT Process
- Migrate a test batch (100 contacts, 50 conversations) into a separate Crisp test workspace
- Have agents verify conversation readability and search functionality
- Test Crisp bot scenarios and triggers with migrated data
- Validate campaign targeting using imported segments
- Test routing, assignment, shortcuts, and conversation state workflows
- Verify that Crisp's chatbox widget correctly identifies returning visitors who match imported profiles
- Approve production migration only after test pass
Rollback Plan
Crisp does not offer a native "undo import" function. Your rollback options:
- Delete imported conversations via API (
DELETE /v1/website/{website_id}/conversation/{session_id}) — must be done per-conversation - Delete imported contacts via API (
DELETE /v1/website/{website_id}/people/profile/{people_id}) - Start with a fresh workspace if contamination is too severe — create a new Crisp website and re-import
Always keep your Podium account active until migration is validated and signed off. Budget at least 30 days of overlap.
Post-Migration Tasks
Once the data is verified in Crisp, complete your operational cutover:
- Rebuild automations. Re-create Podium automations as Crisp bot scenarios, chatbot flows, or triggers. Document the mapping between old Podium rules and new Crisp triggers.
- Configure integrations. Reconnect any third-party tools (Shopify, CRMs, analytics) to Crisp. Point all webhooks away from Podium. Set up Crisp webhooks for any downstream systems that relied on Podium events.
- Update website widgets. Replace Podium's webchat widget with Crisp's chatbox SDK. The Crisp chatbox requires adding a
<script>tag with yourCRISP_WEBSITE_IDto your site — remove the Podium widget script simultaneously to avoid conflicts. - Train agents. Crisp's shared inbox, shortcuts, and assignment model differ from Podium's location-based views. Ensure agents understand how to filter by segments and tags. Budget 2–4 hours for team onboarding on Crisp's interface.
- Monitor for 30 days. Watch for missing conversations, broken attachments, segment mismatches, and placeholder email contacts that need cleanup.
- Decommission Podium only after validation. Keep Podium active for at least 30 days post-migration as a reference and fallback.
For a detailed post-migration QA process, see Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration.
When to Use a Managed Migration Service
Build in-house when:
- You have available engineering capacity (1–2 developers for 2–4 weeks)
- Your dataset is small (<5K conversations)
- Your contacts are predominantly email-based (low phone-only percentage)
- You're comfortable managing API rate limits, error handling, and data validation yourself
Use a managed service when:
- You don't have engineering bandwidth to spare
- Your dataset is large or complex (multi-location, custom fields, 50K+ conversations)
- You need guaranteed timelines — your Podium contract is ending
- Data integrity is non-negotiable (regulated industry, audit requirements)
- Support must stay live during cutover with zero downtime
- The same team implementing Crisp would also be building the migration pipeline
- You've attempted a DIY migration and hit edge cases you can't resolve
The hidden cost of DIY migration is rarely the initial script — it's the debugging. Phone-only contacts, multi-location deduplication, attachment re-hosting, rate limit cascades, and malformed timestamps will consume 3–5x more engineering time than the initial extraction and loading code. For a 50K-conversation migration, the initial ETL script typically takes 40 hours; edge case handling, validation, and re-runs add another 80–120 hours.
ClonePartner specializes in exactly this type of cross-platform migration — handling the extraction, transformation, relationship mapping, and validation so your team can focus on configuring Crisp rather than debugging import scripts. If your Podium-to-Crisp migration involves more than contacts, we should talk.
Best Practices
- Back up everything before migration. Export Podium contacts via CSV, request FTP raw data, and store a snapshot of all conversation data. Do this before revoking any access.
- Run test migrations first. Use a separate Crisp test workspace. Never test against production.
- Contact Crisp support before importing conversations. They can block outgoing email notifications from imported conversations. Email
support@crisp.chator use the in-app messenger with your workspace ID and planned import date. - Validate incrementally. Don't migrate 100K records and then check. Log the first 50 API responses and validate immediately. Check after every batch of 500–1,000 records.
- Map agents before migration. Ensure all Podium users who need operator access in Crisp are provisioned before conversation import. Build a
podium_user_uid → crisp_operator_emaillookup table. - Preserve Podium UIDs. Store original Podium contact and conversation UIDs as Crisp custom data for cross-reference and audit trail. This creates a permanent reference if you ever need to trace back to the source system.
- Handle rate limits by construction, not reaction. Build fixed-delay pacers into your scripts rather than relying solely on retry logic after 429 errors. For plugin tokens, track daily request counts locally and pause before hitting the quota ceiling.
- Download attachments during extraction. Podium attachment URLs expire after seven days. Do not rely on later access. Verify downloads with SHA-256 checksums.
- Use middleware only for delta sync. Zapier and Make are useful glue for ongoing sync after the backfill, not migration engines for historical data.
- Plan for phone-only contacts explicitly. Decide on your placeholder email strategy before starting the migration — changing the approach mid-migration creates inconsistent data.
Making the Right Choice
A Podium-to-Crisp migration is a data model transformation, not a copy-paste. The platforms were built for different audiences with different architectural assumptions. The real engineering work is in the transformation layer — mapping Podium's location-based, SMS-first model to Crisp's website-based, chat-first model.
For contacts-only migrations, CSV export/import is fast and free. For anything involving conversation history, you're looking at a custom API pipeline or a managed migration service. There is no shortcut, and there is no off-the-shelf tool that handles this path end-to-end.
Treat this as an engineering project, not an administrative task. Audit your data, map your objects deliberately, and build robust error handling into your ETL pipeline. Poorly mapped timestamps, unhandled rate limits, and dropped attachments will disrupt your support operations post-cutover.
For a broader look at migration planning, see Help Desk Data Migration Checklist and Best Practices for Help Desk Data Migration.
Frequently Asked Questions
- Can I migrate conversation history from Podium to Crisp?
- Yes, but only via the Crisp REST API or Crisp's open-source conversation import tool (crisp-import-conversations on GitHub). There is no CSV import for conversations in Crisp, and no native Podium adapter exists — you need a custom ETL pipeline to extract from Podium's API, transform the data, and load it into Crisp.
- Does Crisp have a built-in Podium migration tool?
- No. Crisp's official conversation import tool supports adapters for Zendesk, Gorgias, Help Scout, Tidio, GrooveHQ, and WHMCS — but not Podium. You would need to write a custom adapter or transform Podium data into Crisp's expected JSON schema.
- What Podium data cannot be migrated to Crisp?
- Reviews (Google/Facebook), Payments (text-to-pay), and Campaign content have no Crisp equivalent. These must be archived externally before decommissioning Podium. Automations and workflows also need to be manually rebuilt in Crisp.
- How long does a Podium to Crisp migration take?
- A contacts-only CSV migration takes a few hours. A full migration with conversation history typically takes 2–4 weeks of engineering effort, depending on data volume and complexity. Budget 80–160 engineer-hours for a mid-size dataset (10K–50K conversations) using a custom API pipeline.
- How do I handle phone-only Podium contacts in Crisp?
- Crisp uses email as the primary identifier for People Profiles. Phone-only contacts can exist in Crisp but won't merge with future chat sessions unless email is later added. You can assign a placeholder email or accept that these contacts will be orphaned from future sessions. Crisp's CSV import requires email, so phone-only records must go through the API.


