Migrating from your previous ticketing platform to Tines Cases is a straightforward project when you break it into manageable steps.
This is part two of our Tines Cases guide and walks through those steps and provides practical advice on how to avoid common pitfalls, keep your migration on schedule, and end up with a well-structured Cases environment from day one.
API authentication and access to your legacy system
Before you can move anything, you need reliable, well-scoped API access to your existing ticketing system. This sounds straightforward, but authentication and access issues are one of the most common sources of early delays in migration projects. Getting this right up front saves you from debugging mysterious failures weeks into the process.
Choose the right authentication method
Generally speaking, OAuth 2.0 (the standard for the credentials flow) is our preferred method for server-to-server integrations. It provides token-based access with automatic expiration, which is both more secure and more manageable than static credentials.
Basic authentication works in many systems but is increasingly deprecated or disabled by default, so check your platform’s current guidance before building around it. Some organizations have security policies that explicitly prohibit basic authentication for API integrations, so you don’t want to discover this after you’ve already built your stories.
If your legacy platform supports API key authentication, that’s a reasonable middle ground for systems that don’t offer full OAuth support. The key should be stored securely in Tines’ credential management and never hardcoded in stories, with a rotation schedule that aligns with your company’s policies.
Create a dedicated integration service account with the minimum permissions required for the migration. Avoid piggybacking on a named user’s account, as every API action will appear in logs as though that person performed it, creating audit confusion. It also risks breaking the integration when that person changes roles, goes on leave, or departs the organization entirely. A dedicated service account also makes it easier to scope permissions precisely and revoke access cleanly when the migration is complete.
Confirm network-level access
If your legacy platform is hosted on-premise or behind a firewall, confirm whether Tines cloud egress IPs need to be allowlisted. This step typically involves your network or infrastructure team and can take days or weeks depending on your organization’s change management process, so start early.
For cloud-hosted legacy systems, network access is usually simpler, but you should still confirm that your instance doesn’t enforce IP allowlisting or geo-based restrictions that could block Tines’ requests.
Service account permissions
The integration account will need read access (and in some cases write access) to the relevant ticket tables, comment or activity logs, attachment stores, and any custom fields or objects your team uses. The exact permission model varies by platform — some use role-based access, others use granular permission sets, and some use a combination.
Watch out for hidden field-level restrictions. Many ticketing platforms support fine-grained access control lists that can silently block access to specific fields even when the account has broad table-level permissions. You might be able to query a ticket table successfully but find that certain sensitive fields such as assignment groups, internal notes or custom classification fields return as null or empty. This is not because the data isn’t there, but because your service account doesn’t have explicit permission to see it.
API rate limits and throttling
Every ticketing platform enforces some form of rate limiting on API calls. Understanding these limits before you start is critical, especially for the migration phase, which is far more API-intensive than steady-state operations. A migration that works perfectly in your test environment with 50 records can fall apart in production when it encounters 50,000 records and starts competing with every other integration on the instance.
Key considerations
Rate limits vary by platform, license tier, and instance configuration. Some systems enforce per-user limits, others enforce per-instance limits, and some do both. Common defaults range from 50 to 100 concurrent API requests, but this is heavily platform and customer-specific. Get specific numbers from your platform’s admin or documentation.
Rate limits are usually shared across all integrations. If your organization has other tools such as SIEM, CMDB sync, ITSM integrations, asset management, vulnerability scanners, or HR systems, making API calls to the same instance, your Tines migration competes for the same budget. This is one of the most commonly overlooked factors in migration planning. Before planning your throughput, get clarity on current API utilization. Ask the legacy system’s admin team for monitoring data on API call volumes. Many platforms provide dashboards or logs showing API consumption by integration user.
Monitoring API consumption
During both migration and steady-state operations, build visibility into your API consumption. Track the number of API calls per story execution, the rate of 429 responses, and the average retry count per operation. This data helps you tune your throughput, identify stories that are making more calls than expected, and proactively detect when you’re approaching rate limit ceilings before they start causing failures.
Data migration
Migrating historical ticket data into Tines Cases is often a requirement since your team needs to reference against prior data and context from the old system shouldn’t vanish on cutover day. Data migration is also the phase where the most things go wrong, and where the temptation to “just move everything” leads to the most wasted effort. Approach this phase with a clear plan and well-defined success criteria.
Migrate with purpose, not volume
Only migrate what’s necessary. Define clear criteria and work with your team to understand which historical data they actually reference in their day-to-day work. You may find that the vast majority of lookups hit a relatively narrow window of recent data, and that migrating three years of closed, low-severity tickets provides no operational value while consuming significant time and API resources.
More data does not equal better correlation: it equals more noise, more API calls, and more things to troubleshoot. Every record you migrate is a record you need to validate and a record that could create a false-positive correlation if its data is stale or malformed.
Before migrating anything, spend time understanding how Tines Cases search and correlation work. Take some time to tinker with subject lines, descriptions, records, and custom fields to understand what’s searchable, how deduplication logic behaves and can be set up, and what field formats yield the best lookup performance. This upfront investment will save you from migrating data in a format that isn’t valuable for lookups.
Define your data model in Tines first
Before you write any migration stories, document your data model. Decide on naming conventions for case subjects, define your custom fields, establish consistent formatting for key observables like IP addresses, hostnames, file hashes, and user identifiers, and determine how you'll use tags, priorities, and statuses. This data model becomes the target schema for all migration transformations.
Having a well-defined target data model also makes it easier to validate the migration. You can build automated checks that verify every migrated record conforms to the expected schema, flags missing required fields, and reports on data quality issues.
Keep migrated records lean
Migrated reference records are for correlation, not fully enriched cases. When migrating, include core fields such as identifiers, timestamps, status, key observables, severity, and assignment information. You can typically skip migrating heavy payloads such as full comment history, attachments, embedded images, or audit trails unless there’s a specific operational need that justifies the added complexity and API load.
If there’s a possibility you’ll need the heavy data later, consider a two-phase approach: migrate the lean core data first to get the correlation foundation in place, then selectively backfill additional detail for specific records as needed. This is far more efficient than trying to migrate everything at once.
Use Tines Stories for the migration Itself
Build a dedicated migration story that pages through your legacy system’s results, transforms records, and creates Tines Cases or Records. This approach gives you built-in logging, error handling, and retry logic. It is also auditable in a way that ad-hoc scripts aren’t and allows you to trace which records were processed, if any failed, and why.
Batch thoughtfully
Batch operations should be run during off-peak hours and coordinated with your legacy system’s admin team to schedule during maintenance windows or low-usage periods. Plan so that only a manageable number of records are migrated in each window. A migration that runs at a sustainable pace over two weeks is far better than one that tries to rush through everything in a weekend and ends up destabilizing the legacy system or hitting rate limits that affect other production integrations.
Define a target migration rate (e.g., 500 records per hour) based on your rate limit budget and test environment results. Build in pauses between batches to give both systems breathing room and to allow you to spot-check migrated data before proceeding with the next batch.
Implement checkpointing
Your migration story should track which records have been migrated, using a watermark like the record’s unique ID or last-updated timestamp. If a batch fails midway, you need to resume from where you left off, not start over from scratch. Starting over would mean re-processing records that already migrated successfully, which wastes time and risks creating duplicates.
Store your checkpoint state in a Tines Resource or a dedicated tracking record. Update it after each successfully migrated batch, not after each individual record to avoid overhead and potential performance issues during large migrations.
Data validation
After each migration batch, run a validation pass that compares source records against their Tines equivalents. Verify that record counts match, that key fields contain the expected values, and that no records were silently dropped or malformed during transformation. Automated validation catches problems early before they become deeply embedded in your operational data and much harder to fix.
Consider building a reconciliation report that includes total records in source, total records successfully migrated, total records failed, and a list of specific failures with error details. This report serves as both a quality assurance tool and an audit artifact.
What to watch out for
Pagination quirks. Most APIs default to a maximum page size, but actual response sizes can hit timeouts well before that limit. Use conservative page sizes (100–500 records per page) and paginate using offsets or cursor tokens as your platform supports. Be aware that some platforms’ pagination behavior is not guaranteed to be stable if records are being modified during pagination and you may miss records or see duplicates. For migration purposes, consider filtering to a fixed time window to reduce this risk.
Data format differences. Legacy systems often store datetimes in proprietary or non-standard formats and reference related records by internal IDs rather than human-readable identifiers. Plan for transformation logic to normalize these into the formats Tines Cases expects. Where possible, set your integration service account’s timezone to UTC to eliminate timezone conversion headaches. Timezone bugs are subtle and painful — an incident that appears to have happened at the wrong time can throw off correlation logic and confuse analysts.
Character encoding and special characters. Legacy ticket data is messy. Comments may contain HTML entities, Unicode characters, embedded images, email headers, pasted log output, and all manner of unexpected content. Your transformation logic needs to handle these gracefully, either by sanitizing input or by ensuring Tines Cases can store and display the content correctly.
Duplicate detection. Build deduplication logic into the migration story. If the migration needs to be re-run (and it will), you don’t want to create duplicate cases in Tines. Use the legacy system’s unique record identifier as a deduplication key, and check whether a record with that identifier already exists in Tines before creating a new one.
Bi-directional sync: Legacy system ↔︎ Tines Cases
In many migrations, the cutover isn’t instant. There’s a transition period, often lasting weeks or even months, where Tines Cases handles new work while escalations or handoffs still flow into the legacy system for teams that haven’t migrated yet.
This may create a bi-directional sync requirement and it’s one of the most operationally complex parts of the integration.
Field mapping and conflict resolution
Build an explicit field mapping document before writing any stories. Create a spreadsheet or structured document that defines, for every relevant field: the field name and data type in the legacy system, the corresponding field in Tines Cases, the sync direction (legacy → Tines, Tines → legacy, or bidirectional), the transformation logic if any (e.g., mapping priority values, reformatting dates), and the conflict resolution strategy.
Conflict resolution is the part that people skip and later regret. If the same field is updated on both sides between sync cycles, which value wins? Common strategies include “last write wins” (simple but can lose data), “source of truth wins” (designate one system as authoritative for each field), or “flag for manual review” (safest but adds analyst overhead). Choose deliberately based on the operational impact of each field.
State and status mapping requires special attention. Tines Case statuses and your legacy system’s ticket states may not be a 1:1 match. Define the mapping table up front and handle edge cases explicitly.
Pay particular attention to these scenarios: what happens when the legacy ticket is resolved but the Tines Case is still open? What happens when a Tines Case is closed but the legacy ticket gets reopened by another team? What about re-opening? Should that be allowed from both ends or should you enforce a closure cap where a case simply cannot be re-opened by a user or automation after a certain point? These decisions have real workflow implications and should involve your analysts, not just your engineers.
Idempotency is non-negotiable
All sync operations should be idempotent. If Tines processes the same webhook or poll result twice, it should not create duplicate tickets or duplicate comments. Design every sync action so that replaying it produces the same outcome as running it once. This means checking whether a record already exists before creating it, whether a comment has already been synced before adding it, and using unique identifiers (not just timestamps) as deduplication keys.
Idempotency is especially important because you will have situations where events are processed more than once. Webhooks can fire twice due to network retries. Polling can pick up the same record in consecutive cycles. Story re-runs during debugging will replay events. If your sync logic isn’t idempotent, each of these scenarios creates duplicates that are tedious to clean up and that erode confidence in the system.
Breaking infinite sync loops
This is the classic trap with bi-directional sync, and virtually every team encounters it if they don’t plan for it explicitly. The loop works like this: Tines updates a ticket in the legacy system and that update fires a webhook back to Tines. Tines sees the webhook, interprets it as a change, and updates the Case. The Case update triggers another sync back to the legacy system. The legacy system fires another webhook. And so on, forever, or until something crashes.
Build in explicit loop-breaking logic. The most reliable approaches include: setting a flag field on the record (e.g., updated_by_tines = true) so the receiving side can detect and ignore echo updates, comparing timestamps to determine whether an update is new information or just a reflection of a change you already made, or including a transaction ID in sync payloads that both sides track to identify echoes. Use Tines’ deduplicate action where possible as well, it’s designed for exactly this kind of scenario.
Test your loop-breaking logic thoroughly by simulating rapid back-and-forth updates in your test environment. The bugs in this area tend to be subtle and they often only manifest under specific timing conditions or when particular fields are updated.
Polling vs. webhooks for real-time sync
The choice between webhooks and polling determines how quickly changes in one system are reflected in the other, and how much API overhead the integration generates during steady-state operations. Most mature integrations use both: webhooks for speed and polling for reliability.
Webhooks (preferred)
Most modern ticketing platforms support outbound webhooks or automation rules that can fire HTTP calls to Tines webhook endpoints on record insert or updates. This is the preferred approach for real-time sync because it provides near-instant notification of changes without the overhead of continuous polling.
Be selective about which events trigger webhooks. Don’t attach outbound calls to high-volume tables such as audit logs, event queues, raw alert tables, or telemetry streams. These tables see massive write volumes, and an outbound REST call on every insert will crush performance on the legacy system and flood Tines with events that may not be relevant.
Filter your webhook triggers to fire only on the specific conditions that matter: when a ticket in a specific queue is created or updated, when the assignment group matches your team, or when a custom flag indicates the record is Tines-managed.
Use asynchronous webhook triggers (after insert/update) rather than synchronous ones. Synchronous triggers execute as part of the same database transaction that created or updated the record, which means they block the transaction until the outbound HTTP call completes.
If Tines is slow to respond or temporarily unavailable, synchronous triggers can cause timeouts that roll back the original transaction. This means the ticket update itself fails because the webhook couldn’t be delivered. Asynchronous triggers decouple the webhook delivery from the record operation, which is safer and more resilient.
Include the record’s unique ID in the webhook payload so Tines can do a fresh GET if needed. This is important because the data in the webhook payload may be slightly stale if other automation modified the record between the time the trigger fired and the time the webhook was constructed.
Plan for missed webhooks. Most ticketing platforms outbound webhook mechanisms do not have built-in retry or guaranteed delivery. If Tines is temporarily unavailable when the webhook fires, whether due to a Tines maintenance window, a network blip, or a transient error, that event is lost with no automatic recovery. Always implement a fallback polling mechanism to catch gaps. Think of webhooks as “best effort, fast path” and polling as the “guaranteed, slow path.”
Polling as a safety net
Polling should be a safety net and reconciliation check, not the primary sync mechanism. Poll on the last-updated timestamp with a watermark, and add a small overlap buffer (30–60 seconds) to account for clock skew between the legacy system’s server time and your watermark. Without this buffer, records updated in the gap between poll cycles can be missed permanently.
Watch out for noisy updates. Many systems update the “last modified” timestamp on any field change, including system-generated changes like SLA timer ticks, workflow state transitions, scheduled job updates, or system-level housekeeping. If your polling story doesn’t filter these out, you’ll reprocess the same record over and over, wasting API calls and potentially re-triggering downstream logic. Build in field-level change detection: after fetching a record, compare the fields you care about against the last known state and skip processing if nothing meaningful changed.
Reconciliation runs
Beyond real-time sync, schedule periodic reconciliation runs, either daily or weekly, that complete a comprehensive comparison between the two systems. Pull all records modified in the reconciliation window from both systems, compare key fields, and flag any discrepancies. This catches drift that individual missed webhooks or polling gaps might introduce over time. Think of it as an integrity check that keeps the two systems honest.
Attachments
If attachments need to sync between systems, there can be significant additional complexity. Attachments typically live in separate storage tables or object stores, require separate API calls to upload and download, and large files can hit API payload limits. Many platforms impose file size limits on their APIs that are lower than the limits in the UI, so a file that was uploaded through the web interface might be too large to download via API without special handling.
Treat attachment sync as its own workstream with its own error handling and retry logic. Consider whether you need to sync all attachments or just specific types (e.g., screenshots and log files but not video recordings or disk images). Selective syncing can dramatically reduce complexity and API load.
Error handling, observability, and resilience
The difference between a migration that succeeds and one that fails often comes down to how well the integration handles the unexpected. In a perfect world, every API call succeeds on the first try, data formats are always consistent, and nothing changes while you’re running. In reality, APIs time out, credentials expire, data is malformed, and things break in creative ways. Your integration’s error handling determines whether these inevitable problems are minor bumps or major incidents.
Error handling
Every API call in a Tines story should have explicit error handling for common failure modes: authentication failures (401/403 — token expired, credentials rotated, or permissions changed), not found errors (404 — record deleted between the time you queried the list and the time you tried to fetch the detail), rate limiting (429), and server errors (5xx — instance unavailable, undergoing maintenance, or experiencing an unrelated outage).
Each of these failure types requires a different response. Authentication failures should trigger an alert to the integration owner since they usually indicate a configuration change that requires human intervention. 404s on known records might indicate a deletion that should be reflected in Tines. 429s should trigger automated retry with backoff. 5xx errors should trigger retry with longer delays and an alert if they persist.
Build a dead letter queue pattern: when a record fails to sync after a defined number of retries, park it in a Tines Record or Case with an error status, the original payload, and the error details for review. Don’t silently drop failures — every dropped record is a potential gap in your security posture, a ticket that someone thinks was escalated but never actually made it, or an alert that correlated against stale data.
Alerting on integration health
Set up proactive alerts for integration health metrics. At a minimum, alert on: a sustained increase in error rates (more than X failures per hour), complete sync stoppages (no records processed in Y minutes), dead letter queue depth exceeding a threshold, and authentication failures (which usually indicate an expired credential or revoked permission that won’t self-resolve).
Route these alerts through your existing notification channels so they’re visible to the team, not just buried in Tines logs.
Performance and capacity planning
Load test the integration before go-live. Simulate production alert volumes through your Tines stories and observe API call counts, rate limit headroom, and end-to-end processing time. Don’t just test with average volumes, test with peak volumes and burst scenarios. If your team typically sees 200 alerts per day but occasionally has 2,000-alert days during major incidents, your integration needs to handle the 2,000-alert day without falling over.
Plan for burst scenarios. Case volumes are inherently spiky and a single incident can generate hundreds of correlated alerts in minutes or there can be hundreds of access requests for new employees. Your integration needs to handle bursts without falling behind, building up a backlog, or erroring out from rate limit exhaustion.
Understand Tines story concurrency. If 50 alerts arrive simultaneously, will 50 story instances fire in parallel, each making API calls to your legacy system? This can spike API usage dramatically and blow through rate limits in seconds. Understand how Tines manages concurrency for your stories and consider implementing throttling at the story level if needed. For instance, using a queue-based pattern where incoming events are buffered and processed at a controlled rate.
Graceful degradation
Design your integration to degrade gracefully rather than fail catastrophically.
If the legacy system is temporarily unavailable, Tines Cases should queue events for retry rather than dropping them.
If a specific record consistently fails to sync, it should be isolated in the dead letter queue without blocking the processing of other records.
If rate limits are exhausted, the integration should slow down rather than keep hammering the API and generating errors.
Checklist
To recap, here's a quick-reference checklist of the key principles covered in this part two of our Cases guide:
Establish secure API access early. Set up a dedicated service account with scoped permissions and confirm network access before writing a single story. Authentication and connectivity issues are the most common source of early delays.
Migrate data with purpose, not volume. Define your target data model first, migrate only the records your team actually uses, and keep migrated entries lean. More data means more noise, not better correlation.
Build your migration as a Tines Story. Using a dedicated story with checkpointing, batching, and automated validation gives you auditability, retry logic, and a clear record of what was migrated and what failed.
Plan bi-directional sync carefully. Document your field mappings and conflict resolution strategy upfront, ensure all sync operations are idempotent, and build explicit loop-breaking logic to prevent echo update cycles.
Treat error handling as a first-class requirement. Handle each failure type appropriately, implement a dead letter queue for records that can't sync, and set up proactive alerting on integration health metrics before go-live.
Up next, read part three of this series which covers stakeholder communication, running your legacy system in parallel with Tines Cases, rollback planning, and maintaining reporting and compliance continuity throughout the transition.