Skip to content

Groove to Gorgias Migration: A Technical Guide

How to migrate from Groove to Gorgias — API constraints, data mapping, attachment handling, and the step-by-step process to avoid data loss.

Rishabh Rishabh · · 24 min read
Groove to Gorgias 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

Groove to Gorgias Migration: A Technical Guide

Info

TL;DR: A Groove to Gorgias migration requires an API-led approach because Gorgias offers no native Groove importer. The primary engineering challenges are mapping Groove's multi-state ticket model to Gorgias's binary open/closed system, handling Groove's authenticated attachments, and managing Gorgias's strict API rate limits (40 requests per 20 seconds on API key auth, ~120 requests/minute effective throughput). At realistic throughput of 30–60 tickets per minute, a 10,000-ticket migration takes 3–6 hours of continuous import time; a 50,000-ticket migration takes multiple days. Plan 1–4 weeks total depending on volume, attachments, and data complexity.

A Groove to Gorgias migration moves conversation history, customer records, tags, agent assignments, and attachments from Groove's flat shared inbox into Gorgias's e-commerce-native helpdesk. There is no native import path — Gorgias offers a first-party historical importer for Zendesk and an email-history import for connected Gmail/Outlook addresses, but Groove is not on either list. Every Groove-to-Gorgias migration is API-driven, export-driven, or handled by a third-party tool. (docs.gorgias.com)

The core data maps reasonably well. Both platforms are conversation-based inboxes with tags, assignments, and customer records. The hard parts are Groove's transitional API state (REST v1 is deprecated, GraphQL v2 is incomplete for Inbox data), Gorgias's tight API rate limits, and the status-model mismatch between the two systems. This guide covers the real constraints on both sides, field-level data mapping, every realistic migration method, and the edge cases that cause silent data loss.

For getting data out of Groove, start with How to Export Data from Groove. If you're evaluating Gorgias migrations from other source platforms, Zendesk to Gorgias Migration: The Complete Technical Guide covers the native importer in depth. If you are still evaluating target platforms, see our Groove to Zendesk Migration Guide. For target-side rollback planning, see How to Export Data from Gorgias.

Warning

Scope check: This guide covers GrooveHQ shared inbox data moving into Gorgias. Knowledge Base articles, widget configurations, and CRM-specific entities must be migrated separately — Gorgias does not accept KB imports from Groove via the ticket API.

Why Teams Move from Groove to Gorgias

The switch is almost always driven by e-commerce needs that Groove was not built for:

  • Native Shopify integration. Gorgias surfaces order data, refund actions, and shipping status inside the ticket sidebar. Agents can issue refunds, duplicate orders, and edit shipping without leaving the conversation. Groove has a Shopify integration, but it does not offer the same depth of in-ticket actions.
  • Ticket-based pricing with unlimited agents. Gorgias charges by ticket volume, not by seat. For teams scaling headcount quickly, this shifts costs from per-agent to per-interaction.
  • AI Agent for autonomous resolution. Gorgias's AI Agent can auto-resolve tickets end-to-end on Shopify stores. Groove's AI features cover summarization and writing suggestions but not autonomous resolution.
  • Multi-channel consolidation. Gorgias natively handles email, chat, social (Instagram, Facebook, TikTok Shop), SMS, and voice in a single queue. Groove covers email, live chat, and social but lacks native TikTok Shop and SMS support.

If you don't sell on Shopify or a supported e-commerce platform, Gorgias's deepest features — AI Agent, order sidebar, refund actions — are gated to Shopify stores. Evaluate accordingly.

Groove Data Extraction Constraints

Groove offers two production-ready extraction paths, with a third in progress. Both working options have real limitations.

Groove JSON Export

Groove provides a full data export via Settings → Company → More → Exports. The export produces a GZIP-compressed JSON file following the v1 Tickets API "full" conversations format. It includes ticket metadata (status, assignee, tags), all messages in each conversation thread, and customer contact information. (help.groovehq.com)

The JSON export is the recommended method for bulk historical migration because it bypasses API rate limits entirely. Only one export request can be queued at a time, and large exports can take up to 72 hours to complete.

Info

Download links expire quickly. Groove generates short-lived download URLs for security. Trigger the export, wait for completion, and download immediately — the link will be dead if you come back hours later.

Groove REST API (v1)

The REST API at https://api.groovehq.com/v1 is no longer in active development but remains functional. It exposes tickets, messages, customers, agents, mailboxes, folders, groups, and attachments. Authentication uses a Bearer token from Settings → API.

