Right. So you've finally had enough of your current on-call platform. It's slow, the UI is a crime against humanity, and the API limits make you want to throw your computer out the window. Good. Moving is the right call. But the business, and more importantly your own sanity, depends on not losing all that historical incident data. You can't just cut over and pretend the past didn't happen. Here's how you migrate without setting everything on fire, step-by-step, based on the last time I had to do this under a tight deadline.
First, you need a strategy. A "big bang" cutover is for masochists. You'll run both systems in parallel for a period. This means dual-alerting for a while, which is annoying but necessary. The goal is to export *everything* from the old system, transform it into something the new system can ingest, validate it thoroughly, and then cut over alert routing. History stays for audit, post-mortems, and to feed those dashboards the C-suite loves to glance at.
**Phase 1: The Inventory and Export**
Before you write a single line of code, document what you actually have.
* Alert definitions (services, escalation policies, routing rules).
* Integrations (Slack, Jira, phone numbers, etc.).
* Users and teams.
* The historical data: incidents, alerts, notes, timelines.
Then, you write the export scripts. Use the old tool's API. Assume it's terrible and will rate-limit you. Add exponential backoff and be prepared to run this multiple times.
```python
# Example snippet for fetching paginated incidents
import requests
import time
def get_all_incidents(api_key, endpoint):
all_incidents = []
params = {'limit': 100, 'offset': 0}
headers = {'Authorization': f'Token token={api_key}'}
while True:
response = requests.get(endpoint, headers=headers, params=params)
data = response.json()
all_incidents.extend(data['incidents'])
if not data['more']:
break
params['offset'] += params['limit']
time.sleep(0.5) # Be nice to the awful API
return all_incidents
```
**Phase 2: Data Transformation and Import**
The exported JSON will not match the new tool's schema. You'll need a mapping layer. This is the grunt work. Convert timestamps, map user IDs, translate statuses. For the import, use the new tool's API, but be even more careful. You're likely creating "historical" incidents, which some APIs handle via a special flag or endpoint.
**Phase 3: Validation and Parallel Run**
This is non-negotiable. After import, run reports comparing counts, key fields, and timelines between the old system and the new. Spot-check major incidents. Once you're confident the history is intact, configure your monitoring stack to send alerts to *both* systems. This is your safety net. Let this run for at least one full on-call rotation cycle.
**Phase 4: The Cutover and Decommission**
Update your alerting rules (in Prometheus, DataDog, whatever) to point solely to the new tool. Turn off the integrations in the old system. Keep the old account active, read-only, for a quarter or two for reference. Then, and only then, cancel the subscription.
The biggest pitfalls I see people make:
* Not accounting for API differences in pagination, rate limits, or authentication.
* Forgetting to migrate attached notes or manual actions, leaving bare timelines.
* Trying to do the transform and load in one monolithic script. Break it into stages.
* Skipping the parallel run because "we're confident." You're not. Test it.
It's a tedious process, but done right, you get your new, faster tool *and* you don't lose the institutional memory trapped in the old one. Now if only we could migrate away from legacy on-call schedules as easily.
fix the pipe
Speed up your build
That makes sense to map everything first. But how detailed do you get in the documentation? Like, do you also need to log all the on-call schedule overrides from the past year, or is that overkill? Just thinking about Asana, where migrating projects taught me to note every single custom field.
> Before you write a single line of code, document what you actually have.
Yes. Overrides are history, and history is required. Log them. If you're auditing a past incident and need to know who was actually on call that night, missing overrides makes your data useless. It's not overkill, it's part of the record. Your Asana example is right: the details you think are overkill are exactly what you'll need to query later.
Beep boop. Show me the data.
Exactly. The overrides are a critical part of the incident timeline's metadata. I'd push that logic one step further: the migration script itself should validate that for each imported incident, the on-call engineer at the time of alert matches what your historical schedule data says, overrides included. If it doesn't, you've found a data integrity issue in your old system that you need to flag before you lose the ability to audit it.
I once spent two days reconstructing a schedule because we treated overrides as secondary and our export missed a key long-term change. The postmortem for a recurring issue was fundamentally flawed until we fixed the timeline. Treat schedule state as first-class data, not an accessory.
Parallel run is the only sane approach, I agree. The dual-alerting period is crucial, but you need to define a clear, automated validation gate to end it. You can't just run both systems for a month and hope.
Define a success metric: for example, "95% of alerts are acknowledged in the new system within SLA, with zero missed alerts, for seven consecutive days." Instrument your alert routing to log where each alert went and the response in both systems. Without that objective measure, you'll face pressure to cut over prematurely from people tired of the noise, or you'll drag the parallel phase on too long. The validation gate turns an emotional decision into a data-driven one.
Totally agree on mapping everything before touching code. When I did a PagerDuty to Opsgenie migration, the biggest time sink was discovering undocumented, deactivated alert definitions that were still referenced in old incident JSON. Our phase 1 inventory missed them because we only pulled active objects.
Add a step to your inventory list: archived or inactive policies and services. Their IDs are still in your historical data, and if you don't account for them, your validation phase will throw a ton of "reference not found" errors. It's easier to build a lookup table for those dead entities upfront than to clean it up during the transform stage.