Gladly to HappyFox Migration: The Complete Technical Guide
A technical guide to migrating from Gladly to HappyFox — covering API extraction, data model mapping, rate limits, truncation risks, timestamp caveats, and validation.
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
Gladly to HappyFox Migration: The Complete Technical Guide
TL;DR: Gladly to HappyFox Migration
Migrating from Gladly to HappyFox is a data-model translation — person-centric to ticket-centric — not a simple export-import. There is no native migration path. You must extract data through Gladly's REST API (or its bulk Export API for complete history), transform Conversations into ticket payloads, and load them into HappyFox via its REST API (v1.1). The hardest problems: Gladly's API caps at 100 conversations per customer and 1,000 items per conversation, both unpaginated and silently truncated. On the HappyFox side, you get 300 POST requests per minute and 500 GET requests per minute, with a 10-minute lockout on 429 errors. HappyFox supports created_at (ISO 8601 UTC format, e.g., 2023-11-15T14:30:00Z) on ticket creation, but its documented reply and note endpoints do not expose a field for backdating individual updates. For a mid-size dataset of 5,000–25,000 conversations, expect 8–15 business days end to end with one engineer working full-time.
Last updated: August 2026. API behaviors verified against Gladly REST API and HappyFox API v1.1 documentation as of this date.
What This Migration Actually Involves
Gladly is a person-centric customer service platform. Customer communications across all channels are stored in one lifelong conversation thread in one centralized view. Its data model centers on the Customer — a persistent profile with a continuous timeline of Conversations, each containing Conversation Items (emails, chat messages, SMS, voice calls, notes). Agents see one lifelong conversation across voice, email, chat, SMS, and social with complete order history.
HappyFox is a traditional ticket-based help desk. All HappyFox help-desk instances include a RESTful web service API that enables ticket creation, ticket update submission, and ticket and user listing. It organizes support requests into Categories, assigns them to Staff, and tracks them through status workflows with priorities, due dates, and custom fields.
The structural mismatch is the core challenge. A single Gladly Customer might have 50 Conversations spanning 3 years and 6 channels. In HappyFox, each of those Conversations becomes a discrete Ticket with its own category, status, assignee, and update thread. You are not moving data between compatible schemas — you are translating between two fundamentally different philosophies of support record-keeping.
Why Teams Move from Gladly to HappyFox
Teams leave Gladly for specific operational reasons:
-
Cost structure. Gladly's pricing targets mid-market and enterprise. HappyFox starts at $29 per agent per month, with higher tiers adding multi-brand helpdesk capabilities, custom email, custom domain, custom roles and permissions, and custom ticket queues. (Verify current pricing on each vendor's site — these figures shift frequently.)
-
Ticket-based workflow preference. Not every team benefits from Gladly's conversation-first model. Teams that need discrete ticket IDs, strict category-based routing, SLA timers per ticket, and traditional queue management often find HappyFox a better operational fit.
-
Asset management and ITSM needs. HappyFox includes native asset management and task management features on higher tiers that Gladly does not offer natively.
Gladly vs. HappyFox: Data Model Mapping
Before writing any extraction code, you need a clear mapping between the two platforms:
| Gladly Object | HappyFox Equivalent | Migration Notes |
|---|---|---|
| Customer | Contact (User) | Map email, phone, name. Custom Attributes → Contact Custom Fields. Gladly allows multiple emails; HappyFox requires a primary email. |
| Conversation | Ticket | Each Conversation becomes one Ticket. |
| Conversation Item | Ticket Update | Messages, notes, replies become staff/contact updates. |
| Topic | Category or Tag | Gladly Topics are labels; HappyFox Categories are structural. Decide which mapping fits your workflow. |
| Agent | Staff | Map agent IDs to HappyFox staff IDs. Must be created before ticket import to preserve assignment history. |
| Team | (No direct equivalent) | HappyFox uses Categories for routing, not agent teams. |
| Inbox | Category | Gladly Inboxes may map to HappyFox Categories depending on your structure. |
| Task | Ticket (separate) | Gladly Tasks become separate HappyFox Tickets. |
| Answer | Knowledge Base Article | Gladly Answers can be extracted via GET /api/v1/answers, but HappyFox's public KB API is read-only — articles must be recreated through the UI or a separate content pipeline. |
| Smart Match / Rules | Smart Rules | Cannot be migrated. Rebuild manually. |
| SLA Policies | SLA Policies | Cannot be migrated. Rebuild manually. Historical SLA performance data requires export to an external BI tool. |
Topics ≠ Categories. Gladly Topics are flexible labels that can be stacked on any Conversation. HappyFox Categories are structural containers — every ticket lives in exactly one Category. If you have been using multiple Topics per Conversation in Gladly, pick a primary Topic → Category mapping and push secondary Topics to HappyFox Tags. Gladly topics can also carry parent-child structure via parentId; HappyFox tags are flat strings. If topic hierarchy matters for reporting or routing, use a custom field or prefixed tag scheme (e.g., topic:billing:refund) to preserve that structure.
Phase 1: Data Extraction from Gladly
Authentication and API Access
Clients access the REST API via HTTPS at your organization's Gladly domain (e.g., https://{organization}.gladly.com). Resources follow REST semantics and use standard HTTP verbs, response codes, and authentication schemes. All responses and payloads are JSON.
Gladly uses HTTP Basic Authentication with your agent email and an API token. Generate API tokens under your Gladly admin settings.
Rate Limits on the Gladly Side
Gladly's default rate limits apply uniformly across all HTTP methods:
| Method | Limit |
|---|---|
| GET | 10 requests/second |
| POST | 10 requests/second |
| PUT | 10 requests/second |
| PATCH | 10 requests/second |
| DELETE | 10 requests/second |
Organizations that send many requests quickly may see 429 error responses. Individual APIs may have their own documented rate limits that override these defaults.
At 10 GET requests/second, you can process approximately 600 customers per minute (one call to list conversations, then one per conversation for items). For 20,000 customers with an average of 3 conversations each, extraction alone requires ~80,000 API calls — roughly 2–3 hours of continuous runtime at safe throttle.
The Silent Truncation Problem
This is the single biggest extraction risk:
-
The conversation list endpoint (
GET /api/v1/customers/{customerId}/conversations) is not paginated and returns at most 100 conversations. Conversations are returned in ascending timestamp order. TheGladly-Limited-Dataresponse header is set totrueif the customer has more conversations than returned. -
The conversation items endpoint (
GET /api/v1/conversations/{conversationId}/items) is not paginated and returns at most 1,000 items. The sameGladly-Limited-Dataheader signals truncation.
If a customer has 101+ conversations, you silently lose everything beyond the first 100. If a single conversation has 1,001+ items, same problem. There is no standard pagination workaround for either endpoint. You must monitor the Gladly-Limited-Data response header on every call.
Using the Export API for Complete Extraction
For datasets where truncation is a risk, use Gladly's file-based Export API instead of per-customer REST calls.
The Export API is a comprehensive, file-based data export that covers the lifetime of your customers' conversations. It exports all communications successfully delivered within a specified date range to a central data repository such as a data warehouse or data lake.
Key constraints of the Export API:
- By default, export files are generated every 24 hours — data is at least one day behind real-time.
- Export files are available for 14 days before being removed. Build an automated retrieval job, not a manual one-time download.
- Each export file has a limit of 2,000 Conversation items.
- The Export API gives you
customers.jsonland conversation item files, but you still need supplemental per-customer GET calls for custom attributes and per-conversation calls for metadata (assignee, inbox, topics).
Customer Pagination and Cursor Handling
The GET /api/v1/customer-profiles endpoint uses cursor-based pagination, not offset pagination. Each response includes a nextCursor value. Pass this value as the cursor query parameter on the next request to retrieve the subsequent page. Important edge case: if a new customer is created between two paginated requests, that customer may appear in a subsequent page or be missed entirely depending on cursor implementation. Run a final count reconciliation after extraction completes to catch any gaps. The endpoint also returns 301 redirects for merged customer IDs — your extraction script must follow these redirects and deduplicate by canonical ID.
Extraction Script Approach
Here is a simplified extraction flow in Python:
import requests
import time
import json
GLADLY_DOMAIN = "https://yourorg.gladly.com"
AUTH = ("admin@yourorg.com", "your-api-token")
def get_customers(cursor=None):
"""Paginate through all customers using cursor-based pagination."""
url = f"{GLADLY_DOMAIN}/api/v1/customer-profiles"
params = {"pageSize": 100}
if cursor:
params["cursor"] = cursor
resp = requests.get(url, auth=AUTH, params=params)
resp.raise_for_status()
data = resp.json()
# data["nextCursor"] is None when pagination is exhausted
return data
def get_conversations(customer_id):
"""Get up to 100 conversations for a customer. Check truncation header."""
url = f"{GLADLY_DOMAIN}/api/v1/customers/{customer_id}/conversations"
resp = requests.get(url, auth=AUTH)
# Handle merged customer redirects
if resp.status_code == 301:
canonical_url = resp.headers.get("Location")
resp = requests.get(canonical_url, auth=AUTH)
resp.raise_for_status()
truncated = resp.headers.get("Gladly-Limited-Data", "false")
if truncated.lower() == "true":
print(f"WARNING: Customer {customer_id} has >100 conversations — use Export API")
return resp.json(), truncated.lower() == "true"
def get_conversation_items(conversation_id):
"""Get up to 1000 items for a conversation. Check truncation header."""
url = f"{GLADLY_DOMAIN}/api/v1/conversations/{conversation_id}/items"
resp = requests.get(url, auth=AUTH)
resp.raise_for_status()
truncated = resp.headers.get("Gladly-Limited-Data", "false")
if truncated.lower() == "true":
print(f"WARNING: Conversation {conversation_id} has >1000 items — use Export API")
time.sleep(0.12) # Stay under 10 req/s
return resp.json(), truncated.lower() == "true"Always check Gladly-Limited-Data. If this header returns true for any customer or conversation, your REST extraction is incomplete. Fall back to the Export API for those records, or explicitly document the data loss. Silence is the worst outcome — you will not know what you are missing until an agent reports it weeks later.
Conversation Item Initiator Types
Gladly conversation items carry an initiator field that determines how they map to HappyFox update types. Handle all four cases explicitly:
| Gladly Initiator | Item Type | HappyFox Mapping |
|---|---|---|
CUSTOMER |
Inbound message | user_reply |
AGENT |
Agent reply | staff_update |
AGENT + internal flag |
Internal note | staff_pvtnote |
SYSTEM |
System event | Filter out (or append as staff_pvtnote for audit) |
BOT / AI |
Automated response | Treat as staff_update with [Bot] prefix, or filter |
System events include items like "Agent reassigned conversation" or "Conversation reopened." Filter these during transformation unless you specifically want audit trails in HappyFox ticket updates. Bot and AI-generated items from Gladly Sidekick require an explicit decision: migrate as prefixed staff updates, or discard. Make this decision before writing load code.
Attachment Handling During Extraction
Gladly attachments are hosted on expiring URLs. Your extraction script must download the binary file to a secure intermediate server during extraction — do not store the Gladly URL and plan to re-fetch later. Once your Gladly instance is deprovisioned, those URLs become dead links.
Voice recordings and transcripts require separate handling via dedicated Gladly endpoints. Decide up front whether you are migrating binaries, transcripts only, or storing references to an external archive. Waiting until load day is how migrations end up partially complete.
Phase 2: Data Transformation
The transformation layer is where Gladly's person-centric model gets reshaped into HappyFox's ticket-centric model.
Conversation → Ticket Mapping
Each Gladly Conversation becomes one HappyFox Ticket. Key fields to transform:
- Subject: Gladly conversations do not always have a subject line (chat and SMS do not). Generate one from the first message body (first 80 characters) or the Topic name.
- Category: Map from the Conversation's primary Topic or Inbox. Pre-create all Categories in HappyFox and reference them by integer ID — not by name. If your Gladly design used inboxes mainly as routing endpoints, keep the HappyFox category model smaller and carry legacy inbox names in a custom field or tag rather than exploding your category list.
- Status: Map Gladly conversation statuses (
OPEN,CLOSED, etc.) to HappyFox statuses (New, In Progress, On Hold, Completed). HappyFox status updates require numeric IDs, not string names. - Contact (email): Required for ticket creation. Pull from the Gladly Customer profile primary email.
- Priority: Gladly does not have ticket-level priority. Set a default or derive from Topic/SLA context.
created_atformat: HappyFox accepts ISO 8601 UTC timestamps in the format2023-11-15T14:30:00Z. Gladly timestamps are also UTC. Normalize timezone handling at the transformation layer — do not convert to local time.
Conversation Items → Ticket Updates
Each Conversation Item becomes an update on the corresponding HappyFox ticket:
- Initiator type determines endpoint. See the initiator mapping table above. Customer messages go to
user_reply, agent replies tostaff_update, internal notes tostaff_pvtnote. - System events. Filter these during transformation unless you want audit logs in ticket updates.
- Timestamps. HappyFox supports
created_aton ticket creation. The documentedstaff_update,staff_pvtnote, anduser_replyendpoints do not expose a backdating field for individual replies or notes. See the timestamp fidelity section below.
Multi-Channel Flattening
A single Gladly Conversation can span email, chat, SMS, and voice. HappyFox tickets do not natively carry per-message channel metadata. Options:
- Prefix each update with a channel tag like
[Chat],[Email],[SMS]in the message body. - Use a custom field on the HappyFox ticket to record the primary channel.
- Accept the loss — most teams do not need per-message channel data in the target system.
Phase 3: Loading Data into HappyFox
Why Not CSV Import
If you care about message-by-message history, do not use HappyFox's CSV ticket migration as your primary method. As noted in our HappyFox to Freshdesk migration guide, HappyFox's ticket migration CSV format maps the Text column to the first message only. Later correspondence cannot be imported as separate updates, and attachments are out of scope. At best, you can group later replies into a single private note. That is acceptable for a light archive. It is not acceptable for a faithful migration of operational history.
HappyFox API Basics
All HappyFox help-desk instances include a RESTful web service API supporting ticket creation, ticket update submission, and ticket and user listing. The API supports JSON and multipart/form-data payload formats.
Authentication uses HTTP Basic Auth with your HappyFox API key and auth code. To enable the API, go to Main Menu > Apps > Goodies > API in your HappyFox account.
Key endpoints:
| Endpoint | Purpose |
|---|---|
GET /api/1.1/json/categories/ |
List categories (required for ID mapping before any ticket creation) |
GET /api/1.1/json/staff/ |
List staff members |
GET /api/1.1/json/users/ |
List/create contacts (bulk upsert up to 100 per request) |
GET /api/1.1/json/ticket_custom_fields/ |
List ticket custom fields and their option IDs |
POST /api/1.1/json/tickets/ |
Create tickets (single or bulk, up to 100 per request) |
POST /api/1.1/json/ticket/<number>/staff_update/ |
Add staff replies to existing ticket |
POST /api/1.1/json/ticket/<number>/staff_pvtnote/ |
Add private notes to existing ticket |
POST /api/1.1/json/ticket/<number>/user_reply/ |
Add customer replies to existing ticket |
EU domain. If your HappyFox account is hosted in the EU, use .happyfox.net instead of .happyfox.com. Calls to the wrong domain fail silently or route to the wrong instance with no clear error message.
Rate Limits and the 429 Lockout
HappyFox enforces 500 GET requests/minute and 300 POST requests/minute globally across the account. As we detailed in our Freshchat to HappyFox migration guide, when this threshold is exceeded, the client receives 429 error responses for the next 10 minutes — a full lockout, not just throttling.
The HappyFox API does not reliably return a Retry-After header or X-RateLimit-Remaining counter on 429 responses in v1.1. Do not rely on these headers to determine wait time. Instead, build your loader to treat any 429 as a mandatory 600-second sleep before retrying. Conservative throttling — targeting no more than 200 POST requests per minute — keeps you safely below the 300/min threshold and provides headroom for bursts. At 200 POST/min with an average of 5 updates per ticket, you can load approximately 2,400 tickets per hour.
Bulk Ticket Creation
A maximum of 100 tickets can be created in one request using POST /api/1.1/json/tickets/ with an array payload. The response returns a list indicating which payloads succeeded and which had validation errors. Parse the error array on every bulk response — failed tickets do not raise HTTP-level errors; they appear only in the response body.
Concurrent API calls to the same ticket are not supported. If you are creating a ticket and then immediately adding updates to it, serialize those calls sequentially. Parallel calls to the same ticket number produce unpredictable results.
Deduplication Key for Delta Sync and Upserts
The hardest part of any upsert or delta sync is detecting whether a Gladly Conversation already exists as a HappyFox ticket. HappyFox has no native "external ID" field on tickets. The reliable pattern is:
- Store the Gladly Conversation ID in a HappyFox ticket custom field (e.g., a text field named
gladly_conversation_id) at creation time. - Before creating a ticket during delta sync, query HappyFox's ticket list API with a custom field filter for the Gladly Conversation ID.
- If a match is found, append new items as updates to the existing ticket. If no match is found, create a new ticket.
This requires pre-creating the gladly_conversation_id custom field in HappyFox before the initial load. Without this deduplication key, delta syncs are unreliable — you will create duplicate tickets for conversations that were already migrated.
Thread Replay Order
For faithful history, use the ordered sequence of HappyFox write endpoints:
1. Pre-create all HappyFox Categories, Staff, and custom fields
2. Create or upsert the HappyFox contact (with full custom field set — see gotcha below)
3. Create the HappyFox ticket with original created_at (ISO 8601 UTC) and gladly_conversation_id custom field
4. Replay customer messages via user_reply in chronological order
5. Replay agent replies via staff_update in chronological order
6. Insert internal notes via staff_pvtnote
7. Reapply tags, status, assignee, and custom fields
8. Attach files via multipart/form-data (capped at 25 MB total per request)Example ticket creation with created_at and deduplication field:
curl -X POST https://<account>.happyfox.com/api/1.1/json/tickets/ \
-H "Authorization: Basic <credentials>" \
-H "Content-Type: application/json" \
-d '{
"category": 1,
"contact": 54,
"subject": "Refund request for order #999",
"text": "I need a refund for my recent purchase.",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"created_at": "2023-11-15T14:30:00Z",
"custom_fields": {
"gladly_conversation_id": "conv_abc123"
}
}'Here is a Python loader with 429 handling:
import requests
import time
HF_DOMAIN = "https://yourcompany.happyfox.com"
HF_AUTH = ("api-key", "auth-code")
def create_tickets_bulk(ticket_payloads):
"""Create up to 100 tickets in a single request. Parse per-ticket errors."""
url = f"{HF_DOMAIN}/api/1.1/json/tickets/"
resp = requests.post(url, json=ticket_payloads, auth=HF_AUTH)
if resp.status_code == 429:
print("Rate limited — sleeping 600 seconds (10-minute lockout)")
time.sleep(600)
return create_tickets_bulk(ticket_payloads)
resp.raise_for_status()
result = resp.json()
# Check per-ticket errors — 429 does not surface HTTP errors for individual failures
for i, item in enumerate(result):
if "error" in item:
print(f"Ticket {i} failed: {item['error']}")
return result
def find_ticket_by_gladly_id(gladly_conversation_id):
"""Look up an existing HappyFox ticket by the gladly_conversation_id custom field."""
url = f"{HF_DOMAIN}/api/1.1/json/tickets/"
params = {"custom_field_gladly_conversation_id": gladly_conversation_id}
resp = requests.get(url, auth=HF_AUTH, params=params)
resp.raise_for_status()
data = resp.json()
tickets = data.get("data", [])
return tickets[0] if tickets else None
def add_update_to_ticket(ticket_number, endpoint, payload):
"""Add a reply, note, or update to an existing ticket with rate limit handling."""
url = f"{HF_DOMAIN}/api/1.1/json/ticket/{ticket_number}/{endpoint}/"
resp = requests.post(url, json=payload, auth=HF_AUTH)
if resp.status_code == 429:
print(f"Rate limited on ticket {ticket_number} — sleeping 600 seconds")
time.sleep(600)
return add_update_to_ticket(ticket_number, endpoint, payload)
resp.raise_for_status()
time.sleep(0.3) # ~200 req/min target
return resp.json()Attachment Upload
HappyFox attachment uploads use multipart/form-data with a Content-Type: multipart/form-data header and the file in a field named attachments. The total per-request size cap is 25 MB. For attachments larger than 25 MB, split into multiple requests or store externally and reference via URL in the ticket body. Include the original filename in the multipart disposition — HappyFox uses this as the displayed filename in the ticket.
Contact Custom Field Gotcha
In HappyFox's contacts API, when you update a contact's custom fields, all custom field values not included in the payload are reset to empty. Do not dribble contact custom fields in over several passes. Load contacts with the complete canonical field set in a single request, then avoid partial custom-field updates for the remainder of the migration.
The Timestamp Fidelity Problem
HappyFox accepts created_at in ISO 8601 UTC format (2023-11-15T14:30:00Z) on ticket creation, allowing you to preserve the original conversation start date. However, the documented staff_update, staff_pvtnote, and user_reply endpoints do not expose an equivalent backdating field for individual replies or notes. Individual message timestamps are not officially supported for backdating in the v1.1 public API.
If audit-grade chronology matters, you have three options:
- Embed the original timestamp in the update body — prepend
[Original timestamp: 2023-11-15T14:32:00Z]to each migrated message. - Use a custom field on the ticket for key date milestones (first response, resolution).
- Maintain an immutable external archive of the original Gladly export (e.g., in S3 or a data warehouse) as the system of record for audit purposes. This is the most defensible approach for compliance-sensitive teams.
Decide which approach fits your compliance requirements before writing any load code. Retrofitting timestamp handling after the load has run is expensive.
Phase 4: Validation and Reconciliation
After the load completes, validate every layer:
- Record counts. Compare total Gladly Conversations extracted vs. HappyFox Tickets created. Every mismatch needs investigation — check the error array from bulk creation responses first.
- Thread integrity. Spot-check 50–100 tickets. Verify that message order, sender attribution (staff vs. contact), and initiator types match the Gladly source.
- Contact deduplication. Gladly allows multiple emails per Customer profile. HappyFox enforces unique emails on contacts. The primary email decision must be made before loading — changing it after load requires manual correction.
- Custom field values. HappyFox custom fields use numeric IDs for dropdown options. A single mismap corrupts the field silently across every ticket in that category.
- Deduplication key integrity. Verify that every migrated ticket has a populated
gladly_conversation_idcustom field. Missing values break delta sync logic. - Attachments. Verify file sizes and counts match. Attachments are the most common source of post-migration support tickets.
HappyFox's ticket list API supports pagination up to 50 per page and filters including created-on-or-after, last-modified-on-or-after, and tag/status — use these for automated post-load reconciliation scripts rather than manual spot-checks alone.
Run a dry-run first. Create a test Category in HappyFox and load 100 converted tickets before running the full migration. Include representative edge cases: customers with multiple conversations, conversations with many items, records with attachments, merged customers, threads with internal notes, and bot-initiated items. If the sample fails, the full migration will fail more slowly and more expensively.
What Cannot Be Migrated Programmatically
Some Gladly features have no API-accessible equivalent in HappyFox and must be rebuilt manually:
- Smart Match rules and routing logic — Gladly's intelligent routing does not export. Recreate as HappyFox Smart Rules.
- SLA policies — Different SLA models between platforms. Historical SLA performance data requires export to an external BI tool (Snowflake, BigQuery, Looker) before migration.
- CSAT survey configuration and scores — Survey workflows and historical CSAT scores do not transfer. Export Gladly CSAT data to a data warehouse for historical reporting before deprovisioning.
- Knowledge Base articles — Gladly Answers can be extracted via
GET /api/v1/answers, but HappyFox's public KB API is read-only. Articles must be recreated through the UI or a separate scripted pipeline using the admin interface. - Canned responses — Must be manually recreated as HappyFox Canned Actions.
- Sidekick (AI) configuration — Gladly's AI agent settings are platform-specific and have no HappyFox equivalent.
- Webhooks and integrations — All external integrations need to be reconnected to HappyFox endpoints.
Delta Sync for Zero-Downtime Cutover
Migrations take time. During extraction and load, your support team continues working in Gladly, creating new conversations and updating existing ones. To prevent data loss without a support blackout, execute a delta sync:
- Initial Load: Migrate all historical data up to a specific cutoff date (e.g., everything older than 7 days). Record the cutoff timestamp.
- Delta Extraction: Query Gladly for conversations created or modified since the cutoff using timestamp filters on the search API. Gladly webhooks can serve as delta triggers — they send small ID-based payloads, not full conversation data. Your webhook endpoint must respond within 15 seconds; failed deliveries retry on a fixed schedule and can eventually deactivate the webhook if consistently slow. Use webhooks as triggers to queue IDs for extraction, not as the sole data carrier.
- Upsert Logic: Query HappyFox by
gladly_conversation_idcustom field. If a match exists, append new items as updates. If no match, create a new ticket. Without the deduplication key described in the deduplication section above, this step is unreliable. - Final Cutover: On switch day, pause Gladly, run a final delta sync (which should take minutes if the deduplication logic is correct), then route all new traffic to HappyFox.
Common Failure Modes
1. Hitting the Gladly truncation cap without noticing. If you do not check Gladly-Limited-Data on every response, you deliver an incomplete migration and will not know it until an agent reports missing conversations.
2. HappyFox 429 lockout cascading. One aggressive burst triggers a 10-minute lockout. If your retry logic does not sleep the full 600 seconds, it retries immediately, triggers another lockout, and enters an infinite loop. The v1.1 API does not reliably return Retry-After — hardcode 600 seconds.
3. Category ID mismatch. HappyFox requires numeric Category IDs. If you map by name and a Category was renamed or recreated between your pre-migration setup and load day, tickets land in the wrong queue silently.
4. Missing mandatory custom fields. If the Category contains mandatory custom fields, omitting them from the bulk creation payload causes silent per-ticket failures — the ticket is not created, and you find out only by parsing the error array in the response.
5. Timezone misalignment. Gladly timestamps are UTC. If you do not enforce UTC normalization in your transformation layer, ticket history ordering breaks in any HappyFox instance configured for a non-UTC timezone.
6. Contact custom field resets. Partial contact updates reset all unincluded custom fields. Load contacts with the complete field set in a single pass.
7. Missing deduplication key. Running a delta sync without a stored external ID (the gladly_conversation_id custom field) creates duplicate tickets for every conversation touched since the initial load.
8. Cursor pagination gaps. If new customers are created in Gladly between two paginated extraction requests, they may be missed. Run a count reconciliation after extraction, not just after load.
Timeline Estimates by Dataset Size
| Dataset Size | Conversations | Estimated Duration | Recommended Approach |
|---|---|---|---|
| Small | < 5,000 | 5–8 business days | Self-serve with API scripting |
| Medium | 5,000–25,000 | 8–15 business days | Managed migration recommended |
| Large | 25,000–100,000 | 15–25 business days | Managed migration strongly recommended |
| Enterprise | 100,000+ | 25+ business days | Phased migration with dedicated support |
These estimates assume one engineer working full-time on extraction, transformation, loading, and validation. Add 2–3 days for heavy attachment volumes. Add 1–2 days if you have complex custom field structures or multi-brand setups. The largest variable is attachment volume — binary file downloads and re-uploads are the primary bottleneck at scale.
When to DIY vs. When to Get Help
Self-serve is viable when:
- You have fewer than 5,000 conversations
- Custom fields are simple (text, single-select dropdowns)
- You do not need attachment migration
- You have an engineer who can dedicate 60–100 hours
- You can tolerate a brief period of reduced support during cutover
- You can accept that per-reply timestamps will not be preserved
A managed migration makes sense when:
- Your dataset exceeds 10,000 conversations
- You need zero-downtime cutover with reliable delta sync
- You have complex custom field structures or multi-brand setups
- Attachment fidelity is non-negotiable
- You need the migration completed on a strict timeline
- Audit-grade timestamp preservation matters for compliance
Plan for the Data Model, Not Just the Data
The technical work in a Gladly to HappyFox migration is not the API calls — it is the structural translation. Gladly thinks in people; HappyFox thinks in tickets. Every decision you make during transformation — how to map Topics, what to do with multi-channel conversations, how to handle customers with 100+ conversations, how to preserve the deduplication key for delta sync, whether to accept timestamp limitations on replies — ripples through your entire support operation post-migration.
Get the mapping right before writing any code. Pre-create your deduplication custom field. Run a dry-run. Validate thoroughly. Budget 30% more time than you think you need. And keep an immutable copy of the original Gladly export regardless of which migration path you choose.
For related reading, see our guides on migrating Gladly to Zendesk, Gladly to Freshdesk, and the Gladly migration checklist.
Frequently Asked Questions
- Can I migrate from Gladly to HappyFox without coding?
- No. There is no native migration path or built-in import tool between Gladly and HappyFox. You must use both platforms' REST APIs to extract, transform, and load data. HappyFox's CSV ticket import only creates the first message as the ticket body and excludes attachments, so it is not suitable for a faithful migration.
- How long does a Gladly to HappyFox migration take?
- For a mid-size dataset of 5,000–25,000 conversations, expect 8–15 business days including extraction, transformation, loading, and validation. Smaller datasets under 5,000 conversations can be completed in 5–8 business days. Enterprise datasets over 100,000 conversations may take 25+ business days.
- Does HappyFox preserve original Gladly reply timestamps?
- Only partially. HappyFox supports a created_at field on ticket creation, but its documented staff_update, staff_pvtnote, and user_reply endpoints do not expose an equivalent backdating field for individual replies or notes. Store original timestamps in the note body or a custom field if audit-grade chronology matters.
- What data can't be migrated from Gladly to HappyFox?
- Smart Match rules, SLA policies, CSAT survey configuration, Sidekick AI settings, canned responses, Knowledge Base articles (HappyFox's KB API is read-only), webhook integrations, and routing logic cannot be migrated programmatically. These must be manually rebuilt in HappyFox.
- Does Gladly's API paginate conversation lists?
- No. Gladly's conversation list API returns at most 100 conversations per customer and 1,000 items per conversation, with no pagination. The Gladly-Limited-Data response header indicates if data was truncated. For complete extraction, use Gladly's file-based Export API.