curl -H "Authorization: Bearer YOUR_TOKEN" \
     -H "Content-Type: application/json" \
     https://api.groovehq.com/v1/tickets?page=1

Key extraction endpoints:

Endpoint Method Notes
/v1/tickets GET Lists tickets, paginated. Returns metadata, tags, assignee links, customer links.
/v1/tickets/:number/messages GET All messages for a ticket. Includes body, sender, timestamps.
/v1/customers GET Lists all customers.
/v1/agents GET Lists all agents.
/v1/attachments GET Lists attachments for a ticket.

The REST API uses page-based pagination (?page=N). Groove does not publicly document per-plan rate limits for the v1 REST API; treat rate limit thresholds as unverified and implement adaptive backoff (respect 429 responses and Retry-After headers) rather than assuming a fixed ceiling. For delta migrations (syncing tickets created after the initial JSON export), the REST API is the right tool. For bulk extraction of your full history, use the JSON export.

Groove GraphQL API (v2)

Groove's GraphQL API is the officially recommended path forward, but the Inbox and Knowledge Base portions are still under active development as of the date of this guide — check Groove's API changelog for current endpoint availability. The GraphQL API can work for flexible queries, but for bulk export of ticket history, the REST API or built-in JSON export is more battle-tested.

Warning

API transition risk: Groove's REST API is deprecated but functional. The GraphQL API coverage for Inbox data is incomplete. Before building extraction scripts, verify which endpoints return the data you need against your specific Groove plan. Test both APIs against a small dataset before committing to either extraction path.

Gorgias API Constraints for Import

Gorgias exposes a RESTful API for creating and managing tickets, customers, messages, tags, and custom fields. All requests go to https://{your-domain}.gorgias.com/api with HTTP Basic authentication.

Rate Limits

Gorgias rate limits are the single biggest constraint for any bulk import:

Auth Method Limit Window Effective RPM
API key 40 requests 20 seconds ~120
OAuth2 80 requests 20 seconds ~240
Enterprise (API key) 40 requests 10 seconds ~240
Enterprise (OAuth2) 80 requests 10 seconds ~480

The API uses a leaky bucket algorithm. Exceeding the limit returns 429 Too Many Requests with Retry-After and X-RateLimit-Reset headers. Read both headers; use Retry-After for the backoff duration. At 40 requests per 20 seconds (the typical API key rate), you get roughly 120 requests per minute. Creating a single ticket with its messages and customer requires multiple API calls — realistically, expect 30–60 tickets per minute throughput during a migration (this is an empirical approximation based on typical call patterns, not a guaranteed API throughput figure).

For a 10,000-ticket migration at 30–60 tickets per minute, that's roughly 3–6 hours of continuous import time assuming no errors. For 50,000+ tickets, you're looking at multiple days. Build queueing, retry, resumability, and idempotency into the importer instead of assuming one uninterrupted pass.

Tip

Set limit=100 on every list call. Gorgias uses cursor-based pagination with a default page size of 30 and a max of 100. Bumping to 100 cuts your request count by more than 3x during read operations.

Key Import Endpoints

Endpoint Method Purpose
POST /api/customers POST Create or find a customer
POST /api/tickets POST Create a ticket with initial message(s)
POST /api/tickets/{id}/messages POST Add messages to an existing ticket
PUT /api/tickets/{id}/custom-fields PUT Set custom field values (replace-all semantics)
PUT /api/tickets/{id} PUT Update ticket status, tags, assignee

The create-ticket endpoint accepts 1 to 500 messages in the initial payload. Threads longer than 500 messages need follow-up calls to the ticket-message endpoint. (developers.gorgias.com)

Danger

Replace-all semantics on custom fields. When you PUT to /api/tickets/{id}/custom-fields, the request body must contain all custom field values you want the ticket to have. Any field not included is deleted. Always fetch current values first, merge, then write back.

Deduplication and Idempotency Behavior

The external_id field on Gorgias tickets is the correct mechanism for tracking migrated records, but Gorgias does not enforce uniqueness on external_id at the API level. A POST /api/tickets with the same external_id value will create a second ticket — it will not return a 409 or match the existing record. Deduplication is your responsibility:

  • Before each ticket creation call, query GET /api/tickets?external_id=groove-{number} to check for an existing record.
  • Maintain a local mapping table (SQLite works) of groove_ticket_number → gorgias_ticket_id.
  • On script restart, load the mapping table and skip already-imported tickets.

Without this guard, a failed and restarted migration creates duplicate ticket records in Gorgias.

Common API Error Responses

Understanding error responses prevents wasted debugging time:

HTTP Status Common Cause Resolution
400 Bad Request Malformed JSON, missing required field, invalid field value Check response body for field-level error detail; sent_datetime missing and from_agent null are frequent causes
422 Unprocessable Entity Valid JSON but invalid business logic (e.g., invalid status value, unrecognized channel) Validate enum values (status: open/closed; channel: email, chat, api, phone, sms, twitter, facebook, instagram) against docs before bulk run
429 Too Many Requests Rate limit exceeded Read Retry-After header; implement exponential backoff
404 Not Found Invalid ticket ID in message append call Verify ticket was created successfully before appending messages

The channel field on messages deserves particular attention: Gorgias validates that the channel value is a recognized type (email, chat, api, phone, sms, twitter, facebook, instagram). Using channel: "api" is always safe for migrated historical data regardless of original source channel, but loses channel provenance. If you want to preserve the original channel (e.g., chat for Groove live chat conversations), Gorgias does not require an active integration of that channel type to accept the message — the field is stored as metadata. Verify this against current Gorgias API behavior before relying on it in production.

Automation and Webhook Behavior During Import

API-created messages with channel: "api" will not trigger auto-reply rules, but other rule actions (tagging, assignment, etc.) can still fire based on ticket creation and update events. This applies to both Rules and outbound webhooks — if you have external integrations (a Slack notifier, a CRM sync) subscribed to Gorgias webhook events, those webhooks will fire for every imported ticket. Tighten or pause automations and disable or filter webhooks before bulk import to prevent historical tickets from triggering modern workflows and external system noise.

Danger

Historical messages must carry sent_datetime. If a message is created via the API without sent_datetime, Gorgias treats it as a new outbound message and will actually send it to the customer. This is how test imports turn into accidental customer emails. Always set sent_datetime on every historical message. (developers.gorgias.com)

Warning

from_agent: true requires a valid Gorgias user. If you set from_agent: true on a message and the associated author does not match an existing Gorgias user, the API returns a 400 error with a field-level message indicating the user could not be resolved. Create all agent accounts — including a "Legacy Agent" placeholder for deprovisioned agents — before importing tickets.

Data Model Mapping: Groove → Gorgias

Both platforms are conversation-first inboxes, but the structural differences matter during transformation.

Object-Level Mapping

Groove Entity Gorgias Entity Notes
Conversation (ticket) Ticket 1:1 mapping. Store Groove number in Gorgias external_id for cross-reference. Note: Gorgias does not enforce external_id uniqueness — deduplication requires a local mapping table.
Message Message Includes body_html, sender, timestamps. Set sent_datetime on all historical messages.
Private note / internal phone log Internal note message Set public: false. Do not remap internal notes into customer-visible replies.
Customer Customer Email-based primary identity.
Agent User Map by email address. Create agents in Gorgias first.
Company Customer data field or custom widget Gorgias has no native company/organization entity. See note below.
Tag/Label Tag Tags are case-sensitive in Gorgias. Normalize case before import to avoid duplicates.
Mailbox N/A (integration) Gorgias uses channel integrations, not mailbox entities. Connect email addresses as integrations.
Group Team Create Gorgias Teams that mirror Groove Groups before importing tickets.
Folder (Smart Folder) View Rebuild manually. Map Smart Folder filter criteria to Gorgias View filters.
Instant Reply Macro Rebuild manually. Gorgias macro template variables differ from Groove's syntax.
Rule Rule Rebuild manually. Groove rule conditions/actions don't export programmatically.
Attachment Attachment Must be downloaded from Groove and re-uploaded. See attachment section.
Knowledge Base article Help Center article Separate migration. Gorgias Help Center import supports Zendesk, HelpDocs, Intercom, Re:amaze — not Groove.
Warning

Company/organization data has no native home in Gorgias. If your team relies on company-level grouping (multiple contacts under one org), Gorgias does not support this natively. Store company data in the customer data field for sidebar display, but you lose the ability to group tickets by organization. Gorgias also limits you to four active customer fields — choose carefully which Groove contact/company fields to promote. (developers.gorgias.com)

Status Mapping

This is where data silently degrades if you don't handle it explicitly.

Groove's current UI uses three statuses: Open, Snoozed, and Closed. The v1 API (which the JSON export uses) may also expose legacy states like unread, opened, pending, and spam. Gorgias exposes two statuses via API: open and closed.

