Opsgenie Heartbeat Migration: Intervals, JSM Heartbeats, and Cron Schedules
Contents
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). 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, as documented in the Opsgenie API docs and on Atlassian's developer site (checked October 30, 2026). The dead man's 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 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 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 takes interval (minimum 1), intervalUnit (minutes, hours, or days), and enabled, and the heartbeat guide 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), 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).
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).
The heartbeat guide 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'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:
- Heartbeats move automatically, but heartbeat monitoring "isn't available in Jira Service Management Free and Standard plans," only Premium and Enterprise (feature changes).
- 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 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?). 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 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:
# 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 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). 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, 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 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 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). 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 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), 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.
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). Dead Man's Snitch vs CronRadar covers the lifecycle events, stuck runs, and outcome values, and 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.
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). 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).
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). It needs an API integration that isn't restricted to access configurations; a restricted one can only ping.
# 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). 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). 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).
3. Send Both From the Same Job#
This wrapper is adapted from the nightly backup in choosing a 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 explains why not to set them in the crontab.
#!/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"30 2 * * * /opt/backup-monitored.shFor 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 and Celery beat 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 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). 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). 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).
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). Then work through the Jira Service Management changes 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.
Put this guide to work
Know when the next run is missing
Connect one scheduled job, confirm a real completion, then verify the alert destination. Keep your existing scheduler in charge of running the work.