---
title: "HappyFox to Puzzel Case Management Migration: Technical Guide"
slug: happyfox-to-puzzel-case-management-migration-technical-guide
date: 2026-07-21
author: Nachi
categories: [Migration Guide, HappyFox]
excerpt: "Technical guide to migrating from HappyFox to Puzzel Case Management. Covers API constraints, data mapping, conversation history preservation, and ETL architecture."
tldr: HappyFox CSV exports lose conversation history. Use the REST API for full thread extraction and Puzzel's API ticket channels for import. Plan 2–4 weeks for a mid-size dataset.
canonical: https://clonepartner.com/blog/happyfox-to-puzzel-case-management-migration-technical-guide/
---

# HappyFox to Puzzel Case Management Migration: Technical Guide


# HappyFox to Puzzel Case Management Migration: Technical Guide

> [!NOTE]
> **TL;DR: HappyFox → Puzzel Case Management Migration**
>
> A HappyFox to Puzzel Case Management migration is a data-model redesign, not a CSV copy job. HappyFox's native CSV export strips staff replies, client replies, and private notes — you only get the initial message. You must use the HappyFox REST API (`/api/1.1/json/tickets/`) to extract full thread data. On the Puzzel side, tickets are created through API ticket channels with Basic Token or OAuth authentication. HappyFox Categories, Contact Groups, and Custom Fields do not map 1:1 to Puzzel's Teams, Categories, Forms, and Form Fields — each requires structural mapping decisions. Smart Rules, SLAs, and Canned Actions cannot be migrated programmatically. A typical mid-size migration (10,000–50,000 tickets) takes 2–4 weeks, broken down roughly as: 3–4 days planning and mapping, 3–5 days extraction, 3–5 days transformation and test loading, 2–3 days production loading and delta sync, and 2–3 days validation and cutover.

## What Is a HappyFox to Puzzel Case Management Migration?

A **HappyFox to Puzzel Case Management migration** moves tickets, contacts, contact groups, custom fields, tags, and attachments from HappyFox Help Desk into Puzzel Case Management while preserving conversation history, timestamps, and relational integrity.

<cite index="6-1">Puzzel Case Management is a Case/Ticket management tool that provides your contact centre or helpdesk with the ability to manage written interactions in a secure and efficient way.</cite> <cite index="6-2,6-3">Each email in the queue is converted into a support ticket and assigned a unique case/ticket ID which is then filtered, categorised and distributed to the right team or agent.</cite> It is part of the broader Puzzel CX ecosystem — designed primarily for contact centres that need unified ticketing across email, SMS, and API integrations.

Teams move from HappyFox to Puzzel for three main reasons:

1. **Contact centre consolidation.** <cite index="21-9">Puzzel Case Management can be combined with the Puzzel Contact Centre to offer a unified platform for ticketing and case management integrated into the contact centre solution.</cite> Teams already running Puzzel for voice want a single-pane agent experience.
2. **European data residency.** <cite index="29-33">Puzzel is ISO27001 certified (by DnV).</cite> Organisations in the Nordics or broader EU that need data sovereignty benefit from Puzzel's European-hosted infrastructure.
3. **Skill-based routing.** <cite index="22-5">Puzzel efficiently handles incoming support requests from email, SMS, and API integrations by categorising, prioritising, and routing them to agents based on their most relevant skills.</cite>

This is not a lift-and-shift job. The two platforms have fundamentally different data models, and treating the migration as a CSV exercise will result in lost conversation threads and broken relationships.

## Core Data Model Differences: HappyFox vs Puzzel

Before writing a single line of migration code, understand where the data models diverge:

| Concept | HappyFox | Puzzel Case Management |
|---|---|---|
| Ticket container | Category (hierarchical) | Team + Category (flat, separate concepts) |
| Customer grouping | Contact Group (with tagged domains) | Organisation (optional dimension) |
| Custom data on tickets | Ticket Custom Fields (text, dropdown, date, checkbox) | Forms + Form Fields (pre-defined field sets per ticket type) |
| Custom data on contacts | Contact Custom Fields | Organisation Custom Attributes |
| Agent grouping | Teams (within Categories) | Teams (standalone, mapped to queues) |
| Automation | Smart Rules, Canned Actions | Inbound Rules, Event Rules, Outbound Integrations |
| SLA tracking | Built-in SLA policies | SLA analysis via reporting |
| Knowledge base | Built-in KB with sections/articles | Customer Hub (additional paid feature) |
| Ticket linking | Ticket merging, parent tickets | Parent/child ticket relationships |
| Ticket statuses | New, In Progress, On Hold, Resolved, Closed (customisable) | Open, Pending, Solved, Closed (configurable per instance) |
| API authentication | API Key + Auth Code (Basic Auth) | Basic Token or OAuth Token |

<cite index="59-1">Puzzel Case Management uses placeholders like `{{organisation:name}}`, `{{category}}`, `{{form:name}}`, and `{{form_field:$FORM_FIELD_NAME}}`</cite> to reference ticket metadata — a fundamentally different structure from HappyFox's flat custom field model.

> [!WARNING]
> HappyFox "Categories" and Puzzel "Categories" are **not the same concept**. In HappyFox, a Category is the primary routing container (like a department). In Puzzel, Categories are metadata labels on tickets, while Teams handle routing. This naming collision causes mapping errors if you don't catch it early.

HappyFox also lets one contact belong to multiple contact groups and uses a **primary contact group** to default ticket affiliation. Puzzel uses **customer** and **organisation** records, where organisation is an optional dimension. A many-to-many contact-to-group pattern in HappyFox often must be flattened into a primary organisation in Puzzel, with secondary affiliations stored in custom attributes or an external reference system.