Groove Status Gorgias API Status Recommended Handling
opened / unread / Open open Direct map.
pending / Snoozed open Gorgias supports snoozing via snooze_datetime on open tickets. Set it as a separate update after ticket creation if the snooze date is still in the future. If the snooze date has already passed, close the ticket instead of importing it as open. Append a tag like status:pending or status:snoozed to preserve context in all cases.
closed / Closed closed Direct map.
spam closed Import as closed with a spam tag, or skip entirely depending on your retention policy.

If you bulk-import snoozed tickets as open without setting snooze_datetime, they appear in agents' active queues and create noise on day one. If you import them as closed, you lose the intent that follow-up was planned.

Field-Level Mapping for Tickets

Groove Field Gorgias Field Transform Required
number external_id Store Groove ticket number for cross-reference. Prefix with groove- for namespacing (e.g., groove-1045).
created_at created_datetime ISO 8601 format.
updated_at updated_datetime ISO 8601 format.
state status Map per status table above.
tags tags Array of tag name objects: [{"name": "tag"}]. Case-sensitive.
priority N/A Gorgias has no native priority field. Map to a tag (e.g., priority-high) or a custom ticket field.
assigned_group assignee_team Map Groove group → Gorgias team.
assignee (agent email) assignee_user Map by email → Gorgias user ID.
summary N/A Gorgias derives the summary from the first message. No separate field.

Field-Level Mapping for Messages

Groove Field Gorgias Field Transform Required
body (HTML) body_html Direct map. Sanitize encoding issues (see note below).
body (plain text) body_text Optional plain-text version.
N/A stripped_text Populate if you rely on body-based rules or full-text search.
author (agent/customer) from_agent Boolean. true if sent by agent, false if from customer. Agent messages require a valid Gorgias user — map unresolvable agents to a Legacy Agent before setting from_agent: true.
created_at sent_datetime Critical. Use sent_datetime to prevent Gorgias from treating historical messages as new outbound sends.
note (boolean) public Groove internal notes → public: false in Gorgias.
Warning

Encoding and character set issues. Groove messages may contain non-UTF-8 characters, smart quotes, em-dashes, or emoji. The Gorgias API expects UTF-8. Transcode message bodies to UTF-8 and strip or replace invalid byte sequences before submission. Malformed encoding typically produces 400 errors with generic body-field messages that don't identify the character-level cause.

Customer Mapping

Groove Field Gorgias Field Notes
email email Primary identifier.
name firstname / lastname Groove stores a single name. Split on first space for Gorgias. Names with no space: put full value in firstname, leave lastname empty.
company_name data or custom widget Gorgias has no native company entity.

Payload Transformation Example

To illustrate the transformation, here is a simplified Groove ticket mapped to a Gorgias import payload:

Source: Groove JSON Export

{
  "id": 987654,
  "number": 1045,
  "state": "pending",
  "customer_email": "user@example.com",
  "assignee": "agent@company.com",
  "tags": ["refund", "escalated"],
  "messages": [
    {
      "body": "<p>I need a refund for my order.</p>",
      "created_at": "2023-10-12T14:20:00Z",
      "is_agent": false
    }
  ]
}

Target: Gorgias Ticket Creation Payload

{
  "external_id": "groove-1045",
  "channel": "email",
  "via": "api",
  "status": "open",
  "customer": {
    "email": "user@example.com"
  },
  "tags": [
    {"name": "refund"},
    {"name": "escalated"},
    {"name": "status:pending"}
  ],
  "messages": [
    {
      "channel": "email",
      "via": "api",
      "source": {
        "to": [{"address": "support@company.com"}],
        "from": {"address": "user@example.com"}
      },
      "body_html": "<p>I need a refund for my order.</p>",
      "sent_datetime": "2023-10-12T14:20:00Z",
      "from_agent": false,
      "public": true
    }
  ]
}

Key structural changes: tags become arrays of objects, state maps to status plus a preservation tag, messages require explicit channel and source objects, sent_datetime is set to prevent accidental outbound sends, and external_id is prefixed for namespacing. A follow-up PUT /api/tickets/{id} would set snooze_datetime if the pending snooze date is still in the future.

The Attachment Problem

Attachments are the most common cause of silent data loss in helpdesk migrations.

Groove attachments are stored behind authenticated URLs. If you extract a Groove message payload and pass the raw attachment URL into Gorgias, Gorgias will attempt to fetch the file, hit an authentication wall, and fail silently — no error in the API response, just a missing file.

To migrate attachments, your script must:

  1. Parse Groove message bodies for attachment URLs — including <img> tags for inline images in the HTML body.
  2. Download each file using a Groove API token for authentication.
  3. Re-host the file on a publicly accessible endpoint (e.g., an S3 bucket with temporary public read access, or GCS).
  4. Pass the new public URL into the Gorgias API payload.
  5. Gorgias fetches and hosts the file natively.

