Your automation stopped because a token expired. Here's how to hear about it the same day.
Credentials expire on their own schedule, not yours: a Google token after 7 days in testing mode, an API key someone rotated, an app connection that needs reconnecting. The platform may send one notice, or none, and then the automation just goes quiet.
Short answer
Don't rely on the expiry itself to raise an alarm. Expect a result from the automation on a schedule, and get alerted when it doesn't arrive. Did It Run? does this for Zapier, n8n, Make, cron jobs, GitHub Actions and AI agents: the last step of each run pings its watch's URL, and if no ping arrives by the expected time plus your grace window, you get a missed alert. Whatever broke the credential (expiry, revocation, a password change, a deactivated scenario), the effect is the same: no ping, so you hear about it at the first deadline. Start free (3 watches, no credit card).
Why expired credentials go unnoticed
- Some tokens are built to expire. Google says a project with an external OAuth consent screen in Testing status "is issued a refresh token expiring in 7 days" (unless it only asks for basic profile scopes). Google also lists other reasons a refresh token stops working: the user revoked access, it went unused for six months, or the user changed their password and the token has Gmail scopes (Google OAuth 2.0 docs). n8n's docs describe the same 7-day expiry and say to reconnect the credential (n8n Google credential docs).
- The notice stays inside the platform. Make documents that an
InvalidAccessTokenError"deactivates the scenario and notifies the user" (Make developer docs). Zapier lists broken authentication on its Alerts page (Zapier help: Alerts page). Both are useful, but after that a switched-off scenario produces nothing at all, so if the notice is missed, nothing else will remind you. - Runs wait instead of failing. Zapier puts a run on hold when "one of the app accounts used in the Zap is disconnected"; it keeps the trigger data so you can replay the run after reconnecting (Zapier help: held runs). Nothing is lost, but nothing gets done until someone notices.
- The trigger stops seeing new data. A Zapier Community member described it like this: *"in my case it was an expired auth, so no error emails, just days of silence"* (Zapier Community thread).
- Scripts and agents swallow the 401. A cron script that ignores HTTP status codes, or an AI agent that writes "I couldn't access that account" instead of doing the work, finishes "successfully" with nothing useful in it.
Step 1: put a heartbeat at the end of every run
The check-in goes last, after the step that uses the credential. If that step fails because the token is dead, the check-in never runs.
- Zapier: a final Webhooks by Zapier → POST step to the ping URL. Webhooks by Zapier is a Premium app, so it needs a paid Zapier plan. See the Zapier recipe.
- n8n: a final HTTP Request node. See the n8n recipe.
- Make: a final HTTP → Make a request module. See the Make recipe.
- Cron, scripts, GitHub Actions, AI agents: a
curlto the ping URL as the last command. See the cron and GitHub Actions recipes.
Create the watch with the schedule the automation keeps ("every weekday by 9:15am", or a cron expression) and a grace window. For event-driven automations (new form entry, new email), use the longest normal quiet period, e.g. "at least once a day by 6pm".
Check-ins are by ping URL today, with /start and /fail variants. Email check-ins are coming soon.
Step 2: report auth errors as failures, with the reason
A missed alert tells you the run didn't arrive. If the job can tell that the problem is authentication, have it call /fail with a short reason so you get a failed alert right away, with the cause in the alert:
#!/usr/bin/env bash
PING="https://diditrun.app/p/<your-token>"
code=$(curl -s -m 60 -o /tmp/items.json -w "%{http_code}" \
-H "Authorization: Bearer $API_TOKEN" "https://api.example.com/v1/items")
if [ "$code" = "401" ] || [ "$code" = "403" ]; then
curl -fsS -m 10 --retry 3 -X POST --data-binary "API returned $code: token expired or revoked?" "$PING/fail" > /dev/null
exit 1
elif [ "$code" != "200" ]; then
curl -fsS -m 10 --retry 3 -X POST --data-binary "API returned $code" "$PING/fail" > /dev/null
exit 1
fi
items=$(jq 'length' /tmp/items.json)
curl -fsS -m 10 --retry 3 -X POST -H "Content-Type: application/json" \
--data "{\"items\": $items}" "$PING" > /dev/nullIn n8n, an error workflow (Error Trigger → HTTP Request to /fail) does the same for nodes that throw. In Make, add an error handler route that calls /fail, as in the Make guide.
Step 3: catch runs that "succeed" without access
Some failures don't stop the run. An agent loses access to a mailbox and summarises nothing; an API returns an empty list instead of an error. Send the run's output or an item count with the check-in and set a sanity check on the watch:
- Minimum item count, e.g. 1, so
{"items": 0}raises an empty alert. - Required keyword, e.g. have the agent end its report with a marker like RUN-OK only when it actually did the work.
- Minimum size, so a near-blank report is flagged.
More on this in scheduled job ran but the output was empty.
Fix the cause, too
A heartbeat tells you something broke; it doesn't renew anything. For Google credentials stuck on 7-day tokens, the n8n docs point to the consent screen's Testing status as the cause. For personal access tokens, GitHub lets you choose an expiration when you create one, and "automatically removes personal access tokens that haven't been used in a year" (GitHub docs), so note the date somewhere you'll see it. In Zapier, reconnecting an expired connection reconnects it for every Zap that uses it (Zapier help: manage app connections); then replay the held runs.
What you'll see
- Missed: no check-in by the expected time plus grace. A scenario that was switched off, a Zap whose runs are on hold, or a trigger that stopped seeing data ends up here.
- Failed: the job called
/fail, e.g. with "API returned 401". - Empty: a run arrived but failed your sanity check.
- Recovered: the first good run after you reconnect.
You get one alert when a watch turns missed, failed or empty, not one for every run, and a recovery notice when it's fixed. Free covers 3 watches with email alerts; Solo ($15/month) adds Slack, Discord and Telegram alerts and the daily roll-call digest across every automation.
FAQ
Will Did It Run? tell me before a token expires?
No. It doesn't connect to your accounts or read your credentials, so it can't see expiry dates. It alerts on the effect: the first scheduled run that doesn't check in, reports a failure, or comes back empty.
Zapier and Make already flag connection problems. Why add this?
Make notifies you when it deactivates a scenario, and Zapier lists broken connections on its Alerts page. Keep those on. A heartbeat is a second, independent check that also covers what those notices don't: a trigger that quietly stops seeing data, runs held for other reasons, cron jobs and AI agents outside those platforms, and the notice that landed in spam.
Does the heartbeat need a paid Zapier plan?
Yes, today. The check-in is a Webhooks by Zapier step, which needs a paid Zapier plan (or Zapier's trial). Email check-ins are coming soon, but they aren't live yet.
Can I check in by email instead of a URL?
Not yet. Email check-ins are coming soon. Today every check-in is an HTTP request to the watch's ping URL, with /start and /fail variants.
Is this on the free plan?
Yes. Missed, late, failed and empty detection is included on every plan, including Free (3 watches, email alerts).
Know the next time it doesn't run
Know when your AI agents and automations didn't run. Free for 3 watches.
Start free