Skip to content

Tidio to Groove Migration: A Technical Guide

Technical guide to migrating from Tidio to Groove. Covers API constraints, data model mapping, chat sessionization, rate limits, edge cases, and cutover planning.

Rishabh Rishabh · · 22 min read
Tidio to Groove 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

Tidio to Groove Migration: A Technical Guide

Migrating from Tidio to Groove means converting a conversation-and-contact-centric platform into a ticket-centric shared inbox. There is no native migration path, no vendor-provided import tool, and no push-button connector between these two systems. Groove's official migration flow points customers to Import2 or the API, and Tidio is not on Groove's published Import2 source list. Every record — contacts, operators, conversations, messages, tickets, tags, attachments — must be extracted from the Tidio OpenAPI, transformed to match Groove's data model, and loaded through Groove's API.

This is a schema translation project. Tidio stores data around the Contact object, with conversations and tickets as child entities. Groove stores everything around the Ticket (conversation) object with messages threaded inside it. Tidio's chat history is a continuous stream of messages, bot interactions, and system events. Groove expects discrete tickets with subjects, threaded replies, and clear state transitions.

This guide covers the full data model mapping, API constraints on both sides, extraction strategy, loading sequence, idempotency handling, and the specific edge cases that surface during this migration.

Info

Short answer: There is no documented native Tidio-to-Groove importer. Budget for an ETL project, not a CSV upload.

Why Teams Move from Tidio to Groove

Tidio is a customer service and conversational AI platform built for SMBs and e-commerce. It merges live chat, chatbots (Flows), AI agents (Lyro), and a Help Desk ticketing system into one dashboard. Channels include web chat widget, email, Facebook Messenger, Instagram, and WhatsApp. Pricing is conversation-based: Starter at $29/mo, Growth at $59/mo, Plus from $749/mo, and Premium from ~$2,999/mo. All self-serve plans cap at 10 operator seats.

Groove (GrooveHQ) is a shared inbox and helpdesk for small-to-mid-sized teams that prefer simplicity over enterprise complexity. It combines email, live chat, social channels, and a knowledge base into a unified agent workspace with collision detection, automations, SLA tracking, and reporting. Groove uses per-user pricing: Standard at ~$20/user/month, Plus at ~$45/user/month, and Pro at ~$70/user/month.

Common reasons for the switch:

  • Pricing predictability. Tidio's three-meter billing model (billable conversations, Lyro AI conversations, Flow triggers) makes costs hard to forecast. Groove's flat per-user pricing is simpler.
  • Ticket-centric workflow. Teams handling primarily email-based support find Groove's shared inbox model more natural than Tidio's chat-first interface.
  • Avoiding the $59-to-$749 cliff. Tidio jumps from Growth ($59/mo) directly to Plus ($749/mo) with no mid-tier option — a 12× price increase that pushes growing teams elsewhere.
  • API access costs. Tidio's full OpenAPI access requires the Plus plan ($749/mo minimum). Groove includes API access on all paid plans.

Data Model Mapping: Tidio → Groove