Inline images in HTML bodies deserve special attention. If you import the HTML as-is, those images reference Groove's CDN. They may work initially but will break if Groove decommissions the URLs after account closure. Download and re-host inline images alongside standard attachments, then rewrite the src attributes in the HTML body before submission.

This is the slowest part of any migration. Each attachment is a separate download-upload cycle, and Gorgias rate limits apply to the upload calls. For large migrations with many attachments, budget additional time proportional to attachment count — a migration with 5 attachments per ticket doubles or triples total import time compared to a text-only migration.

Migration Methods: Trade-offs

Method 1: JSON Export → Custom Script → Gorgias API

Best for: Teams with engineering capacity, any volume.

Export full conversation history as GZIP JSON. Write a transformation script. Import via Gorgias REST API. Full control over data transformation, attachment handling, and edge cases. Requires a developer comfortable with API orchestration, rate-limit management, and deduplication logic. Estimated effort: 1–2 weeks for a typical mid-size migration (5K–50K tickets).

Method 2: Groove REST API → ETL Pipeline → Gorgias API

Best for: Teams that need incremental extraction or real-time sync during transition.

Pull data from Groove's API in batches and push to Gorgias. Supports selective migration (e.g., only last 12 months) and incremental sync during parallel-run periods. The trade-off: you face double rate-limit constraints (Groove extraction + Gorgias import) and depend on Groove's deprecated REST API, which may break without notice. Build the deduplication mapping table from the start — delta runs will overlap with bulk import records.

Method 3: Third-Party Migration Tool

Best for: Non-technical teams with straightforward data.

Tools like Help Desk Migration support Gorgias as a target. Gorgias explicitly recommends partner tooling for teams coming from non-Zendesk providers. (docs.gorgias.com) Pre-built mapping handles common cases. The trade-off: per-record pricing adds up on large datasets, limited control over transformation logic, and Groove may not be a natively supported source in these tools — you may need to export to CSV first, which loses some metadata fidelity. Validate attachment and internal note handling specifically before committing to a third-party tool for a Groove source.

Method 4: Managed Migration Service

Best for: Teams with large volumes, complex data, or zero tolerance for data loss.

Hand the migration to an engineer-led service that handles extraction, transformation, rate-limit orchestration, attachment transfer, deduplication, and validation. Fastest path for complex migrations. Higher upfront cost than DIY, but includes error handling, test runs, and audit trails.

Step-by-Step Migration Process

Step 1: Audit Your Groove Data

Before touching any API, document what you have:

  • Total ticket count by status (Open, Snoozed, Closed, Spam)
  • Active agents and groups — list email addresses and group memberships
  • Tags in use — export the full tag list, identify active vs. legacy tags
  • Conversation custom fields — list all fields and their types
  • Contact/company custom fields — decide which map to Gorgias's four customer field slots
  • Instant Replies — copy each template's text for rebuild as Gorgias Macros
  • Rules — document conditions and actions for each Rule
  • Active integrations — Shopify, Stripe, Slack, HubSpot connections don't transfer
  • Knowledge Base — plan a separate migration path if applicable
  • Channel mix — identify email, chat, social, and phone conversation volumes; note that non-email channels require channel mapping decisions
  • Attachment volume — count total attachments and average per ticket; this directly determines import timeline

Step 2: Build the Gorgias Schema

Create the target environment before loading any data:

  1. Create your Gorgias account and connect your Shopify store (if applicable)
  2. Connect email channels — add the same email addresses used in Groove
  3. Create agent accounts with matching email addresses — including a "Legacy Agent" placeholder for any agents who have left the team
  4. Create Teams that mirror your Groove Groups
  5. Create custom Ticket Fields (Dropdown, Number, Text, Yes/No) — only fields created before import can be used in Rules
  6. Create tags matching your Groove tag taxonomy (remember: case-sensitive in Gorgias)
  7. Pre-create dropdown values — Gorgias dropdown CSV imports cap at 2,000 values and 5 levels
  8. Disable or filter automations and webhooks before running bulk import
Warning

Do not connect the same email address to both Groove and Gorgias simultaneously. Incoming messages will be duplicated across both platforms, creating conflicting ticket histories. Keep Groove active until migration is complete, then switch email routing.

Step 3: Extract Data from Groove

Option A — JSON Export (recommended for bulk): Trigger the export from Settings → Company → More → Exports. Download immediately when ready. This is the fastest path for your full history and bypasses API rate limits.