## Migration Approaches

### 1. Native CSV Export → Manual Import

**How it works:** Export tickets from HappyFox via Reports → Exports. Import contacts into Puzzel via CSV.

**When to use it:** Only for sub-1,000 ticket archives where conversation history is expendable.

<cite index="43-1,43-2">HappyFox's export includes the client's initial message and subject of the email. However, you will not be able to export the staff/client replies that are updated after the initial message and private notes of a ticket.</cite>

Puzzel's CSV import covers customers and address books, not threaded ticket history. CSV is useful for seeding reference data, not replaying case conversations.

- **Pros:** Zero engineering effort, fast for small datasets
- **Cons:** Loses all conversation history, no attachments, no relational data
- **Scalability:** Small datasets only (<1,000 tickets)
- **Complexity:** Low
- **Risk:** High — data loss is guaranteed for any team that needs thread history

### 2. API-Based Migration

**How it works:** Extract full ticket data including all updates (replies, notes, attachments) via the HappyFox REST API. Transform the data to match Puzzel's schema. Load into Puzzel through its API ticket channel.

<cite index="11-1">All HappyFox help-desk instances are shipped with a RESTful web service API that enables various operations, including ticket creation, ticket update submission, ticket and user listing.</cite> On the Puzzel side, <cite index="29-7">3rd party integrations are available via open REST-based APIs and an API is also available for customers to develop their own integrations.</cite>

- **Pros:** Full conversation history preserved, handles attachments, scriptable, supports reruns and deltas
- **Cons:** Requires custom development, must handle rate limits and pagination
- **Scalability:** Mid-size to enterprise datasets with proper batching
- **Complexity:** High
- **Risk:** Medium — needs proper error handling and retry logic

### 3. Custom ETL Pipeline

**How it works:** Build a three-stage pipeline: Extract from HappyFox API → Transform (clean, map, reshape) in a staging database → Load into Puzzel API. Typically Python or Node.js with PostgreSQL or SQLite for intermediate storage and validation.

- **Pros:** Full control over transformation logic, reusable for ongoing syncs, staging database enables rollback and audit trails
- **Cons:** Highest engineering investment (80–160 hours), requires maintenance
- **Scalability:** Best for 50,000+ ticket datasets or multi-system consolidations
- **Complexity:** High
- **Risk:** Low (if well-tested) — staging database provides a checkpoint for re-runs

### 4. Middleware Platforms (Zapier, Make)

**How it works:** Use Zapier or Make to connect HappyFox triggers/actions to Puzzel API endpoints. HappyFox supports webhooks and workflow HTTP actions. Puzzel documents outbound webhooks and JSONPath-based response mappings.

Middleware is useful for **delta sync** and **coexistence during phased cutovers**, not for bulk historical migration.

- **Pros:** Low-code, fast setup for ongoing sync
- **Cons:** Not designed for bulk historical migration, per-task pricing adds up fast, limited transformation capability, weak for ordered thread replay
- **Scalability:** Low for historical data (>5,000 records)
- **Complexity:** Low
- **Risk:** High for one-time migrations — timeout and rate limit issues at scale

### 5. Managed Migration Service

**How it works:** A migration partner handles extraction, transformation, loading, and validation end-to-end using pre-built connectors and tested pipelines.

- **Pros:** Zero internal engineering burden, handles edge cases, includes validation and rollback planning
- **Cons:** External cost, requires vendor coordination
- **Scalability:** Any dataset size
- **Complexity:** Low (for your team)
- **Risk:** Low — accountability sits with the partner

### Migration Approach Comparison

| Approach | History Preserved | Attachments | Scale | Complexity | Estimated Effort | Best For |
|---|---|---|---|---|---|---|
| CSV Export | ❌ No | ❌ No | <1K tickets | Low | 2–4 hours | Quick archival, contact seeding |
| API-Based | ✅ Yes | ✅ Yes | 1K–50K | High | 40–100 eng-hours | Engineering teams with capacity |
| Custom ETL | ✅ Yes | ✅ Yes | 50K+ | High | 80–160 eng-hours | Large, complex, audit-heavy datasets |
| Middleware | ⚠️ Forward-only | ⚠️ Limited | Event-driven | Low | 8–20 hours setup | Delta sync during coexistence |
| Managed Service | ✅ Yes | ✅ Yes | Any | Low (your side) | 2–8 hours coordination | Teams without bandwidth or zero-downtime needs |

**Recommendations by scenario:**

- **Small team, <5K tickets, simple fields:** API-based migration with a single script (40–60 hours)
- **Enterprise, 50K+ tickets, custom fields, zero downtime:** Custom ETL pipeline or managed service
- **Already on Puzzel Contact Centre, need ongoing sync:** Middleware for new tickets, API migration for historical
- **No engineering bandwidth:** Managed migration service

## When to Use a Managed Migration Service

Building an API migration script in-house often looks like a two-day project on a sprint board. In practice, it becomes a multi-week engineering distraction. Use a managed service when:

- Your dataset exceeds 20,000 tickets with conversation history
- You have custom fields that require complex transformation logic
- You need zero downtime during cutover
- Your team's engineering bandwidth is committed to product work
- You have compliance requirements (GDPR, ISO27001) that demand audit trails

**Hidden costs of DIY migration** that teams consistently underestimate:

- **Attachment re-hosting:** <cite index="11-20">HappyFox attachment URLs have an expiry time of 5 minutes.</cite> You must download every attachment during extraction and re-upload to Puzzel. At scale (e.g., 50,000 tickets averaging 2 attachments each = 100,000 file operations), this alone can take 2–3 days of continuous processing.
- **Rate limit management:** <cite index="45-1,45-2">HappyFox has a global rate limiter for API requests. Users who send many requests in quick succession may see error responses with HTTP status code 429.</cite> HappyFox allows roughly 500 GETs and 300 POSTs per minute; a breach can lock you out for up to 10 minutes. ([support.happyfox.com](https://support.happyfox.com/kb/article/1039-tickets-endpoint/?section_id=131))
- **Retry logic and idempotency:** Failed API calls need to be retried without creating duplicate tickets. If you migrate a ticket before the associated contact exists in Puzzel, the ticket becomes orphaned. A failed migration with 30,000 tickets and no idempotency safeguards typically costs 40–80 hours of rework to identify and fix duplicates, orphaned records, and ordering errors — plus 3–5 days of agent downtime while the data is corrected.
- **Timestamp handling:** If you fail to preserve historical timestamps, every ticket appears as if created on migration day, destroying SLA reporting and making historical analysis meaningless.
- **Validation:** Field-level comparison across 10,000+ records takes more time than the migration itself — expect 16–24 hours of engineer time for thorough validation of a 20K-ticket dataset.

## Pre-Migration Planning

Before extracting a single record, complete this audit.

### Data Inventory

- [ ] **Tickets:** Total count, date range, status distribution (open, pending, closed)
- [ ] **Contacts:** Total count, contact groups, custom fields in use, duplicate emails
- [ ] **Contact Groups:** Count, tagged domains, membership overlap, primary group usage
- [ ] **Categories:** Full hierarchy, active vs archived
- [ ] **Custom Fields:** Ticket custom fields, contact custom fields, data types
- [ ] **Knowledge Base:** Article count, sections, inline images, attachments
- [ ] **Attachments:** Total volume (GB), file types, average size per ticket
- [ ] **Smart Rules / Automations:** Document all — these must be rebuilt manually
- [ ] **SLAs:** Document policies for manual recreation in Puzzel
- [ ] **Canned Actions:** Export templates for rebuilding

### What to Leave Behind

Not everything needs to move:

- Spam and test tickets
- Contacts with no ticket history
- Archived categories with zero active tickets
- Duplicate contacts (merge before migration)
- Tickets older than your compliance retention window
- Unused custom fields — do not migrate garbage data into a clean Puzzel instance

See our [Help Desk Data Migration Playbook](https://clonepartner.com/blog/blog/help-desk-data-migration-playbook/) for detailed guidance on what to move and what to leave behind.

### Migration Strategy

| Strategy | Description | Best For | Typical Duration |
|---|---|---|---|
| **Big Bang** | Full cutover in a single maintenance window | <20K tickets, weekend migration | 1–2 days |
| **Phased** | Migrate by category/team over multiple windows | Large orgs with independent departments | 2–4 weeks |
| **Incremental** | Migrate historical data first, then sync delta | Zero-downtime requirements | 1–3 weeks |

We recommend a Big Bang cutover preceded by a delta migration strategy. Migrate the bulk of historical data first, then run a final delta pass to catch records created or modified during the initial extraction.

### Migration Timeline Breakdown (Mid-Size: 10K–50K Tickets)

| Phase | Duration | Key Deliverables |
|---|---|---|
| **Planning & Mapping** | 3–4 days | Data inventory, field mapping document, Puzzel instance configuration |
| **Extraction** | 3–5 days | Full HappyFox API export, attachment downloads, staging DB populated |
| **Transformation & Test Load** | 3–5 days | Data cleaned and reshaped, sandbox migration executed, mapping errors fixed |
| **Production Load** | 2–3 days | Production import, delta sync, attachment upload |
| **Validation & Cutover** | 2–3 days | Record count verification, UAT, email routing switch, monitoring |
| **Total** | **2–4 weeks** | |

### Risk Mitigation

- Take a full backup of HappyFox data before starting (API export to local storage)
- Run at least two test migrations on a Puzzel sandbox (contact Puzzel support to provision a sandbox environment — this typically requires an active Puzzel license and a support request to enable a non-production instance)
- Document a rollback plan: keep HappyFox active until validation passes
- Assign a dedicated QA owner for post-migration validation

For a complete planning framework, see our [help desk data migration checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-checklist/).

## Data Mapping: HappyFox → Puzzel Case Management

This section makes or breaks the migration. Every object requires explicit mapping decisions.

> [!WARNING]
> If your brief mentions **Accounts**, **Leads**, **Opportunities**, or **Pipelines**, pause. HappyFox Help Desk is built around contacts, contact groups, tickets, categories, and custom fields. Puzzel's model is customers, organisations, ticket attributes, categories, forms, and form fields. Sales objects belong in a CRM, not in this migration.

### Ticket Mapping

| HappyFox Field | Puzzel Case Management Field | Notes |
|---|---|---|
| Ticket ID (display ID) | Ticket Reference / `external_reference_id` | Puzzel assigns its own IDs; store HappyFox ID in a custom field for traceability |
| Subject | Subject | Direct map |
| Category | Team + Category | Split: Team handles routing, Category handles classification |
| Status | Status | Map HappyFox statuses → Puzzel statuses (see status mapping table below) |
| Priority | Priority | Direct map (verify priority level names match) |
| Assignee | Assigned User | Map agent emails; agents must exist in Puzzel first |
| Tags | Tags | Direct map |
| Custom Fields | Form Fields | Requires creating matching Forms in Puzzel first |
| `created_at` | `created_at` | Preserve historical timestamp via API |
| `text` (initial message) | Description | HTML/Text |

#### Status Mapping Reference

| HappyFox Status | Suggested Puzzel Status | Notes |
|---|---|---|
| New | Open | Default initial state |
| In Progress | Open | Puzzel uses assignment + Open rather than a separate "In Progress" |
| On Hold | Pending | Indicates waiting on external input |
| Resolved | Solved | Agent considers issue resolved |
| Closed | Closed | Ticket fully closed |
| Custom statuses | Map to nearest equivalent | Document each custom status decision |

Note: Puzzel status names are configurable per instance. Verify your instance's actual status values in Settings → Ticket Statuses before finalising the mapping.

### Contact / Organisation Mapping

| HappyFox Field | Puzzel Case Management Field | Notes |
|---|---|---|
| Contact Name | Customer Name | Direct map |
| Contact Email | Customer Email | Primary identifier for ticket association; normalize to lowercase |
| Contact Phone | Customer Phone | May need custom attribute in Puzzel |
| Contact Group | Organisation | <cite index="55-2,55-3">A Puzzel customer can optionally use the dimension 'organisations'. If used, you will find the name and description for each organisation record.</cite> |
| Contact Custom Fields | Organisation Custom Attributes | Verify data type compatibility |

### Conversation Thread Mapping

| HappyFox Field | Puzzel Case Management Field | Notes |
|---|---|---|
| Staff Reply | Outbound message / Note | <cite index="5-6,5-7">In Puzzel, notes can be flagged as public or private. When a note has been marked as private, it will not be exposed when retrieving ticket content via an API channel.</cite> |
| Client Reply | Inbound message | Chronological order must be preserved |
| Private Note | Private Note | Must set private flag correctly |
| Attachments | Attachments | Re-upload required; HappyFox URLs expire in 5 minutes |

> [!CAUTION]
> **Attachment URL Expiry:** HappyFox attachment URLs expire after 5 minutes. Your extraction script must download each attachment immediately upon retrieval. Do not store URLs for later download — they will be dead links by the time you use them.

### Custom Field → Form Field Mapping

HappyFox uses flat custom fields on tickets. <cite index="62-1">In Puzzel, a ticket form is a set of predefined ticket fields that serves a specific support request.</cite> You need to:

1. Group related HappyFox custom fields into logical Puzzel Forms (e.g., group "Order Number," "Product SKU," and "Purchase Date" into an "Order Support" form)
2. Create those Forms in Puzzel admin before import — **forms cannot be created via API**
3. Map each HappyFox custom field to a specific Form Field
4. Handle data type conversions (HappyFox dropdown → Puzzel dropdown — verify that every option value matches exactly, or the API POST will fail silently or reject the record)

## Migration Architecture: Extract → Transform → Load

### Phase 1: Extract from HappyFox

<cite index="11-1,11-2">The HappyFox API is a RESTful web service that supports JSON and multipart/form-data payload formats.</cite> Authentication uses HTTP Basic Auth with API Key and Auth Code.

**Key extraction endpoints:**

```
GET /api/1.1/json/tickets/?size=50&page=1          # Paginated ticket list
GET /api/1.1/json/ticket/<ticket_id>/               # Single ticket with updates
GET /api/1.1/json/users/?size=50&page=1              # Contact list
GET /api/1.1/json/contact_groups/                    # Contact groups
GET /api/1.1/json/categories/                        # Category list
GET /api/1.1/json/custom_fields/                     # Ticket custom fields
```

**Extraction constraints:**

- <cite index="11-24">Concurrent/parallel API calls to the ticket endpoint for any given particular ticket are not supported.</cite>
- Page size maximum is 50 for ticket and report endpoints
- Rate limiting returns HTTP 429 — implement exponential backoff
- Attachment URLs inside ticket updates expire after 5 minutes — download immediately during extraction
- EU-hosted HappyFox accounts use `.happyfox.net` — ensure your API base URL is correct

```python
import requests
import time
import json
import os

HF_BASE = "https://yourcompany.happyfox.com"
HF_API_KEY = "your_api_key"
HF_AUTH_CODE = "your_auth_code"

def extract_tickets(page=1, size=50):
    """Extract tickets with full conversation history."""
    url = f"{HF_BASE}/api/1.1/json/tickets/"
    params = {"size": size, "page": page}
    response = requests.get(url, auth=(HF_API_KEY, HF_AUTH_CODE), params=params)
    
    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", 60))
        time.sleep(retry_after)
        return extract_tickets(page, size)
    
    response.raise_for_status()
    return response.json()

def extract_ticket_detail(ticket_id):
    """Get single ticket with all updates, attachments, and custom fields."""
    url = f"{HF_BASE}/api/1.1/json/ticket/{ticket_id}/"
    response = requests.get(url, auth=(HF_API_KEY, HF_AUTH_CODE))
    response.raise_for_status()
    return response.json()

def download_attachment(attachment_url, local_path):
    """Download attachment immediately — URLs expire in 5 minutes."""
    response = requests.get(attachment_url, stream=True)
    response.raise_for_status()
    os.makedirs(os.path.dirname(local_path), exist_ok=True)
    with open(local_path, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)
```

Production scripts need significantly more error handling, logging, and asynchronous processing to handle attachments and relational dependencies at scale. The code above outlines the basic pagination and rate-limit handling pattern.

### Phase 2: Transform

Store extracted data in a staging database before loading into Puzzel. This gives you a checkpoint for validation and re-runs without hitting the HappyFox API again.

**Key transformations:**

1. **Status mapping:** Map HappyFox statuses to Puzzel equivalents using the translation dictionary defined in your mapping document
2. **Category → Team + Category split:** Decide which HappyFox Categories become Puzzel Teams and which become Puzzel Categories
3. **Custom fields → Forms:** Group HappyFox custom fields into Puzzel Form structures
4. **Contact Groups → Organisations:** Map domain-tagged groups to Puzzel Organisations; flatten multi-group logic where needed
5. **Timestamp normalization:** Ensure UTC consistency across all records; HappyFox returns timestamps in the account's configured timezone
6. **HTML sanitization:** Clean ticket body HTML for Puzzel's editor compatibility — watch for inline images stored as base64 or authenticated URL references (see Edge Cases section)
7. **Deduplication:** Merge duplicate contacts before import
8. **Legacy ID preservation:** Store HappyFox IDs in immutable custom fields for traceability

```python
STATUS_MAPPING = {
    "New": "Open",
    "In Progress": "Open",
    "On Hold": "Pending",
    "Resolved": "Solved",
    "Closed": "Closed",
}

def transform_ticket(hf_ticket, field_mapping):
    """Transform a HappyFox ticket into Puzzel-compatible format."""
    puzzel_ticket = {
        "subject": hf_ticket["subject"],
        "status": STATUS_MAPPING.get(hf_ticket["status"]["name"], "Open"),
        "priority": hf_ticket["priority"]["name"],
        "team": field_mapping["categories"].get(hf_ticket["category"]["id"]),
        "customer_email": hf_ticket["user"]["email"].lower().strip(),
        "tags": [tag["name"] for tag in hf_ticket.get("tags", [])],
        "created_at": hf_ticket["created_at"],
        "external_reference_id": str(hf_ticket["id"]),
        "custom_fields": {},
        "messages": []
    }
    
    # Map custom fields to Puzzel form fields
    for cf in hf_ticket.get("custom_fields", []):
        puzzel_field = field_mapping["custom_fields"].get(cf["name"])
        if puzzel_field:
            puzzel_ticket["custom_fields"][puzzel_field] = cf["value"]
    
    # Preserve conversation thread in chronological order
    for update in sorted(hf_ticket.get("updates", []), key=lambda u: u["timestamp"]):
        puzzel_ticket["messages"].append({
            "type": classify_message_type(update),
            "body": update.get("message", ""),
            "timestamp": update["timestamp"],
            "is_private": update.get("message_type") == "private_note",
            "attachments": extract_attachment_refs(update)
        })
    
    return puzzel_ticket
```

### Phase 3: Load into Puzzel Case Management

<cite index="7-1,7-2,7-3,7-4,7-5,7-6">In Puzzel, you create an API ticket channel by going to Settings → Ticket Channels → API, selecting an authentication level (Global or per-Organisation), and choosing a Token Type (Basic Token or OAuth Token).</cite>

**Loading sequence matters.** You must create dependencies before the records that reference them:

1. **Organisations** — Create Puzzel Organisations from HappyFox Contact Groups
2. **Users/Agents** — Provision agents in Puzzel ID (manual or SCIM API)
3. **Categories and Forms** — Create via Puzzel admin interface (forms cannot be created programmatically)
4. **Customers** — Create customer records with email, name, and organisation association
5. **Tickets** — Create via API ticket channel with all metadata; override the default `created_at` timestamp with the historical value from HappyFox
6. **Ticket Messages** — Add conversation thread entries in chronological order
7. **Attachments** — Upload to each ticket/message

**Puzzel API ticket creation example (pseudocode):**

```python
import requests

PUZZEL_BASE = "https://app.puzzel.com/ticketing/api/v1"  # Verify with your instance
PUZZEL_TOKEN = "your_basic_token_or_oauth_token"

def create_puzzel_ticket(puzzel_ticket):
    """Create a ticket via Puzzel API ticket channel.
    
    Note: Actual endpoint URL and payload schema depend on your 
    Puzzel instance configuration. Verify with Puzzel support.
    The API ticket channel must be created in Settings → Ticket Channels → API
    before this will work.
    """
    headers = {
        "Authorization": f"Bearer {PUZZEL_TOKEN}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "subject": puzzel_ticket["subject"],
        "status": puzzel_ticket["status"],
        "priority": puzzel_ticket["priority"],
        "team_id": puzzel_ticket["team"],
        "customer_email": puzzel_ticket["customer_email"],
        "external_reference_id": puzzel_ticket["external_reference_id"],
        "created_at": puzzel_ticket["created_at"],  # Historical timestamp
        "tags": puzzel_ticket["tags"],
        "form_fields": puzzel_ticket["custom_fields"],
        "description": puzzel_ticket["messages"][0]["body"] if puzzel_ticket["messages"] else ""
    }
    
    response = requests.post(
        f"{PUZZEL_BASE}/tickets",
        headers=headers,
        json=payload,
        timeout=30
    )
    
    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", 30))
        time.sleep(retry_after)
        return create_puzzel_ticket(puzzel_ticket)
    
    if response.status_code == 422:
        # Validation error — log payload for debugging
        log_error(f"Validation failed for ticket {puzzel_ticket['external_reference_id']}: {response.text}")
        return None
    
    response.raise_for_status()
    return response.json()  # Contains the new Puzzel ticket ID

def add_ticket_message(puzzel_ticket_id, message):
    """Add a conversation message to an existing Puzzel ticket."""
    headers = {
        "Authorization": f"Bearer {PUZZEL_TOKEN}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "body": message["body"],
        "is_private": message["is_private"],
        "created_at": message["timestamp"],
        "type": message["type"]  # "inbound" or "outbound" or "note"
    }
    
    response = requests.post(
        f"{PUZZEL_BASE}/tickets/{puzzel_ticket_id}/messages",
        headers=headers,
        json=payload,
        timeout=30
    )
    
    response.raise_for_status()
    return response.json()
```

> [!WARNING]
> **Important:** The Puzzel API endpoint URLs and payload schemas shown above are illustrative. Puzzel does not publish comprehensive public API documentation in the same way HappyFox does. You must: (1) create your API ticket channel in Settings → Ticket Channels → API, (2) contact Puzzel support to obtain your instance-specific API documentation and endpoint URLs, and (3) test against a sandbox before production use. The authentication method (Basic Token vs OAuth) and exact field names depend on your instance configuration.

**Error handling patterns for the Load phase:**

| Error | Cause | Resolution |
|---|---|---|
| HTTP 429 | Rate limit exceeded | Exponential backoff; respect `Retry-After` header |
| HTTP 422 | Validation failure (missing required field, invalid enum value) | Log payload, fix mapping, retry |
| HTTP 404 | Referenced team/category/customer doesn't exist | Check dependency creation order; create missing dependency first |
| HTTP 409 | Duplicate record (if idempotency key is set) | Skip — record already exists |
| HTTP 500 | Server error | Retry with backoff; if persistent, contact Puzzel support |
| Timeout | Large attachment or slow network | Retry with longer timeout; chunk large files |

**Idempotency strategy:** Use the HappyFox ticket ID stored in `external_reference_id` as an idempotency key. Before creating a ticket, query Puzzel for existing tickets with that reference. If found, skip creation and proceed to message/attachment sync.

Keep crosswalk tables for every dependency: group → organisation, contact → customer, ticket → case. Make loaders idempotent and serialize writes per ticket so thread order stays stable.

> [!WARNING]
> **Puzzel API rate limits are not publicly documented** as a fixed number. Contact Puzzel support before running a bulk migration to understand your instance's throughput limits and request a temporary increase. <cite index="33-10">The 'Outbound Integrations', 'Event Rules', and 'Response Mappings' features require activation on your Puzzel Case Management instance before use.</cite> Confirm API access is enabled before development starts.

### Delta Sync

Because the initial extraction takes time, tickets will be created or updated in HappyFox during the migration window. A delta sync captures only records modified since the initial extraction timestamp and updates Puzzel accordingly. This is the mechanism that allows a near-zero-downtime cutover:

1. Run the bulk migration (historical data)
2. Run the delta sync (records created/modified during bulk migration)
3. Freeze HappyFox (set to read-only or disable inbound email)
4. Run one final delta sync
5. Validate record counts and spot-check data
6. Switch mail routing to Puzzel

Use HappyFox's `updated_after` filter parameter on the tickets endpoint to retrieve only modified records:

```
GET /api/1.1/json/tickets/?size=50&page=1&updated_after=2024-01-15T12:00:00Z
```

## Step-by-Step Migration Process

1. **Audit** — Inventory all HappyFox data: tickets, contacts, groups, custom fields, KB articles, automations
2. **Map** — Create a field-level mapping document (HappyFox → Puzzel) for every object type
3. **Configure Puzzel** — Set up Teams, Categories, Forms, Form Fields, Organisations, and API ticket channels; provision agents; request API access and rate limit increase from Puzzel support
4. **Extract** — Pull all data from HappyFox via REST API; download all attachments immediately
5. **Transform** — Clean, reshape, and store in staging database
6. **Test Load** — Import into Puzzel sandbox; validate record counts and field accuracy
7. **Fix** — Correct mapping errors found in test; re-run
8. **Production Load** — Import into production Puzzel instance in dependency order
9. **Delta Sync** — Capture tickets created or updated since initial extraction; apply to Puzzel
10. **Validate** — Record counts, field-level spot checks, UAT with agents
11. **Cutover** — Freeze HappyFox (set to read-only), switch email routing to Puzzel
12. **Monitor** — Watch for 2 weeks; keep HappyFox read-only as fallback

## Edge Cases and Challenges

### Inline Images

> [!WARNING]
> **Beware of Inline Images**
> HappyFox allows users to paste images directly into the ticket body. These are typically stored in one of three ways: (1) base64-encoded data URIs in the HTML, (2) authenticated URL references pointing to HappyFox's CDN, or (3) external image URLs. Types 1 and 2 require parsing and re-hosting: extract the image data, upload it as an attachment or to your own CDN, and replace the HTML `src` attribute with the new URL. Type 2 URLs will break after HappyFox deprovisioning, and type 1 URLs dramatically inflate ticket body size. If you migrate the raw HTML to Puzzel without parsing and re-hosting these images, they will appear as broken image icons to your agents.

### Duplicate Contacts

HappyFox allows multiple contacts with the same email across different Contact Groups. Puzzel may not handle this gracefully. Deduplicate before import:

- Choose the most recently active contact as the primary
- Merge ticket histories onto the primary contact
- Log all merge decisions for audit

### Missing or Deactivated Authors

If a staff member has left the company, their HappyFox profile might be deactivated. When you migrate their historical ticket replies to Puzzel, the API may reject the interaction if the author ID does not correspond to an active Puzzel agent. Map deactivated HappyFox agents to a generic "Legacy Agent" profile in Puzzel to preserve the history. Create this profile before starting the migration, and add a note or custom field on affected messages indicating the original author name.

### Multi-Level Ticket Relationships

<cite index="55-4,55-8,55-9">Tickets in Puzzel can be linked together through a parent/child relationship. When done, such relations are shown in a dedicated table.</cite> If HappyFox tickets have parent/child or merged relationships:

1. Import parent tickets first
2. Record the new Puzzel ticket IDs in your crosswalk table
3. Link child tickets to parent IDs in a second pass
4. For merged tickets, decide whether to maintain the merge relationship or consolidate into a single ticket with all messages

### Attachment Failures

- Download from HappyFox immediately (5-minute URL expiry)
- Store locally with ticket ID mapping (directory structure: `./attachments/{ticket_id}/{filename}`)
- Re-upload to Puzzel after ticket creation
- Verify file size limits — Puzzel rejects invalid Base64, restricted file types, and files exceeding size limits; large emails over 40MB can fail into ERROR status
- Implement a retry queue for failed uploads — network interruptions during large file transfers are common

### Multi-Group Contact Flattening

A HappyFox contact can belong to several contact groups with one designated as primary. Decide early which approach to take:

| Approach | Pros | Cons |
|---|---|---|
| Flatten to primary organisation | Simple, clean data | Loses secondary affiliations |
| Create duplicate customer records | Preserves all affiliations | May cause confusion; tickets split across records |
| Store secondary affiliations in custom attributes | Preserves data without duplication | Requires custom field setup; not searchable as organisations |

### API Throttling

Both platforms enforce rate limits. HappyFox allows roughly 500 GETs and 300 POSTs per minute. If you hit HappyFox with too many concurrent requests, you will receive 429 errors and may be locked out for up to 10 minutes. Your migration script must implement exponential backoff, respect the `Retry-After` header, and log every throttled request for replay.

## Limitations and Constraints

### Puzzel-Side Constraints

- **No public bulk import API:** Puzzel does not document a bulk ticket import endpoint. You create tickets one at a time through the API channel, which limits throughput. Expect approximately 1–3 tickets per second depending on message count and attachments per ticket.
- **Forms must pre-exist:** <cite index="62-1">Ticket forms are predefined sets of ticket fields.</cite> You cannot create forms programmatically — they must be configured in the admin UI before import.
- **Feature activation required:** <cite index="33-10">The 'Outbound Integrations', 'Event Rules', and 'Response Mappings' features require activation</cite> — contact Puzzel support and request enablement before development begins.
- **Organisations are optional:** Not all Puzzel instances use Organisations. If yours doesn't, Contact Group data from HappyFox needs a different structure.
- **Knowledge base is separate:** <cite index="29-6">Customer Hub is an additional paid-for feature in Puzzel</cite> that can serve as a self-service portal. KB migration requires a separate workstream — see the Knowledge Base Migration section below.
- **API documentation is instance-specific:** Unlike HappyFox's publicly documented API, Puzzel's API documentation may need to be obtained through support. Request it early in the planning phase.

### HappyFox-Side Constraints

- **CSV export loses history:** Only the initial message exports. Staff replies, client replies, and private notes require API extraction.
- **Attachment URLs expire in 5 minutes:** Cannot batch-download later.
- **No parallel ticket-level API calls:** <cite index="11-24">Concurrent/parallel API calls to the ticket endpoint for any given particular ticket are not supported.</cite>
- **Global rate limiter:** Exceeding the rate limit returns 429 errors and can lock out API access for up to 10 minutes.
- **EU-hosted accounts use `.happyfox.net`:** Ensure your API base URL is correct.

### Data Structure Compromises

- HappyFox's flat custom fields become nested Form/Form Field structures in Puzzel — data reshaping is unavoidable
- HappyFox ticket categories as routing containers have no direct Puzzel equivalent — you must split into Team + Category
- Satisfaction survey data from HappyFox has no standard import path into Puzzel
- Automations, macros, SLAs, and routing rules cannot be migrated programmatically — these must be manually documented and rebuilt in Puzzel using Inbound Rules, Event Rules, and response templates
- Complex historical metrics (like time-in-status) rarely translate perfectly between vendor schemas, even when timestamps are backdated correctly

### Knowledge Base Migration

If you use HappyFox's built-in KB and plan to move to Puzzel's Customer Hub:

- <cite index="29-6">Customer Hub is an additional paid-for feature</cite> — confirm licensing before planning this workstream
- HappyFox KB articles are organized in Sections → Articles with rich HTML content
- Extract articles via the HappyFox API: `GET /api/1.1/json/kb/sections/` and `GET /api/1.1/json/kb/articles/`
- HTML sanitization is critical: strip HappyFox-specific CSS classes, re-host inline images, convert internal links to new Customer Hub URLs
- Customer Hub article creation may require manual configuration or a separate API — confirm with Puzzel support
- Budget this as a separate 1–2 week workstream depending on article count

## Validation and Testing

### Record Count Comparison

| Object | HappyFox Count | Puzzel Count | Match? |
|---|---|---|---|
| Tickets | _____ | _____ | ☐ |
| Contacts/Customers | _____ | _____ | ☐ |
| Organisations | _____ | _____ | ☐ |
| Attachments | _____ | _____ | ☐ |
| Tags | _____ | _____ | ☐ |
| Categories/Teams | _____ | _____ | ☐ |
| Messages (total across all tickets) | _____ | _____ | ☐ |

### Field-Level Validation

- Sample at least 5% of migrated tickets (minimum 50–100 records)
- Compare: subject, status, priority, assignee, customer email, tags, custom fields
- Verify conversation thread completeness: count messages per ticket in source vs target
- For complex tickets (10+ replies, multiple attachments, custom fields), manually verify formatting, inline images, and chronological order in the Puzzel UI
- Check attachment accessibility: open 10 random attachments in Puzzel
- Confirm that Closed tickets in HappyFox remain Closed in Puzzel, and Open tickets remain Open
- Verify that `created_at` timestamps match the historical values, not the migration date

### UAT Process

1. Select 3–5 agents from different teams
2. Have each agent review 20 migrated tickets they previously handled
3. Verify: correct conversation order, all notes present (including private notes), attachments accessible, inline images rendering
4. Test: Can agents reply to migrated tickets? Do auto-replies work on the new channels?
5. Document all discrepancies before production cutover

For a detailed post-migration QA process, see our [Post-Migration QA Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/).

### Rollback Plan

- Keep HappyFox active and read-only during validation — do not cancel the subscription
- Document a cutover checklist with explicit go/no-go criteria
- If validation fails: pause, fix transformation logic, re-run on sandbox
- Set a hard deadline: if validation isn't complete within 5 business days, roll back to HappyFox

## Post-Migration Tasks

### Rebuild Automations

HappyFox Smart Rules, Canned Actions, and SLA policies cannot be exported. Rebuild them manually in Puzzel:

- **Inbound Rules:** Replace HappyFox Smart Rules that auto-assign or auto-tag
- **Event Rules:** Replicate escalation triggers
- **Response Templates:** Recreate Canned Actions as pre-written response templates
- **SLA configuration:** Set up SLA policies in Puzzel's reporting framework

### Update Email Routing

Change your DNS and mail forwarding rules to route `support@yourdomain.com` to Puzzel instead of HappyFox. Steps:

1. Update MX records or email forwarding rules to point to your Puzzel inbound email address
2. Verify SPF/DKIM/DMARC records include Puzzel's sending domains
3. Test inbound email delivery to Puzzel before deactivating HappyFox's email intake
4. Monitor for bounce-backs during the first 24 hours

### Agent Training

- Puzzel's agent interface differs significantly from HappyFox — budget 2–4 hours of training per agent
- <cite index="25-14">Agents provisioned via Puzzel ID are created with standard 'Basic User' System Role credentials and no user signature initially applied.</cite> Configure signatures and roles before go-live.
- Ensure your team understands the new terminology: "Cases" and "Teams" instead of "Tickets" and "Categories"
- Test the Puzzel Contact Centre integration if your team uses both voice and ticketing

### Monitoring

- Track ticket creation rates in Puzzel for the first 2 weeks post-cutover
- Monitor for orphaned tickets (tickets without customer association)
- Check that inbound email routing is working correctly on the new Puzzel channels
- Watch for missing attachments, stale search visibility, and unresolved mapping gaps
- Verify CSAT survey configuration if applicable
- Compare daily ticket volumes between the final week in HappyFox and the first week in Puzzel to ensure no emails are being lost

## Best Practices

1. **Run a sandbox migration first.** Execute a full test migration into a Puzzel sandbox. The first run always reveals mapping errors.
2. **Do not mutate source data.** Treat HappyFox as a read-only data source. Never run scripts that alter HappyFox data during migration — you need a pristine fallback.
3. **Back up everything.** Export all HappyFox data via API to local storage before every full run. This is your safety net.
4. **Validate incrementally.** Check counts and sample records after each object type loads. Don't wait until the end to discover errors.
5. **Document every mapping decision.** Maintain a living spreadsheet of HappyFox field → Puzzel field mappings. This becomes your audit trail.
6. **Coordinate with Puzzel support.** Request API rate limit increases for migration windows. Confirm feature enablement and obtain API documentation before writing any code.
7. **Communicate the freeze.** Give your support team a clear timeline for the freeze period where they stop working in HappyFox and switch to Puzzel.
8. **Use middleware for deltas, not for historical replay.** Middleware handles forward-looking sync during a phased cutover. It does not handle years of threaded ticket history.
9. **Plan for the knowledge base separately.** If you use HappyFox KB, Puzzel's Customer Hub is a separate product with its own migration considerations and costs.
10. **Test the full agent workflow post-migration.** Don't just validate data — verify that agents can search, reply, reassign, and close migrated tickets in Puzzel without issues.

## Making the Right Call

A HappyFox to Puzzel Case Management migration is a medium-complexity helpdesk migration. The biggest technical risk is conversation history loss — guaranteed if you rely on CSV exports. The biggest structural risk is the data model mismatch between HappyFox Categories and Puzzel's Team + Category + Form architecture.

Teams with fewer than 5,000 tickets and simple custom fields can self-serve with API scripting in 60–100 engineer-hours. For larger datasets, complex custom field mappings, or zero-downtime requirements, the engineering math tilts toward a managed migration.

If you're evaluating this migration and want to understand the exact scope for your dataset, we can review your HappyFox data model and give you a realistic timeline and risk assessment in a 30-minute call.

> Planning a HappyFox to Puzzel Case Management migration? Our engineering team has handled complex HappyFox migrations — including conversation history preservation, custom field transformations, and attachment re-hosting. Book a free scoping call to get a realistic timeline for your specific dataset.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I export conversation history from HappyFox via CSV?

No. HappyFox's native CSV export only includes the initial message and subject. Staff replies, client replies, and private notes are excluded. You must use the HappyFox REST API (/api/1.1/json/ticket/<id>/) to extract full conversation threads.

### How long does a HappyFox to Puzzel Case Management migration take?

A standard migration with 10,000–50,000 tickets takes 2–4 weeks, including data mapping, test migrations, and validation. Smaller datasets under 5,000 tickets can be completed in 1–2 weeks with dedicated engineering effort.

### Do HappyFox Categories map directly to Puzzel Categories?

No. Despite the shared name, they serve different purposes. HappyFox Categories are routing containers (like departments). Puzzel Categories are metadata labels on tickets, while Teams handle routing. You need to split each HappyFox Category into a Puzzel Team and one or more Puzzel Categories.

### Can HappyFox Smart Rules and SLAs be migrated to Puzzel automatically?

No. HappyFox Smart Rules, SLA policies, Canned Actions, and satisfaction surveys cannot be exported or migrated programmatically. They must be manually documented and rebuilt in Puzzel using Inbound Rules, Event Rules, and response templates.

### Does Puzzel Case Management have a bulk ticket import API?

Puzzel does not publicly document a dedicated bulk import endpoint. Tickets are created one at a time through API ticket channels. Contact Puzzel support to discuss throughput limits and request temporary rate limit increases for migration workloads.
