Skip to content

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.

Abdul Abdul · · 27 min read
Podium to Crisp 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

Last verified: July 2025. Tested against Podium API v4 and Crisp REST API V1.

Info

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:

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
Warning

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: phonechat, emailemail, webchatchat
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
Tip

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 Methodpreferred_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 /contacts to extract all contacts (cursor-based pagination, limit up to 100)
  • Paginate through GET /conversations to 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 429 responses with longer Retry-After values (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 origin values
  • 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.

Warning

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:

  1. Apply for a developer account at developer.podium.com
  2. Wait for approval (typically 2–5 business days)
  3. Create an OAuth application with required scopes (contacts, conversations, messages)
  4. Complete OAuth flow to obtain access token

Crisp:

  1. Create a plugin on the Crisp Marketplace or generate a Website token from your workspace settings
  2. For migration volumes, request a production plugin token with higher daily quotas (default is ~5,000 requests/day; request 50,000+ for migration)
  3. 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 contacts

Step 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 None

Step 5: Extract and Load Conversations

Conversation migration is the hardest part. For each Podium conversation, you need to:

  1. Create a new Crisp conversation
  2. Associate it with the correct People Profile
  3. Send each message in chronological order with correct sender attribution
  4. 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_id
Warning

Timestamp 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:

  1. Prepend a synthetic user message (e.g., " [Imported from Podium — no customer reply on record]") to make the conversation visible.
  2. Archive these threads as notes on the contact profile instead of standalone conversations.
  3. 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 conversations
  • people:profile:created — new contact profiles
  • conversation: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
Tip

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:

  1. 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).
  2. No payment data migration. Podium Payments (text-to-pay) has no Crisp equivalent. Export payment records to your accounting system before decommissioning.
  3. 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.
  4. 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.
  5. Location hierarchy flattened. Multi-location structures must be collapsed into segments or custom data keys. Cross-location reporting requires external BI tooling.
  6. 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.
  7. 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_email custom data

UAT Process

  1. Migrate a test batch (100 contacts, 50 conversations) into a separate Crisp test workspace
  2. Have agents verify conversation readability and search functionality
  3. Test Crisp bot scenarios and triggers with migrated data
  4. Validate campaign targeting using imported segments
  5. Test routing, assignment, shortcuts, and conversation state workflows
  6. Verify that Crisp's chatbox widget correctly identifies returning visitors who match imported profiles
  7. 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 your CRISP_WEBSITE_ID to 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.

Info

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

  1. 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.
  2. Run test migrations first. Use a separate Crisp test workspace. Never test against production.
  3. Contact Crisp support before importing conversations. They can block outgoing email notifications from imported conversations. Email support@crisp.chat or use the in-app messenger with your workspace ID and planned import date.
  4. 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.
  5. 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_email lookup table.
  6. 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.
  7. 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.
  8. Download attachments during extraction. Podium attachment URLs expire after seven days. Do not rely on later access. Verify downloads with SHA-256 checksums.
  9. Use middleware only for delta sync. Zapier and Make are useful glue for ongoing sync after the backfill, not migration engines for historical data.
  10. 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.

More from our Blog

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration
Help Desk

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration

Ensure your help desk migration is a success with this comprehensive 20-point post-migration QA checklist. This expert guide details the 20 essential tests needed to validate your data integrity, system functionality, user-friendliness, and performance . Learn exactly how to check everything from ticket data, attachments, and knowledge base articles to critical workflows, automations, and integrations before you go live. This process is your final line of defense against lost tickets, broken workflows, and unhappy customers.

Raaj Raaj · · 8 min read