Option B — REST API (for deltas and selective extraction): Use the Groove REST API to pull tickets created or updated after your JSON export timestamp. Implement adaptive backoff on 429 responses — do not assume a fixed rate limit ceiling.

import requests
import time
 
GROOVE_TOKEN = "your_groove_api_token"
BASE_URL = "https://api.groovehq.com/v1"
headers = {
    "Authorization": f"Bearer {GROOVE_TOKEN}",
    "Content-Type": "application/json"
}
 
def get_all_tickets():
    tickets = []
    page = 1
    while True:
        resp = requests.get(f"{BASE_URL}/tickets?page={page}", headers=headers)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 10))
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        data = resp.json()
        tickets.extend(data.get("tickets", []))
        if not data.get("meta", {}).get("pagination", {}).get("next_page"):
            break
        page += 1
        time.sleep(0.5)  # Conservative pacing between pages
    return tickets

Step 4: Build the Local Mapping Table

Before importing, initialize a local SQLite database to track progress and enable idempotent reruns:

CREATE TABLE ticket_map (
    groove_ticket_number INTEGER PRIMARY KEY,
    gorgias_ticket_id INTEGER,
    imported_at TIMESTAMP,
    status TEXT  -- 'pending', 'imported', 'failed'
);
 
CREATE TABLE customer_map (
    groove_customer_email TEXT PRIMARY KEY,
    gorgias_customer_id INTEGER
);

On script restart, load existing mappings and skip records with status = 'imported'. This prevents duplicate ticket creation, since Gorgias does not enforce external_id uniqueness.

Step 5: Transform and Load into Gorgias

For each Groove conversation:

  1. Check the mapping table — skip if already imported.
  2. Create or find the customer via POST /api/customers using the customer email. Store the returned Gorgias Customer ID in the mapping table.
  3. Create the ticket via POST /api/tickets with the first message(s), tags, channel, status, and external_id set to groove-{number}.
  4. Record the mapping — write groove_ticket_number → gorgias_ticket_id to the mapping table immediately after successful creation.
  5. Add remaining messages via POST /api/tickets/{id}/messages in chronological order. Set sent_datetime on every message. Batches above 500 messages require staged writes.
  6. Set custom fields via PUT /api/tickets/{id}/custom-fields — remember replace-all semantics; fetch current values first if the ticket was partially updated.
  7. Update ticket metadata — set assignee, final status, and snooze_datetime if applicable.

Your script must implement exponential backoff. When the Gorgias API returns 429, read the Retry-After header and pause. Failing to handle 429s results in dropped messages and incomplete ticket threads.

import requests
import time
import sqlite3
 
GORGIAS_DOMAIN = "your-store"
GORGIAS_EMAIL = "[email protected]"
GORGIAS_API_KEY = "your_api_key"
BASE = f"https://{GORGIAS_DOMAIN}.gorgias.com/api"
 
def gorgias_request(method, endpoint, **kwargs):
    """Wrapper with exponential backoff on 429."""
    url = f"{BASE}{endpoint}"
    auth = (GORGIAS_EMAIL, GORGIAS_API_KEY)
    delay = 20
    for attempt in range(5):
        resp = requests.request(method, url, auth=auth, **kwargs)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", delay))
            time.sleep(retry_after)
            delay = min(delay * 2, 120)
            continue
        return resp
    raise Exception(f"Failed after 5 retries: {endpoint}")
 
def map_status(groove_state):
    return "open" if groove_state in ["opened", "pending", "unread"] else "closed"
 
def create_gorgias_ticket(conversation, db_conn):
    # Check mapping table first
    cursor = db_conn.execute(
        "SELECT gorgias_ticket_id FROM ticket_map WHERE groove_ticket_number = ?",
        (conversation["number"],)
    )
    row = cursor.fetchone()
    if row:
        return row[0]  # Already imported
 
    payload = {
        "external_id": f"groove-{conversation['number']}",
        "channel": "email",
        "via": "api",
        "status": map_status(conversation["state"]),
        "customer": {"email": conversation["customer_email"]},
        "tags": [{"name": t} for t in conversation.get("tags", [])],
        "messages": [{
            "body_html": conversation["first_message"]["body"],
            "from_agent": conversation["first_message"]["is_agent"],
            "sent_datetime": conversation["first_message"]["created_at"],
            "via": "api",
            "channel": "email",
            "public": True
        }]
    }
    resp = gorgias_request("POST", "/tickets", json=payload)
    resp.raise_for_status()
    gorgias_id = resp.json()["id"]
 
    # Record mapping immediately
    db_conn.execute(
        "INSERT INTO ticket_map VALUES (?, ?, datetime('now'), 'imported')",
        (conversation["number"], gorgias_id)
    )
    db_conn.commit()
    return gorgias_id