The foundational difference: Tidio organizes data around Contacts (visitors who've interacted with the widget), while Groove organizes data around Tickets (conversations in a shared inbox).

Tidio Object Groove Object API Surface Notes
Contacts Customers REST /v1/customers or GraphQL createContact Match on email; Groove auto-creates customers on ticket creation
Operators Agents Manual or API Agents must exist before ticket assignment
Departments Groups REST or GraphQL Tidio departments are operator teams; Groove separates team structure (groups) from routing channels (mailboxes)
Conversations (chat messages) Tickets + Messages REST /v1/tickets + /v1/tickets/{id}/messages or GraphQL Each Tidio conversation becomes one Groove ticket with threaded replies
Tickets (Help Desk) Tickets + Messages Same as above More direct mapping; preserve status and assignee
Tags Tags REST or GraphQL Tidio uses tag_ids; Groove expects tag names. Build a tag dictionary before loading.
Contact Properties (custom) Custom fields, tags, or notes Varies Groove's custom field support is limited; some properties may become tags or structured notes
Attachments Attachments REST uploader or GraphQL Must be downloaded from Tidio and re-uploaded to Groove
Viewed Pages Not supported Groove has no page-visit tracking; archive externally or drop
Flows / Bots Not supported Groove has basic automation rules, not visual flow builders
Lyro AI data Not supported No migration path for AI training data

A few mapping details that trip people up:

  • Status mapping is not 1:1. Tidio tickets support open, pending, and solved. Groove supports unread, opened, pending, closed, and spam. In most projects: solved → closed, pending → pending, open → opened. Consider setting the state to unread when the last historical message came from the customer.
  • Tags need translation, not passthrough. Tidio ticket APIs use tag_ids; Groove expects tag names when creating or updating tags on a ticket. Build a tag dictionary during extraction.
  • Custom fields may need a second pass. Tidio supports contact properties and ticket custom fields. Groove supports conversation custom fields in the product and GraphQL schema, but the published REST v1 ticket-create docs don't expose conversation custom-field parameters. The safer path: create the ticket first, then update conversation custom fields via Groove's GraphQL customFieldValuesUpdate mutation.
  • Tidio has two inboxes. Tidio's live chat inbox and Help Desk ticketing system are separate modules with different API endpoints. Chat messages come from /contacts/{contactId}/messages; tickets come from /tickets. Make sure your extraction covers both.
Warning

What you will lose: Tidio's Viewed Pages history, Flow/bot configurations, Lyro AI training data, pre-chat survey metadata, conversation ratings, and WhatsApp/Instagram/Messenger channel configs have no Groove equivalents. Archive these externally or deliberately drop them. Be explicit with stakeholders about what's lost before the project starts.

Tidio API: Extraction Constraints

The Tidio OpenAPI is your extraction path.

Authentication

Tidio uses a paired header approach. Every request requires two headers:

X-Tidio-Openapi-Client-Id: <your-client-id>
X-Tidio-Openapi-Client-Secret: <your-client-secret>

Credentials are generated in the Tidio Panel under Settings → Developer → OpenAPI.

Plan Gating

This is the most common blocker teams hit. Tidio's OpenAPI endpoints (outside the Product Recommendation area) are available only on the Plus plan ($749/mo) and above. Free, Starter ($29/mo), and Growth ($59/mo) tiers have no access to the endpoints needed for migration. If you're on a lower-tier plan, you'll need to upgrade — even temporarily — to extract your data via API.

Rate Limits

Tidio enforces rate limits per project:

  • Plus plan: 60 requests per minute
  • Premium plan: 120 requests per minute

The API returns X-RateLimit-Limit and X-RateLimit-Remaining headers on every response. Build your extraction script to read these headers and throttle proactively — don't just catch 429s reactively. Every response also includes a trace-id header, worth storing for support cases.

A Tidio 429 response looks like this:

{
  "status": 429,
  "message": "Too Many Requests",
  "retryAfter": 30
}

The Retry-After header accompanies the 429 and specifies the wait in seconds. Store the trace-id from every failed response — Tidio support requires it for rate-limit investigations.

At 60 req/min on Plus, a dataset with 10,000 contacts and 2,000 tickets needs at least 12,000 detail requests just for per-contact chat history and per-ticket message bodies — before retries, attachments, or delta sync. That's roughly 200 minutes of source-side API time at best.

Pagination

Tidio uses cursor-based pagination. The response includes a cursor value; pass it in the next request's cursor query parameter. Omit the parameter for the first page. There's no documented page-size override — you get whatever the API returns per page and iterate until no cursor is returned.

Delta Sync Filtering

Tidio's /tickets endpoint accepts created_from and created_to query parameters (ISO 8601 timestamps) for filtering by creation date. Use these for delta sync: record the timestamp when your initial extraction starts, then re-run with created_from={extraction_start_timestamp} to capture new records created during the migration window.

For chat messages via /contacts/{contactId}/messages, there is no server-side timestamp filter — you must pull all messages per contact and filter client-side by comparing against your recorded extraction timestamp. This means delta sync for chat history is O(contacts), not O(new messages). For large contact volumes, minimize the delta window to reduce redundant pulls.

Tip

CSV alternative for contacts: Tidio allows CSV export of contacts from the Contacts List UI (Settings → Customers). This export includes only the properties currently displayed in your contacts list. If you need a quick contacts-only dump and don't need conversation history, the CSV export can supplement the API approach — but it won't include messages.

Key Extraction Endpoints

Endpoint Method Purpose
/contacts GET List all contacts (paginated by cursor)
/contacts/{contactId} GET Single contact with properties
/contacts/{contactId}/messages GET All messages for a contact (chat history)
/contacts/{contactId}/viewed-pages GET Page visit history (no Groove equivalent)
/contact-properties GET Custom property definitions
/operators GET List all operators
/departments GET List departments
/tickets GET List all Help Desk tickets (without messages); supports created_from, created_to filters
/tickets/{ticketId} GET Single ticket with details and messages
/tickets/tags GET All ticket tags
/tickets/custom-fields GET Custom field definitions

Note that Tidio's ticket list endpoint returns tickets without messages — you need a second call per ticket to pull the full thread.

Groove API: Loading Constraints

Groove offers two APIs. Understanding the state of each matters for your migration.

REST v1 API

The REST API base URL is https://api.groovehq.com/v1. Authentication uses a Bearer access token generated from Settings → Developer → API. This API is marked as no longer in active development, but it remains functional and is the more battle-tested option for ticket imports.

Key capabilities for migration:

  • Backdated timestamps via sent_at on both tickets and messages
  • Private notes via note: true
  • skip_notifications: true to suppress agent email alerts during import
  • skip_unread_ticket: true to prevent historical replies from reopening queue state
  • Pagination at a maximum of 50 records per page

Groove's REST API returns an empty response body for most error codes. The exception is 422 Unprocessable Entity, which returns field-level JSON:

{
  "errors": {
    "body": ["can't be blank"],
    "mailbox": ["is not valid"]
  }
}

All other error codes (400, 401, 403, 404, 500) return empty bodies. This means your loader must log the full request payload alongside the HTTP status — there is no server-provided error message to log for most failures.

GraphQL v2 API

The GraphQL endpoint is https://api.groovehq.com/v2/graphql, also using a Bearer API key. Key mutations for migration:

# Create a conversation (ticket)
mutation CreateConversation($input: CreateConversationInput!) {
  createConversation(input: $input) {
    conversation {
      id
      number
    }
    errors {
      message
      path
    }
  }
}
 
# Add a reply to an existing conversation
mutation AddReply($input: AddReplyInput!) {
  addReply(input: $input) {
    message {
      id
    }
    errors {
      message
    }
  }
}
 
# Update conversation custom field values
mutation UpdateCustomFields($input: CustomFieldValuesUpdateInput!) {
  customFieldValuesUpdate(input: $input) {
    customFieldValues {
      id
      value
    }
    errors {
      message
    }
  }
}
 
# Create a contact
mutation CreateContact($input: CreateContactInput!) {
  createContact(input: $input) {
    contact {
      id
      email
    }
    errors {
      message
    }
  }
}

GraphQL errors surface in the errors array on the mutation response, not as HTTP error codes — your error handling must check both the HTTP status and the response body's errors field. A 200 response can contain a failed mutation.

The GraphQL API covers conversations, messages, agents, contacts, mailboxes, tags, and knowledge base articles. However, some areas are still being built, so verify endpoint coverage against current documentation before committing to GraphQL exclusively.

Rate Limits

Groove's API limits by plan: 200 calls/min on Standard, 400 calls/min on Plus, and 800 calls/min on Pro. The source platform (Tidio) is almost always the extraction bottleneck, not Groove.

Groove-Specific Constraints

  • No custom object system. Groove is a flat shared inbox. Structured data in Tidio custom properties must be flattened into tags, custom fields, or notes.
  • Ticket numbers are auto-generated. You cannot set or preserve Tidio ticket IDs. Store a mapping table (tidio_ticket_id → groove_ticket_number) for post-migration auditing.
  • Mailbox vs. inbox terminology. Groove's UI refers to "inboxes" but the REST API object is mailbox.
  • Groove does not expose an imported flag or conversation source field on ticket records. Use a dedicated import tag (e.g., tidio-import) on every migrated ticket to distinguish imported records from organic conversations in reporting and validation.
Danger

Set skip_notifications: true and keep customer email delivery off during historical replay. The fastest way to turn a clean migration into an incident is to email customers from years-old imported threads.

Idempotency: Handling Crashes and Reruns

This is the most operationally dangerous gap in DIY migrations. If your migration script crashes mid-run and you restart it without idempotency logic, you will create duplicate tickets in Groove. Groove's ticket-create endpoint has no native deduplication — it creates a new ticket on every POST.

The solution: a local state database.

Before loading any record, write its Tidio ID and status (pending) to a local SQLite or Redis store. After successful creation in Groove, update the record with the Groove ticket number and status (complete). On rerun, skip any record with status complete.

import sqlite3
 
def init_state_db(db_path="migration_state.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS ticket_map (
            tidio_id TEXT PRIMARY KEY,
            groove_ticket_number TEXT,
            status TEXT DEFAULT 'pending',
            error TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()
    return conn
 
def is_already_migrated(conn, tidio_id):
    row = conn.execute(
        "SELECT status FROM ticket_map WHERE tidio_id = ? AND status = 'complete'",
        (tidio_id,)
    ).fetchone()
    return row is not None
 
def record_success(conn, tidio_id, groove_ticket_number):
    conn.execute(
        "INSERT OR REPLACE INTO ticket_map (tidio_id, groove_ticket_number, status) VALUES (?, ?, 'complete')",
        (tidio_id, groove_ticket_number)
    )
    conn.commit()
 
def record_failure(conn, tidio_id, error_message):
    conn.execute(
        "INSERT OR REPLACE INTO ticket_map (tidio_id, groove_ticket_number, status, error) VALUES (?, NULL, 'failed', ?)",
        (tidio_id, error_message)
    )
    conn.commit()

Apply the same pattern for contacts and messages. For messages, the composite key is (tidio_ticket_id, message_index) or (tidio_ticket_id, message_timestamp, sender_id) — whatever uniquely identifies a message in the source data.

This state table also becomes your reconciliation artifact post-migration: every Tidio ID maps to a Groove ticket number, with a status and error log for failures.

Step-by-Step Migration Process

Step 1: Audit and Define Scope

Before extracting anything, decide what moves:

  • Must migrate: Contacts with email addresses, active/recent tickets, associated messages and replies, operator mappings, tags
  • Should migrate: Closed/archived tickets with historical value, custom contact properties
  • Cannot migrate: Flows, Lyro training data, Viewed Pages, pre-chat survey configs, widget customizations
  • Consider dropping: Bot-generated conversations with no human interaction, spam contacts, anonymous visitor sessions with no email

Reducing scope directly reduces migration time and cost. At 60 requests/minute, every unnecessary API call adds up.

Step 2: Extract from Tidio

Extract in dependency order:

  1. Operators (GET /operators) — You need operator IDs to map conversation assignments later.
  2. Departments (GET /departments) — Map these to Groove groups.
  3. Contact properties (GET /contact-properties) — Understand your custom field schema before extracting contacts.
  4. Ticket tags (GET /tickets/tags) and ticket custom fields (GET /tickets/custom-fields) — Capture field definitions.
  5. Contacts (GET /contacts, paginated) — For each contact, fetch messages (GET /contacts/{contactId}/messages) for chat history.
  6. Tickets (GET /tickets, paginated) — For each ticket, fetch details (GET /tickets/{ticketId}) for replies and metadata.

Store everything as JSON files on disk. Don't stream directly from Tidio to Groove — if anything fails mid-run, you want to resume from your local cache, not re-extract against those rate limits.

import requests
import time
import json
 
TIDIO_BASE = "https://api.tidio.com"
HEADERS = {
    "X-Tidio-Openapi-Client-Id": "YOUR_CLIENT_ID",
    "X-Tidio-Openapi-Client-Secret": "YOUR_CLIENT_SECRET"
}
 
def extract_paginated(endpoint, output_file):
    results = []
    cursor = None
    while True:
        params = {}
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(f"{TIDIO_BASE}{endpoint}", headers=HEADERS, params=params)
        if resp.status_code == 429:
            wait = int(resp.headers.get("Retry-After", 60))
            time.sleep(wait)
            continue
        resp.raise_for_status()
        data = resp.json()
        results.extend(data.get("data", []))
        cursor = data.get("cursor")
        if not cursor:
            break
        # Respect rate limits proactively
        remaining = int(resp.headers.get("x-ratelimit-remaining", 1))
        if remaining < 5:
            time.sleep(2)
    with open(output_file, "w") as f:
        json.dump(results, f)
    return results

Step 3: Create Target Structure in Groove

Before loading any conversations, set up Groove:

  1. Create agents — Every Tidio operator assigned to a ticket must exist as a Groove agent. Groove's own migration guide warns that users should be set up before tickets are imported, or tickets can collapse onto one user.
  2. Create mailboxes — Map Tidio channels to Groove mailboxes. One primary support channel typically means one Groove mailbox. Example REST call to verify mailboxes:
    GET https://api.groovehq.com/v1/mailboxes
    Authorization: Bearer {token}
    
    The response returns mailbox slugs (e.g., "slug": "support") — use the slug, not the display name, when assigning tickets.
  3. Create groups — Map Tidio departments to Groove groups.
  4. Create tags — Extract all tags from Tidio and pre-create them in Groove. This avoids tag-creation race conditions during parallel ticket loading.
  5. Set up custom fields — If you need conversation-level custom fields, create them before loading. Note their field IDs from the Groove GraphQL schema — you'll need those IDs for the customFieldValuesUpdate mutation.

For operators who have left your company and you don't want to consume a Groove seat license: map their historical Tidio messages to a generic "Legacy Agent" in Groove, or provision them temporarily, run the migration, and immediately suspend the account.

Step 4: Handle Anonymous Contacts

Tidio allows visitors to initiate chats without providing an email address. They're tracked by visitor_id or browser session. Groove's Customer object requires an email address (a common target constraint also detailed in our Tidio to Enchant migration guide) — you cannot create a customer with just a name or phone number.

Your options:

  1. Generate placeholder emails using the visitor ID (e.g., visitor_{id}@anonymous.local). This preserves the data in Groove but can pollute CRM, automations, and reporting if you don't isolate these records.
  2. Drop anonymous conversations if they have no historical value.
  3. Archive externally and keep them out of Groove entirely.
// Deterministic placeholder email for anonymous Tidio contacts
function formatGrooveCustomer(tidioContact) {
  const email = tidioContact.email || `visitor_${tidioContact.id}@anonymous.local`;
  return {
    email: email,
    name: tidioContact.name || "Anonymous Visitor",
    about: `Imported from Tidio. Original ID: ${tidioContact.id}`
  };
}

Contact deduplication before load

Tidio's create-contact behavior always adds a new contact and never overwrites on matching email or distinct_id. Years of chatbot activity frequently produce duplicate contacts for the same email address. Before loading into Groove, deduplicate your extracted contact set using the following approach:

  1. Normalize emails: lowercase, strip whitespace, resolve common aliasing (e.g., + suffixes if your domain uses them).
  2. Group by normalized email. For duplicate groups, select the contact with the most recent last_seen timestamp as canonical, and merge all conversation and ticket references to that canonical ID.
  3. For contacts without email: treat each visitor_id as a distinct identity — do not attempt to merge anonymous contacts.
  4. Log all merged IDs in your state database so the post-migration mapping table remains accurate.

Skipping this step causes Groove history to fragment across multiple customer records for the same person.

Step 5: Transform Chat Conversations into Tickets

This is where the paradigm shift hits hardest. Tidio chat history is a flat stream of messages per contact. Groove expects discrete tickets with subjects and threaded replies.

Sessionization: Reconstructing ticket boundaries from chat streams

For Tidio Help Desk tickets, the boundary already exists — each ticket is a discrete unit. For chat message history (retrieved via /contacts/{contactId}/messages), you need rules to split the stream into ticket-sized chunks. Common split points:

  • A solved/closed event
  • A long inactivity gap (e.g., 30+ minutes with no messages)
  • A department change
  • A first operator reply after a previously solved state

Keep the rule set simple and document it. Aggressive auto-merging makes validation nearly impossible. If a chat never had a subject, generate one deterministically from channel plus date plus contact name — not from a random message snippet.

Message grouping for readability

In Tidio, a user might send five separate messages in ten seconds:

  1. "Hi"
  2. "I have a problem"
  3. "With my order"
  4. "Order #12345"
  5. "It hasn't arrived"

If you migrate these as five separate replies on a Groove ticket, the UI becomes cluttered. Groove is designed for paragraph-length email replies, not rapid-fire chat bubbles. Two approaches:

  1. Group consecutive messages from the same sender within a time window (e.g., 5 minutes) into a single reply.
  2. Compile the entire transcript into a single HTML body for the Groove ticket. This is often the best approach for historical chat data — it preserves readability without generating hundreds of thousands of individual message records.
{
  "body": "<strong>Chat transcript imported from Tidio</strong><br><br><strong>User (10:01 AM):</strong> Hi<br><strong>User (10:01 AM):</strong> I have a problem with my order #12345<br><strong>Agent (10:05 AM):</strong> I can help with that. Let me check the status.",
  "from": { "email": "customer@example.com", "name": "Jane Doe" },
  "to": "support@yourcompany.com",
  "mailbox": "support",
  "subject": "Chat - 2024-04-09 - Jane Doe",
  "sent_at": "Tue, 09 Apr 2024 10:01:00 +0000",
  "state": "closed",
  "assignee": "agent@yourcompany.com",
  "tags": ["tidio-import"],
  "skip_notifications": true
}

Filtering bot-only conversations

If you used Tidio's Lyro AI or decision-tree bots, your data likely contains abandoned bot flows — "Welcome! How can I help you?" followed by the user leaving. Migrating these to Groove wastes API calls and pollutes your inbox. Filter out conversations with zero messages from a human operator and zero meaningful user input.

Preserving public vs. internal messages

Tidio ticket payloads distinguish message_type: public and message_type: internal. Groove uses normal messages vs note: true. Preserve this split or your agents will lose internal context.

Step 6: Re-host Attachments

Attachments cannot be passed as URL references. Tidio attachment URLs may require authentication or expire after your Tidio account is closed. You must physically move the files.

For each attachment:

  1. Download the file binary from the Tidio attachment URL.
  2. Check size — Groove limits attachments to 20 MB per file and 25 files per message. Files exceeding the limit should be uploaded to external storage (e.g., S3) with the link inserted into the message body instead.
  3. Upload to Groove's attachment endpoint.
  4. Associate the returned attachment ID with the specific message payload.

Log failures. If an attachment can't be transferred, annotate the Groove message body or add a private note documenting the missing file.

Step 7: Load into Groove

Use a queuing system (BullMQ for Node.js, Celery for Python) to manage API calls. This gives you automatic retries, concurrency control, and detailed logging. A simple for loop will not handle rate limiting, retries, or partial failures gracefully.

When creating historical tickets:

  • Set state to closed (or appropriate historical state) to prevent triggering Groove's auto-responders or SLA alerts
  • Set skip_notifications: true to suppress agent email alerts
  • Set skip_unread_ticket: true on historical replies to prevent them from reopening queue state
  • Keep send_copy_to_customer off unless you intentionally want customers emailed from imported history
  • Apply the tidio-import tag on every ticket so imported records are identifiable in reporting and validation queries
{
  "body": "Internal note migrated from Tidio",
  "author": "agent@yourcompany.com",
  "sent_at": "Tue, 09 Apr 2024 10:20:00 +0000",
  "note": true,
  "skip_notifications": true,
  "skip_unread_ticket": true
}

Logging requirements for every API call:

Store the following for each record attempt: Tidio source ID, Groove response HTTP status, Groove returned ticket number (on success), full request payload, full response body, Tidio trace-id header (extraction), and wall-clock timestamp. For Groove REST errors other than 422, the response body is empty — the request payload log is your only diagnostic artifact.

Step 8: Validate

After loading, run automated validation — not just manual spot checks. For migrations over 1,000 tickets, manual review alone is insufficient.

Automated checks:

  • Count verification: Compare total contacts from Tidio vs. customers in Groove. Compare total conversations + tickets vs. Groove conversations. Flag any delta greater than 1% for investigation.
  • Message counts: Spot-check 20–30 tickets. Compare message counts and ordering between source and target.
  • Assignment integrity: Verify tickets are assigned to the correct Groove agents.
  • Tag mapping: Confirm all tags transferred correctly. Query Groove's tag list and diff against Tidio's extracted tag set.
  • Attachments: Click through attachments in Groove to verify files aren't corrupted.
  • Timestamps: Verify the oldest and newest tickets have correct dates, not the import date.
  • Import tag coverage: Confirm 100% of imported tickets carry the tidio-import tag. Any ticket missing it may have been created organically during the migration window — investigate before assuming it's a valid import.

Edge case spot-checks — verify these specifically:

  • Long threads (50+ messages)
  • Internal notes (confirm note: true records appear as notes, not public replies)
  • Multi-attachment tickets
  • Anonymous visitor records (confirm placeholder emails are consistent)
  • Records that changed departments mid-conversation
  • Duplicate-contact cases (confirm merged records appear under one Groove customer)

Use Groove's webhook stream for real-time validation (optional):

Subscribe to Groove's conversation_created webhook during the load phase. Each event returns the conversation number and mailbox. Stream these into a counter and compare against your expected load count in near-real-time. This catches load failures faster than a post-hoc count query.

Step 9: Delta Sync and Cutover

During the initial migration, your team is still using Tidio. New conversations and tickets are being created.

  1. Record the ISO 8601 timestamp when your initial extraction started (e.g., 2024-04-09T00:00:00Z).
  2. After the initial load completes, re-extract Tidio tickets using created_from=2024-04-09T00:00:00Z on the /tickets endpoint.
  3. For chat messages (which lack server-side timestamp filtering): re-pull /contacts/{contactId}/messages for any contact whose last_seen value is newer than the extraction start timestamp. Filter messages client-side by timestamp.
  4. Load the delta into Groove. Your idempotency state database will skip any ticket already marked complete — only net-new and updated records load.
  5. Cutover: Switch your live chat widget, email forwarding, and channel integrations from Tidio to Groove. Update DNS/MX records if applicable.
  6. Run one final delta sync to catch stragglers created between the delta load and cutover.

Tidio publishes ticket and conversation webhooks (ticket.created, ticket.replied, conversation.operator_replied, and solve events), which makes short delta windows possible. One caveat: Tidio notes that changes made by the OpenAPI do not trigger webhooks, so don't expect webhook echo from API-driven test writes.

Edge Cases That Break DIY Migrations

  • Duplicate source contacts. Tidio's create-contact behavior always adds a new contact and never overwrites on matching email or distinct_id. If your Tidio tenant has years of chatbot or integration activity, you may have duplicate contacts. Dedupe before import or Groove history will fragment across multiple customers.
  • Rich messages and cards. Tidio supports interactive bot messages (carousels, buttons, quick replies). These have no Groove equivalent. Convert them to plain-text or HTML representations in the message body.
  • Conversation-level custom fields via REST. Groove supports conversation custom fields in the product and GraphQL schema, but the REST v1 ticket-create docs don't expose them. Plan a second GraphQL pass using customFieldValuesUpdate or flatten values into tags and notes.
  • GraphQL errors on 200 responses. Groove's GraphQL endpoint returns HTTP 200 even for failed mutations. Always check the errors array in the response body — a successful HTTP status does not mean the record was created.
  • Source throttling beats target parallelism. Tidio tops out at 60–120 req/min. Groove allows 200–800 req/min depending on plan. Throwing more workers at the loader doesn't fix a slow extractor.
  • Weak error visibility on Groove REST. Groove REST errors other than 422 return empty response bodies. Tidio returns a trace-id header on every response. Store both platform IDs, HTTP statuses, request payloads, and trace IDs for every failed record.
  • No idempotency on Groove ticket creation. Every POST to /v1/tickets creates a new ticket regardless of payload content. Without a local state database tracking completed records, a script crash creates duplicates on rerun.
Tip

Dry run strategy: Migrate a small but ugly sample first — one anonymous chat, one ticket with internal notes, one thread with attachments, one record that changed departments, and one duplicate-contact case. That sample reveals more than a 100-record happy-path batch.

What Cannot Be Migrated

Tidio Feature Why It Can't Move
Flows (chatbot automation) Groove uses basic rules, not visual flow builders
Lyro AI training data Proprietary to Tidio's AI engine
Viewed Pages history No equivalent tracking in Groove
Pre-chat survey config Groove uses its own widget config
Conversation ratings No standardized import path
WhatsApp/Instagram/Messenger channel config Must be reconfigured in Groove natively
Widget appearance customization Groove has its own widget system

Migration Timeline Estimates

Volume Estimated Time Notes
< 1,000 contacts, < 5,000 messages 1–2 days Extraction is the bottleneck at 60 req/min
1,000–10,000 contacts 3–5 days Includes transform, load, and validation
10,000+ contacts with full history 1–2 weeks Tidio rate limiting is the primary constraint

The biggest variable is Tidio's extraction rate limit. At 60 req/min on Plus, extracting 10,000 contacts plus their messages requires thousands of API calls that take hours even with perfect throttling. Budget additional time if your contact set includes significant anonymous visitor volume — those contacts require client-side deduplication and decision logic that adds processing overhead.

Choosing Your Migration Approach

DIY scripting works if your team has an engineer comfortable with API integrations and you have fewer than a few thousand contacts. The Tidio and Groove APIs are documented well enough for a competent developer to build a migration pipeline in a few days.

Third-party tools like Help Desk Migration (helpdeskmigration.com) advertise Tidio support, but verify they cover both chat conversations and Help Desk tickets, and check whether they support Groove as a target. Confirm the tool handles anonymous contacts, attachment re-hosting, and idempotency before committing.

Managed migration makes sense when volume is high, when you need zero downtime, or when the engineering team shouldn't be pulled off product work for a one-time infrastructure task.

For related migrations involving either platform, see our guides on Freshchat to Groove, Tidio to Zendesk, and Kustomer to Groove.

Technical Checklist

Before you start, make sure you've addressed every item:

  • Confirm Tidio plan has OpenAPI access (Plus or Premium)
  • Generate Tidio API credentials (Client ID + Secret)
  • Generate Groove API token
  • Define migration scope (which contacts, conversations, tickets)
  • Decide handling strategy for anonymous visitors
  • Decide whether to migrate bot-only conversations
  • Map Tidio departments → Groove groups
  • Map Tidio operators → Groove agents (create accounts)
  • Create mailboxes in Groove; record mailbox slugs for ticket assignment
  • Pre-create tags in Groove
  • Record Groove custom field IDs for customFieldValuesUpdate mutations
  • Initialize local idempotency state database before first load run
  • Build extraction script with rate limit handling and trace-id logging
  • Build deduplication step for contacts (normalize emails, merge by last_seen)
  • Build transform layer (sessionization, message grouping, schema mapping)
  • Build load script with error handling, retry logic, and skip_notifications
  • Apply tidio-import tag on all migrated tickets
  • Build validation script (count checks, spot checks, timestamp verification, import tag coverage)
  • Plan delta sync window: record extraction start timestamp, use created_from filter on /tickets, client-side filter on chat messages
  • Plan cutover procedure (widget swap, email forwarding, DNS/MX)
  • Run full pipeline on ugly sample set before running at scale
  • Archive Tidio data externally (full JSON dump) as a safety net

Frequently Asked Questions

Does Groove have a native Tidio importer?
No. Groove's documented Import2 source list does not include Tidio, so Tidio-to-Groove migrations require API-based scripting, a third-party migration tool that supports both platforms, or a managed migration service.
What Tidio plan do I need to access the API for migration?
Tidio's OpenAPI endpoints for contacts, conversations, and tickets are available only on the Plus plan ($749/mo) and Premium plan (~$2,999/mo). Lower-tier plans do not have backend API access for data extraction.
What data is lost when migrating from Tidio to Groove?
Tidio Flows (chatbot automation), Lyro AI training data, Viewed Pages history, pre-chat survey configurations, conversation ratings, and channel configurations (WhatsApp, Instagram, Messenger) have no equivalents in Groove and cannot be migrated.
How do I handle anonymous Tidio visitors in Groove?
Groove requires an email address for all Customer records. For anonymous Tidio visitors, you can generate deterministic placeholder emails (e.g., visitor_{id}@anonymous.local), drop the conversations, or archive them externally. Placeholder emails work technically but can pollute CRM and reporting if not isolated.
How long does a Tidio to Groove migration take?
For under 1,000 contacts, expect 1–2 days. For 1,000–10,000 contacts with conversation history, plan for 3–5 days. For 10,000+ contacts with full history, expect 1–2 weeks. The primary bottleneck is Tidio's 60 requests-per-minute rate limit on the Plus plan.

More from our Blog

LiveChat to Tidio Migration: The Technical Guide
Tidio/Migration Guide/Help Desk

LiveChat to Tidio Migration: The Technical Guide

Step-by-step technical guide to migrating from LiveChat to Tidio. Covers API mapping, data extraction, rate limits, attachment handling, and what you'll lose.

Wahab Wahab · · 18 min read