# Opsgenie Heartbeat Migration: Intervals, JSM Heartbeats, and Cron Schedules

By Dan Ochoa · Published 2026-10-30 · Updated 2026-10-30
Canonical: https://cronradar.com/blog/opsgenie-heartbeat-migration

> Opsgenie shuts off April 5, 2027. How its heartbeats work, the Jira Service Management equivalent, turning an interval into a cron schedule, and a checklist.

**Atlassian stopped selling Opsgenie on June 4, 2025, and on April 5, 2027 it shuts off access and deletes any data that hasn't been migrated ([announcement](https://www.atlassian.com/blog/company-news/evolution-of-it-operations)).** Every Opsgenie heartbeat needs a new home before then: Jira Service Management, where migrated heartbeats keep the same interval model, or a cron monitor that expects each run at its scheduled time plus a grace period. The conversion is mostly arithmetic, and it shows which skipped runs your current interval never alerted on.

This guide covers heartbeat checks only, not alert routing, on-call schedules, or other Opsgenie features. It covers Opsgenie's Heartbeat API v2 and the heartbeat endpoints of the Jira Service Management Cloud operations REST API, whose URLs use v1, the [only version available](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/intro/), as documented in the [Opsgenie API docs](https://docs.opsgenie.com/docs/heartbeat-api) and on [Atlassian's developer site](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-heartbeats/) (checked October 30, 2026). [The dead man's switch](https://cronradar.com/blog/cron-dead-mans-switch) explains the heartbeat pattern itself.

## Atlassian's Opsgenie Dates

| Date                          | What happens                                                                                                                                                                                        |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| June 4, 2025: end of sale     | No new purchases or sign-ups, whether from Atlassian, partners, or resellers, and no edition upgrades or downgrades. Renewals are allowed only if the subscription term ends before end of support. |
| April 5, 2027: end of support | Access to Opsgenie is shut off, and any data still in Opsgenie that hasn't been migrated is deleted.                                                                                                |

Both dates come from Atlassian's [announcement](https://www.atlassian.com/blog/company-news/evolution-of-it-operations) of March 3, 2025, which points customers who want to keep their alert and on-call workflows to Jira Service Management. Atlassian's [integration changes](https://support.atlassian.com/opsgenie/docs/how-opsgenie-integrations-move-to-jira-service-management/) page names Compass as the other destination. This guide covers the Jira Service Management side.

## How Opsgenie Heartbeats Work

### A Named Interval, Reset by Every Ping

A heartbeat is a unique, case-sensitive name and an interval. The [Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api) takes `interval` (minimum 1), `intervalUnit` (minutes, hours, or days), and `enabled`, and the [heartbeat guide](https://support.atlassian.com/opsgenie/docs/add-heartbeats-to-monitor-external-systems/) says each heartbeat can be checked "up to once per minute." The interval runs from the last ping. With a 10-minute interval, "If a Heartbeat request is not received for more than 10 minutes," Opsgenie creates an alert. The API has no schedule or time-zone field: Opsgenie never knows when a run is due, only how long it has been since the last ping.

On expiry, Opsgenie creates an alert "soon after," using the heartbeat's `alertMessage`, `alertTags`, and `alertPriority`. Left unset, the priority is P3 and the message is the heartbeat's name followed by "is expired" ([Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api)), and every expiry alert carries the `Heartbeat` tag by default. The next ping makes the heartbeat active again, and "the expiry alert will be automatically closed soon after." Heartbeats are available on Opsgenie's Standard and Enterprise plans only ([heartbeat guide](https://support.atlassian.com/opsgenie/docs/add-heartbeats-to-monitor-external-systems/)).

### Pinging a Heartbeat

The API ping is a `GET`, `POST`, `PUT`, or `PATCH` to `https://api.opsgenie.com/v2/heartbeats/:heartbeatName/ping` with an `Authorization: GenieKey` header; EU accounts use `api.eu.opsgenie.com`. Opsgenie processes pings asynchronously and answers `PONG - Heartbeat received` without checking that the heartbeat exists, so a misspelled name gets the same answer as a real one ([Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api)).

The [heartbeat guide](https://support.atlassian.com/opsgenie/docs/add-heartbeats-to-monitor-external-systems/) lists the other ways in: the Lamp command-line tool (`lamp heartbeat`), the Opsgenie Java API, the Script Monitoring Client, an email to the heartbeat's built-in address, and Auto Heartbeat Creation, an opt-in setting that, once enabled, creates and pings a heartbeat when a ping names one that doesn't exist, provided the API key belongs to a team.

### What a Heartbeat Can't Report

The [Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api)'s operations are ping, add, get, list, update, delete, enable, and disable. A ping carries only the name: no start event, no failure, no exit status. A run that fails before it pings looks the same as one that never started, and so does a run that hangs; each is reported only when the interval runs out.

## Heartbeats in Jira Service Management

### What the Migration Moves

From Atlassian's [notes on advanced alert configurations after migration](https://support.atlassian.com/opsgenie/docs/advanced-alert-configurations-after-migration/):

- Heartbeats move automatically, but heartbeat monitoring "isn't available in Jira Service Management Free and Standard plans," only Premium and Enterprise ([feature changes](https://support.atlassian.com/opsgenie/docs/feature-changes-and-deprecations-in-jira-service-management/)).
- Heartbeats on the deprecated v1 version aren't moved. Recreate them by hand with Jira Service Management's API.
- Heartbeats belong to teams, under **Teams > Operations > Heartbeats**. A site holds 2,000; a migrated total over the cap still moves, but you must remove the excess before adding more.
- Email ping addresses move from opsgenie.net to atlassian.net. Update scripts that email the old address; **Copy heartbeat name** in the heartbeat's **Actions** menu copies the new one.

Atlassian's [feature changes and deprecations](https://support.atlassian.com/opsgenie/docs/feature-changes-and-deprecations-in-jira-service-management/) list adds two things. Heartbeats have moved to a team-based configuration, which you switch on by hand in Opsgenie under **Settings > General > Heartbeat Configuration > Activate Heartbeat**, before or after migration. And Opsgenie REST API endpoints have to be updated to Jira Service Management's, with Opsgenie's API key management replaced by Atlassian authentication.

### Same Model, New Endpoint and Credentials

Jira Service Management describes the same check: heartbeats are expected at an interval set in minutes, hours, or days, and if one "is not received within a specified time frame," it generates an alert ([What are heartbeats?](https://support.atlassian.com/jira-service-management-cloud/docs/what-are-heartbeats/)). A heartbeat is `Pending` before its first ping, `Responsive` while pings arrive, `Unresponsive` after a silence "far beyond the expected interval," and `Off` when turned off for maintenance. Neither that page nor the [Heartbeats API](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-heartbeats/) says whether the interval counts from the last ping, as Opsgenie's guide does, so the alert times worked out below are Opsgenie's; check Jira Service Management's on a test heartbeat before relying on them.

The ping moves to a team-scoped URL. This is the documented request, adapted to read the credential from a file and the IDs from the environment, and to give up after five seconds:

```bash
# JSM_AUTH holds "email:api_token" and is read from a file only the job's account can read.
# JSM_CLOUD_ID and JSM_TEAM_ID are the {cloudId} and {teamId} path parameters.
JSM_AUTH=$(cat /etc/jsm/heartbeat-auth)
curl --fail --silent --show-error --max-time 5 --output /dev/null \
  --request POST --user "$JSM_AUTH" \
  "https://api.atlassian.com/jsm/ops/api/$JSM_CLOUD_ID/v1/teams/$JSM_TEAM_ID/heartbeats/ping?name=nightly-backup"
```

`GET`, `PUT`, and `PATCH` work too. The [Heartbeats API](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-heartbeats/) lists the scopes `read:ops-config:jira-service-management` and `write:ops-config:jira-service-management`, answers `202` with `PONG - Heartbeat received`, and, like Opsgenie, doesn't check that the heartbeat exists.

The credential is the larger change. The reference authenticates with an Atlassian account's email and API token, not an integration's GenieKey, and Atlassian API tokens expire after 1 to 365 days, one year by default ([API tokens](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)). Every host that pings needs the token, and a new one before it expires; when it lapses, pings stop resetting the heartbeats that depend on it. Atlassian's tracker has a suggestion to allow GenieKey pings, [JSDCLOUD-18241](https://jira.atlassian.com/browse/JSDCLOUD-18241), unresolved and in Gathering Interest status (checked October 30, 2026).

## Converting an Interval to a Cron Schedule Plus Grace

### What Each Model Waits For

An interval heartbeat alerts when the time since the last ping exceeds the interval. A CronRadar monitor goes `critical`, with last result `timeout`, an incident, and an alert, when the run due at a scheduled time hasn't checked in by that time plus a grace period; [how CronRadar applies the grace period](https://cronradar.com/blog/cron-grace-period) has the timing rules.

The consequence for intervals: an interval has to be longer than the longest normal gap between pings, and a skipped run is caught only if the gap it leaves is longer than the interval. On a job with even gaps, that means an interval under two periods. On a job with uneven gaps, some skipped runs never alert. And because the interval counts from the previous ping, the alert time for a skipped run moves with how long the previous run took.

### Five Jobs, Both Ways

Each job below sends its heartbeat when it finishes. Opsgenie creates its alert "soon after" expiry, so its times are the earliest possible. CronRadar times use a completion check-in and the grace shown.

| Job (UTC)                              | Opsgenie interval                   | Opsgenie alert for a skipped run                                                                                                                                | CronRadar schedule | `gracePeriod` | CronRadar `critical`     |
| -------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------: | ------------------------ |
| Every 5 minutes, done about 30 s in    | 10 minutes                          | One skipped run: none, unless the next run finishes later than the last one did, since the gap is about 10 minutes. Two in a row (10:05, 10:10): after 10:10:30 | `*/5 * * * *`      |           120 | After 10:07              |
| Hourly at :20, done about 5 min in     | 90 minutes                          | 10:20 skipped: after 10:55, 90 minutes after the 09:25 ping                                                                                                     | `20 * * * *`       |           600 | After 10:30              |
| Daily at 02:30, done 10 to 40 min in   | 25 hours                            | Tuesday skipped: between 03:40 and 04:10 Tuesday, depending on when Monday's run finished                                                                       | `30 2 * * *`       |         3,000 | After 03:20              |
| Weekdays at 09:00, done about 5 min in | 73 hours, to clear Friday to Monday | Tuesday, Wednesday, or Thursday skipped: none, the gap is 48 hours. Monday: after 10:05 Monday. Friday: after 10:05 Sunday                                      | `0 9 * * 1-5`      |           600 | After 09:10 the same day |
| The 1st at 04:00, done about 20 min in | 32 days, to clear a 31-day month    | March 1 skipped, after a 28-day February: after 04:20 on March 5                                                                                                | `0 4 1 * *`        |         1,800 | After 04:30 on March 1   |

For a job with a fixed period and a steady run time, a grace of the interval minus the period plus the run time reproduces Opsgenie's alert time: 90 minus 60 plus 5, or 35 minutes, for the hourly job. A grace sized from the job is usually shorter, because a cron monitor knows when each run is due; [choosing a grace period](https://cronradar.com/blog/cron-grace-period) works through the measurements, including how a `start` check-in shrinks the grace to the start delay.

### Write the Schedule From the Job

An interval doesn't record when the job runs, so take the schedule from the crontab or scheduler. CronRadar reads five-field cron in UTC, with no seconds field, `@daily`-style shortcuts, `L`, `W`, `#`, `?`, or `7` for Sunday, and when both day fields are restricted it expects a run only on dates that match both ([schedule syntax](https://docs.cronradar.com/schedules)). Convert local times to UTC. If the scheduler follows daylight saving time, use the earlier of the two UTC times and a grace of at least an hour plus the job's usual duration. [Dead Man's Snitch vs CronRadar](https://cronradar.com/blog/dead-mans-snitch-comparison) has more conversions and covers jobs that run any time within a window.

### What Maps Across

| Opsgenie heartbeat                       | CronRadar monitor                                                                                                                        |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Name                                     | Monitor key in the check-in URL                                                                                                          |
| `interval` and `intervalUnit`            | `schedule`, plus `gracePeriod` in seconds: 60 minimum, 90 by default, 60 to 86,400 in the monitor's settings                             |
| `enabled: false`, or the disable request | Pause; a timed maintenance pause resumes monitoring on its own                                                                           |
| `alertPriority`                          | **Severity**                                                                                                                             |
| `ownerTeam`                              | **Owner or team**, and the application whose channels receive the alert                                                                  |
| `alertMessage`, `alertTags`              | No equivalent fields                                                                                                                     |
| Auto Heartbeat Creation                  | A check-in that carries `schedule` creates a missing monitor                                                                             |
| `PONG` for any name                      | `404` with `MONITOR_NOT_FOUND` when the monitor doesn't exist and the check-in carries no `schedule`                                     |
| Email ping                               | An HTTP check-in from the job                                                                                                            |
| Expiry alert, closed by the next ping    | `critical` and an incident: a first alert, at most one reminder every 24 hours, and a recovery message when a successful run resolves it |

A check-in needs `schedule` only to create a missing monitor ([API reference](https://docs.cronradar.com/api-reference)), and CronRadar applies a check-in's `schedule` and `gracePeriod` only at that moment; change an existing monitor's values in its settings. The alert sequence and acknowledgment are in the [verification guide](https://docs.cronradar.com/verify).

An Opsgenie ping can't report a start or a failure; a CronRadar check-in can also be `start` or `fail`, and a `fail` opens an incident at once ([concepts](https://docs.cronradar.com/concepts)). [Dead Man's Snitch vs CronRadar](https://cronradar.com/blog/dead-mans-snitch-comparison) covers the lifecycle events, stuck runs, and outcome values, and [cron job alerting](https://cronradar.com/blog/cron-job-alerting) covers which of them deserve an alert.

## CronRadar Has No Opsgenie or Jira Service Management Channel

CronRadar can't send alerts into Opsgenie or Jira Service Management. Its alert channels are email, Slack, webhook, Discord, and PagerDuty, connected per application in its **Alerts** tab. It has no on-call schedules, escalation policies, or SMS. To decide which migrated heartbeats belong on PagerDuty and which on Slack, email, or a ticket webhook, use the priority classes in the [CronRadar Scheduled Job Methodology](https://cronradar.com/methodology).

Jira Service Management's integration named Webhook doesn't close that gap: Atlassian documents it as "an outgoing integration" that posts Jira Service Management alert actions to a URL you give it ([Integrate with Webhook](https://support.atlassian.com/jira-service-management-cloud/docs/integrate-with-webhook/)). It doesn't receive webhooks, and CronRadar's webhook payload hasn't been verified against any Jira Service Management incoming integration. If a heartbeat's alerts have to arrive in Jira Service Management, keep that heartbeat in Jira Service Management.

CronRadar's Free plan covers 10 active monitors with every alert channel and no card; beyond that, Grow bills each further monitor monthly by schedule class ([pricing](https://cronradar.com/pricing)).

## Migration Checklist: Run Old and New in Parallel

### 1. Export the Heartbeat List

The list request returns each heartbeat's name, interval, unit, enabled flag, owner team, and alert fields ([Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api)). It needs an API integration that isn't restricted to access configurations; a restricted one can only ping.

```bash
# The integration's key, read from a file only this account can read.
OPSGENIE_API_KEY=$(cat /etc/opsgenie/api-key)
curl --fail --silent --show-error --max-time 10 \
  --header "Authorization: GenieKey $OPSGENIE_API_KEY" \
  https://api.opsgenie.com/v2/heartbeats |
  jq -r '.data.heartbeats[] | [.name, .interval, .intervalUnit, .enabled, (.ownerTeam.name // "-"), .alertPriority] | @tsv'
```

On a migrated site, list each team's heartbeats with `GET https://api.atlassian.com/jsm/ops/api/{cloudId}/v1/teams/{teamId}/heartbeats`, which returns `values` and a `links.next` page link ([Heartbeats API](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-heartbeats/)). Then find the sender of each name in crontabs, scheduler configs, and scripts: `heartbeats/` URLs, `lamp heartbeat` calls, and the heartbeat's email address.

### 2. Create Each Monitor Before the Parallel Run

For each job, read its schedule and run time, then create the monitor before deploying the new check-in, with `POST /applications/{id}/jobs` on CronRadar's management API, sending the job's `name`, `monitorKey`, `schedule`, and `gracePeriod` ([assistant access](https://docs.cronradar.com/agent-quickstart)). The Hangfire and Quartz.NET 3.x integrations create monitors when they discover jobs, so they need no separate step. A monitor created in advance waits in `pending`, and if the first expected run plus grace passes without a check-in, CronRadar sends a never-executed alert, so a wiring mistake such as a missing key file shows up instead of staying silent. A monitor created by its first check-in can't catch that.

Choose channels and recipients in the application's **Alerts** tab. A channel test proves a destination receives messages; a `fail` followed by a successful run, from a harmless job in a separate test application, proves detection, delivery, and recovery ([verification guide](https://docs.cronradar.com/verify)).

### 3. Send Both From the Same Job

This wrapper is adapted from the nightly backup in [choosing a grace period](https://cronradar.com/blog/cron-grace-period), shown whole for copying, with the old Opsgenie ping added. It reports `start`, then `complete` or `fail`, to CronRadar, keeps the Opsgenie ping on success as before, and exits with the job's own code. Every request gives up after five seconds and ignores errors, so neither service can fail the backup. Both keys come from files only the job's account can read; [the dead man's switch guide](https://cronradar.com/blog/cron-dead-mans-switch) explains why not to set them in the crontab.

```bash
#!/bin/sh
# /opt/backup-monitored.sh
CRONRADAR_API_KEY=$(cat /etc/cronradar/api-key 2>/dev/null)
OPSGENIE_API_KEY=$(cat /etc/opsgenie/api-key 2>/dev/null)
url="https://cron.life/api/v1/ping/nightly-backup"
run_id="nightly-backup-$(date +%s)-$$"

report() {
  event=$1
  shift
  curl --fail --silent --show-error --max-time 5 --output /dev/null \
    --request POST --get --user "$CRONRADAR_API_KEY:" \
    --data-urlencode "schedule=30 2 * * *" \
    --data-urlencode "gracePeriod=900" \
    --data-urlencode "run_id=$run_id" \
    "$@" "$url/$event" || true
}

report start
/opt/backup.sh
rc=$?
if [ "$rc" -eq 0 ]; then
  report complete
  # The old heartbeat, kept for the parallel run (EU accounts: api.eu.opsgenie.com).
  # Delete this request at cutover.
  curl --fail --silent --show-error --max-time 5 --output /dev/null \
    --request POST --header "Authorization: GenieKey $OPSGENIE_API_KEY" \
    "https://api.opsgenie.com/v2/heartbeats/nightly-backup/ping" || true
else
  report fail --data-urlencode "message=backup.sh exited with code $rc"
fi
exit "$rc"
```

```text
30 2 * * * /opt/backup-monitored.sh
```

For a heartbeat already in Jira Service Management, swap the Opsgenie request for the Jira Service Management ping shown earlier. The check-ins carry the schedule, so a deleted monitor is recreated by the next run.

No CronRadar SDK is installable for Node.js, Python, Celery, PHP, or Laravel (checked September 20, 2026). Jobs there report with HTTP check-ins, which don't discover jobs automatically; [Node.js cron monitoring](https://cronradar.com/blog/node-js-cron-monitoring) and [Celery beat](https://cronradar.com/blog/celery-beat-not-running) have check-in helpers. On .NET, the published CronRadar 1.0.13 wraps a job, and CronRadar.Hangfire 0.1.0 and CronRadar.Quartz 0.1.0 (for Quartz.NET 3.x) discover recurring jobs; [Dead Man's Snitch vs CronRadar](https://cronradar.com/blog/dead-mans-snitch-comparison) shows the setup and the Hangfire release's retry caveat.

### 4. Overlap for the Job's Longest Gap

Run both for at least one normal schedule window, and don't change the scheduler in the same deployment ([migration guide](https://docs.cronradar.com/migrations)). Make the window cover the job's longest gap, a weekend for a weekday job and a full month for a monthly one, and confirm each scheduled run reached CronRadar with a result, not just a `start`.

### 5. Disable, Then Remove the Ping, Then Delete

A heartbeat whose ping is removed expires one interval later and alerts. Disable it first with `POST https://api.opsgenie.com/v2/heartbeats/:heartbeatName/disable`, deploy the wrapper without the old request, and then delete it with `DELETE https://api.opsgenie.com/v2/heartbeats/:heartbeatName` ([Heartbeat API](https://docs.opsgenie.com/docs/heartbeat-api)). In Jira Service Management, send `"enabled": false` in a `PATCH` to `/v1/teams/{teamId}/heartbeats?name={name}`, and delete with a `DELETE` to the same URL ([Heartbeats API](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-heartbeats/)).

### 6. Finish Before April 5, 2027

For the heartbeats you keep in Jira Service Management, confirm the team-based configuration was activated in Opsgenie under **Settings > General > Heartbeat Configuration > Activate Heartbeat** ([feature changes](https://support.atlassian.com/opsgenie/docs/feature-changes-and-deprecations-in-jira-service-management/)). Then work through [the Jira Service Management changes](#heartbeats-in-jira-service-management) for each one: recreate it if it was v1, move its sender to the new email address or endpoint, and put an Atlassian API token on the host that pings.

## Frequently Asked Questions

### When does Opsgenie shut down?

Atlassian ended sales on June 4, 2025 and ends support on April 5, 2027. On that date, access is shut off and data that hasn't been migrated is deleted. Renewals after the end of sale are allowed only for terms that end before April 5, 2027.

### Do Opsgenie heartbeats migrate to Jira Service Management?

Yes, they move automatically, but heartbeat monitoring works only on Jira Service Management Premium and Enterprise, not Free or Standard. Heartbeats on the old v1 version must be recreated by hand, email ping addresses change from opsgenie.net to atlassian.net, and API pings move to a team-scoped endpoint that authenticates with an Atlassian account's email and API token.

### How do I convert an Opsgenie heartbeat interval to a cron schedule?

Take the schedule from the job, not the interval, and write it as five-field cron in UTC. Size the grace from the job's start delay plus, for a completion-only check-in, its run time. For a heartbeat sent when the job finishes, on a job with a fixed period and a steady run time, a grace of the interval minus the period plus the run time reproduces Opsgenie's alert time, but a grace sized from the job is usually shorter.

### Why didn't my Opsgenie heartbeat alert when a run was skipped?

A heartbeat alerts only when the time since the last ping is longer than the interval. A 73-hour interval on a weekday job is long enough that skipping a Tuesday, Wednesday, or Thursday run leaves no gap longer than the interval, and a 10-minute interval on a 5-minute job usually misses a single skipped run. A cron schedule gives each run its own deadline, so a single skipped run alerts.

### Can CronRadar send alerts to Opsgenie or Jira Service Management?

No. CronRadar's alert channels are email, Slack, webhook, Discord, and PagerDuty. Jira Service Management's Webhook integration is outgoing, so it doesn't accept CronRadar's webhook, and no other route into Jira Service Management has been verified.