Step 6: Handle Attachments

For each attachment and inline image:

  1. Download from Groove's authenticated URL using your API token
  2. Upload to a publicly accessible location (S3, GCS) with appropriate expiry
  3. Update the message HTML to reference the new URLs before submitting the message payload to Gorgias
  4. After Gorgias ingests the file, the public hosting URL can be removed or expired

Budget extra time for this phase. Large migrations with many attachments significantly extend total import time due to per-file download-upload cycles consuming Gorgias rate limit budget.

Step 7: Validate

Do not rely on the script completing without errors as proof of success. Run these checks:

  • Ticket count match — total in Groove vs. Gorgias
  • Message count per ticket — spot-check 20–50 tickets for correct threading and message order
  • Customer linkage — verify customers are correctly associated with their tickets
  • Tag preservation — confirm tags transferred with correct case
  • Status accuracy — check open/closed distribution matches source
  • Snoozed ticket handling — verify snooze_datetime is set on pending tickets with future snooze dates; verify past-snooze tickets are closed
  • Attachment availability — open 10–20 tickets with attachments and verify files download
  • Timestamp integrity — confirm sent_datetime values match source data, not today's date
  • Inline images — check HTML body rendering for broken <img> tags
  • Internal notes — verify notes are marked private (public: false)
  • Encoding — check a sample of tickets with special characters, emoji, or non-Latin scripts for correct rendering
  • Deduplication — verify no ticket appears twice (query Gorgias for duplicate external_id values)

Step 8: Rebuild Automations

No automation migrates programmatically between Groove and Gorgias:

  • Instant Replies → Macros: Copy the text content. Gorgias macros use different template variables ({{ticket.customer.firstname}} vs. Groove's syntax). Macro CSV import supports name, body_text, tags, and id only — no formatting preservation on new imports. (docs.gorgias.com)
  • Rules → Rules: Gorgias Rules use similar conditions and actions, but available triggers and operators differ. Map each Groove Rule manually. Re-enable automations only after testing on a small set of live tickets.
  • Smart Folders → Views: Recreate filter logic in Gorgias's view system.
  • Webhooks: Reconnect and re-enable any webhook integrations that were disabled during import.

Step 9: Run Delta Sync and Go Live

Your team will continue answering tickets in Groove during the migration. Once the bulk import and validation are complete:

  1. Use the Groove REST API to fetch tickets updated since your JSON export timestamp
  2. Transform and push these delta tickets to Gorgias (the mapping table prevents duplicates for tickets that appear in both the bulk export and the delta)
  3. Validate the delta
  4. Disable email forwarding to Groove
  5. Verify Gorgias is receiving new emails on the connected addresses
  6. Deactivate Groove integrations (Shopify, Slack, Stripe)
  7. Activate equivalent integrations in Gorgias
  8. Brief your support team on the new interface

Edge Cases That Cause Data Loss

Inline images in HTML bodies. Groove messages may contain inline images hosted on Groove's CDN. If Groove decommissions those URLs after account closure, every imported ticket with inline images breaks. Download and re-host them during migration, and rewrite src attributes in the HTML before submitting to Gorgias.

Snoozed tickets with past snooze dates. If a ticket was snoozed until a date that has already passed, importing it as open creates a false active ticket in agents' queues. Import these as closed instead, with a status:snoozed tag for context.

Merged conversations. Groove supports merging conversations. Merged tickets may have discontinuous message threads or messages from multiple original customers. Verify message ordering and customer attribution after import.

Phone call notes. Groove logs phone calls as internal-only conversations with no customer-visible messages. Import as internal notes (public: false) in Gorgias, not as customer-facing tickets.

Long message threads. The create-ticket endpoint accepts up to 500 messages. Threads longer than that need staged writes — create the ticket with the first batch, then append remaining messages via the ticket-message endpoint. For noisy system-generated threads, consider collapsing low-value audit chatter into a single internal note rather than replaying every line.

Non-email channels. Groove conversations can come from email, live chat, Facebook, and logged phone interactions. Use channel: "api" as a safe default for all migrated messages if you cannot validate the target channel type; preserve the original channel in a tag (e.g., source:chat) so it is queryable.

Groove ticket numbers vs. Gorgias IDs. Gorgias assigns its own ticket IDs. Store the original Groove ticket number in external_id (prefixed as groove-{number}) so agents can cross-reference during the transition period.

Agents no longer on the team. If you push a ticket assigned to an agent email that doesn't exist in Gorgias, the API returns a 400 error. Create a "Legacy Agent" user for historical assignments before running the import.

Encoding issues. Non-UTF-8 characters in Groove message bodies produce 400 errors on the Gorgias API with generic messages that don't identify the offending character. Transcode all message content to UTF-8 and strip or replace invalid byte sequences as part of the transformation step.

Duplicate external_id records. Gorgias does not enforce uniqueness on external_id. A restarted migration without a local mapping table creates duplicate tickets. Always maintain the mapping table and check it before every ticket creation call.

Mailbox-to-integration mapping. Groove lets you create as many inboxes as you need. Gorgias organizes around stores, channels, teams, and views. A Groove mailbox might map to a store, a team, or just a routing tag/view pair. Decide this mapping before import, or historically correct tickets end up in the wrong operational lane.

What Cannot Be Migrated

Item Why Workaround
Instant Replies No programmatic export from Groove Copy text manually, rebuild as Gorgias Macros
Rules/Automations No export API Document conditions and actions, rebuild in Gorgias
Smart Folders Configuration-only, not data Recreate as Gorgias Views
Knowledge Base Gorgias KB import doesn't support Groove Export articles, reformat, import via Gorgias KB editor or CSV
Reports/Analytics Platform-specific Export historical data to a BI tool before decommissioning Groove
Integrations Platform-specific credentials Reconnect each integration (Shopify, Slack, Stripe) in Gorgias
Webhook subscriptions Platform-specific configuration Reconfigure webhooks in Gorgias after import is complete

Timeline Expectations

Scenario Ticket Volume Estimated Timeline
Small team, simple data, minimal attachments Under 5,000 tickets 3–5 days
Mid-size, with attachments 5,000–50,000 tickets 1–2 weeks
Large, with complex data and KB 50,000+ tickets 2–4 weeks

Timelines include data audit, extraction, transformation, import, validation, and automation rebuild. The Gorgias API rate limit is typically the bottleneck — not the extraction from Groove. Attachment volume is the primary multiplier on total time within a given ticket-count tier.

When to DIY vs. Use a Managed Service

Handle it yourself if:

  • Fewer than 10,000 tickets
  • Minimal or no attachments
  • A developer can dedicate 1–2 weeks
  • Simple tag and status model
  • No external webhooks or integrations dependent on Gorgias events

Use a managed service if:

  • Volume exceeds 50,000 tickets
  • Full attachment preservation required
  • Snoozed ticket state and snooze datetimes must be preserved
  • Migration must complete in days, not weeks
  • Zero tolerance for data loss (compliance, audit trail requirements)
  • External integrations are subscribed to Gorgias webhook events and require controlled cutover

ClonePartner has handled Groove migrations across various target platforms. If you need full attachment handling, rate-limit orchestration, deduplication, and validation — we can scope it in a 30-minute call.

Frequently Asked Questions

Does Gorgias have a native importer for Groove?
No. Gorgias offers a native historical data importer for Zendesk and an email-history import for connected Gmail/Outlook addresses, but not for Groove. A Groove to Gorgias migration requires using the Gorgias REST API, Groove's JSON export, or a third-party migration tool like Help Desk Migration.
How do Groove ticket statuses map to Gorgias?
Groove uses Open, Snoozed, and Closed (the v1 API also exposes unread, opened, pending, and spam). Gorgias only has open and closed via its API. Snoozed/pending tickets should be imported as open with snooze_datetime set separately if the snooze date is still in the future. Append a tag like status:pending to preserve context.
Can I migrate Groove attachments to Gorgias?
Yes, but attachments must be downloaded from Groove's authenticated URLs and re-uploaded to Gorgias via the API. You cannot pass Groove attachment URLs directly — Gorgias will fail to fetch them. This is the slowest part of most migrations due to the per-file download-upload cycle and Gorgias rate limits.
How long does a Groove to Gorgias migration take?
Typically 3–5 days for under 5,000 tickets, 1–2 weeks for 5K–50K tickets, and 2–4 weeks for 50K+ tickets. The Gorgias API rate limit (40 requests per 20 seconds on API key auth) is usually the bottleneck.
What is the biggest technical risk in a Groove to Gorgias migration?
Accidentally sending historical messages to customers. If you create messages via the Gorgias API without setting the sent_datetime field, Gorgias treats them as new outbound messages and actually sends them. Always set sent_datetime on every historical message.

More from our Blog