Cron vs Webhook: Pick the Trigger That Matches the Failure Mode
Choose cron vs webhook by failure mode: webhooks miss when subscriptions die; schedules miss when the job never runs — hybrid reconciliation covers both.
William Spurlock Founder — Spurlock Studios Updated 18 MIN
Pick the trigger by how you will notice it dying — not by which demo looks faster. Webhooks win when the source can push and you can detect a dead subscription. Schedules win when freshness can wait and you need a heartbeat you control. Many production paths need both: a webhook for speed and a reconciliation cron that catches what the push missed.
This sits under the Production n8n handbook. Trigger choice is reliability design, not a preference for “real-time” branding. After 500+ automations, the expensive mistake is treating poll vs push as a taste question.
The short answer
- Webhook (push) when the vendor supports events, latency has a cost, and you monitor subscription health.
- Schedule / cron / polling when the API is pull-only, batch is fine, or you want a run you can prove happened.
- Both fail silently in different ways — design detection for the mode you chose.
- Hybrid is normal: webhook for the hot path, hourly or daily cron to reconcile gaps.
- Vendor retry is not a constant. Stripe retries for days. GitHub does not retry. Zapier polling intervals are plan-dependent.
When should you poll vs push?
Poll when you must ask. Push when the source will tell you. That is the whole decision, plus the failure mode you can actually detect.
| Question | Push (webhook) | Poll (cron / schedule) |
|---|---|---|
| Who initiates? | Vendor POSTs to your URL | You GET/list on a timer |
| Latency | Seconds, if delivery works | Your interval, plus query time |
| Proof of life | Traffic, or a synthetic ping | A run record at a known time |
| Typical miss | Subscription dead, URL rotated, HMAC wrong | Interval too coarse, watermark skip, empty success |
| Cost shape | Execution per event | Tick cost even when nothing changed |
| Completeness | Whatever the vendor delivered | Whatever your query returned |
Use this order:
- Does the vendor document webhooks or an event API? If no, you poll. Stop pretending.
- If yes, can you verify the signature and respond fast enough for that vendor’s timeout? If no, fix that before you subscribe — see webhook security.
- What is the maximum lag the business will accept? If it is tighter than a honest poll, you need push, or you need to rename the SLA.
- What does silence look like? Zero POSTs, or a missed cron, or a green zero-row query?
- Is a reconciliation job in v1? On a revenue path, “later” means you accepted silent gaps.
If two rows conflict, ship webhook for intake and add a reconciliation schedule before you declare done.
When is real-time actually required?
Real-time is required when delay creates irreversible cost. “We like seeing it appear instantly in Slack” is not that.
| Path | Delay that hurts | Prefer |
|---|---|---|
| Lead routing while a human is on the phone | Minutes | Push, plus a short reconcile |
| Fraud / access revoke | Seconds to minutes | Push; poll is a backstop only |
| Inventory or booking hold | Minutes | Push if the vendor has it |
| Payment confirmation that unlocks fulfillment | Minutes | Push, plus a List-events reconcile |
| Nightly finance export | Hours | Schedule |
| CRM hygiene / tag cleanup | Hours to a day | Schedule |
| Slack “nice to know” | Whatever | Schedule is enough |
Decision list:
- If this is late by X minutes, what breaks — money, access, or ego?
- If ego, pick a schedule you can explain.
- If money or access, require a documented event API or admit the lag.
- If the vendor offers only polling on Zapier or Make, buying a tighter interval is still sampling. Plan for gaps.
A five-minute schedule is often enough and easier to reason about than a webhook nobody is watching.
What does a webhook miss that a cron will catch?
A webhook only knows what arrived. It does not know what the source still holds.
| Gap | Why push misses it | What the cron does |
|---|---|---|
| Subscription deleted or disabled | Zero deliveries; your dashboard looks fine | Lists source records and upserts the missing ones |
| URL rotated / test URL left in the vendor | Events go to a listener that is not production | Same list/upsert; also surfaces the count mismatch |
| Vendor gave up retrying | Stripe stops after its window; GitHub never started | Pulls by updated_at or event id |
| Created and edited before you were subscribed | Most event APIs are not a full history feed | Backfill from a watermark |
| Filtered event types | You subscribed to created and needed updated | Query is wider than the subscription |
| HMAC / auth reject | Vendor retries, then drops you | Cron never needed that POST |
n8n’s Webhook node is a listener: test URL while you click Listen, production URL after you publish. Point a vendor at the test URL and the integration dies when the editor session ends. A cron does not care which URL you fat-fingered last Tuesday.
Stripe tells you to return a 2xx before slow side effects, then process the event — and to pull undelivered events when the endpoint was down. That pull is a poll. The vendors that take webhooks seriously still assume you will reconcile.
When does polling waste money and still miss events?
Polling costs show up as ticks, not as drama.
| Meter | What you pay for | Miss it still has |
|---|---|---|
| Zapier tasks | Actions when a poll finds work (rules are plan-specific) | Create-then-delete between polls |
| Make operations / check runs | Scheduled asks, including empty ones | Same gap; plus queue caps on scheduled webhooks |
| n8n executions | Every Schedule tick you allow to run the graph | Empty success if the query is wrong |
| Vendor rate-limit budget | “Anything new?” loops | 429s that look like “the CRM is down” |
Miss modes a faster poll does not fix:
- Item created and deleted between ticks
- Pagination that skips page two forever
- Clock skew /
updated_afterfilters that skip edits - Plan interval coarser than the SLA you promised sales
- First page only, because someone hardcoded
limit=50
Prefer webhooks when the vendor has them. Prefer schedules when the business accepts the cadence. Do not poll every minute “to be safe” without measuring cost and rate limits — that fight belongs in API rate limits in n8n.
Checklist before you tighten an interval:
- Written max lag from the business owner, not from the tool default
- Watermark (cursor,
updated_at, or last id) stored outside the run - Pagination explicit; last page proven
- Empty-success streak alert for this source
- Daily full reconcile if the window can skip
- Rate-limit headroom measured at the new cadence
Faster empty polls fail faster. They do not become a webhook.
How do silent failures differ by trigger?
Webhooks fail by absence of traffic. Schedules fail by absence of a run or by empty success. One Slack channel for “any error” catches neither.
| Trigger | Silent failure | What operators see | Detection |
|---|---|---|---|
| Webhook | Subscription deleted, URL rotated, vendor disabled events | Zero executions; dashboards look “fine” | Expect N events/day or a synthetic ping |
| Webhook | Receiver up, HMAC or auth wrong | Vendor retries, then gives up | Alert on 4xx rate at the edge |
| Webhook | Test URL in production | Works in the editor, dies after | Vendor delivery log vs n8n Executions |
| Schedule | Workflow unpublished or cron mis-set | Nothing runs; no error | Dead-man: alert if no success in 2× interval |
| Schedule | Runs green, query wrong | Empty success forever | Items processed > 0 over a window, or a reconcile count |
| Poll (Zapier / Make) | Interval too slow / filter wrong | “Missing” records blamed on the CRM | Dual-count source vs destination daily |
n8n will not start a published Schedule until the workflow is published — their Schedule Trigger docs say so in a callout people skip. Unpublished is not “paused with a heartbeat.” It is off.
Make’s webhook docs add another silence: a webhook not attached to a scenario for more than five days is deactivated and returns 410 Gone. The vendor may still think the URL is live.
Do not share one pager-duty for “workflow error.” Split “no traffic” from “no run” from “run with zero rows.”
How do vendors retry — or refuse to?
“We have a webhook” is not a reliability class. Read the retry paragraph for that vendor.
| Vendor | Retry on failed delivery | Timeout you must beat | What you still poll for |
|---|---|---|---|
| Stripe | Live: up to three days, exponential backoff. Sandbox: three retries over a few hours | Fast 2xx before slow work | Undelivered / missed events via List Events |
| GitHub | Does not automatically redeliver | 10 seconds or it is a failure | Failed deliveries (3-day UI/API window) plus your own reconcile |
| n8n as receiver | You choose Respond Immediately vs when the last node finishes | Whatever the sender allows | Your own dead-subscription monitor |
| Make as receiver | Default 200 Accepted when queued; 400 if the queue is full; 429 over rate | 300 requests / 10 seconds | Queue depth; inactive-hook 410 |
Stripe also regenerates Stripe-Signature on each retry. A replay window that keys only on the raw body without the event id will treat a retry as new. GitHub’s X-GitHub-Delivery is the id you store.
Stripe’s manual catch-up is not infinite: Dashboard Resend works for 15 days; stripe events resend via the CLI works for 30 days (same webhook page). After that you are on List Events plus your own datastore. GitHub’s UI/API redelivery window is 3 days. Miss that window and the cron is the only copy.
n8n Respond modes, from the Webhook node:
| Respond setting | What the sender gets | Use when |
|---|---|---|
| Immediately | Workflow got started | Vendor timeout is tight (GitHub’s 10s) |
| When Last Node Finishes | Last node’s payload | Tiny graphs; you can finish inside the timeout |
| Using ‘Respond to Webhook’ Node | Whatever that node returns | You need a custom 2xx early, then keep working |
| Streaming | Chunked body | Chat-style endpoints — not CRM writes |
Payload cap is 16MB (N8N_PAYLOAD_SIZE_MAX if you self-host). A “webhook plus file” that exceeds it looks like the vendor is down. It is not. The body never entered the workflow.
Procedure when a vendor is “flaky”:
- Read their retry + timeout page. Do not guess from Stripe’s behavior.
- In n8n, set Respond to Immediately (or a Respond to Webhook node early) so GitHub’s 10-second clock is not waiting on a CRM write.
- Apply the side effect after the
2xx, with an idempotency key. - Add a list/reconcile job for the window their retry does not cover.
- If they publish no retry at all, the reconcile job is the product.
A webhook without a documented retry is a best-effort push. Price the cron accordingly.
When do you ship webhook plus a reconciliation cron?
When money, access, or a booked human depends on completeness. That is most lead and order paths we ship.
| Layer | Owns |
|---|---|
| Webhook | Latency and happy-path volume |
| Cron | Completeness and catch-up |
| Idempotency key | Safe overlap when both fire |
| Heartbeat | Proof the webhook path is alive |
| Diff report | Proof the cron is doing work, not sleeping |
Build order:
- Webhook workflow handles the event quickly, responds early, verifies the signature, writes with a stable key.
- Reconciliation schedule (hourly or daily) lists source records updated since the last watermark and upserts anything missing in the destination.
- Diff report posts to ops when the cron creates or repairs rows — so you learn webhook gaps instead of ignoring them.
- Shared validators so both paths reject the same bad shapes.
| Cadence | Use when | Do not use when |
|---|---|---|
| Every 15–60 minutes | Lead or order completeness during business hours | The list API cannot page cheaply |
| Daily | Low volume, or overnight catch-up | Sales promised “instant” with no push |
| Weekly full snapshot | Audit / finance | You are using it as the only path |
Hybrid costs one extra workflow. It buys sleep. If step 3 never fires a repair, either the webhook is healthy or the cron query is as blind as the webhook. Dual-count source vs destination before you celebrate.
What if the vendor has no webhooks?
Then you poll or schedule — honestly. Do not market a 15-minute Watch module as real-time.
Make’s own module types split this cleanly: Watch modules poll on the scenario schedule; instant modules are webhooks. If the app has no instant trigger, you are on a schedule whether the UI feels fancy or not. Their trigger-module guidance also assumes a date or numeric id you can watermark. No id, no safe poll.
- Document the maximum acceptable lag with the business owner
- Set poll/schedule interval inside that lag (not “as fast as the plan allows”)
- Store a watermark (cursor,
updated_at, or last id) - Handle pagination explicitly
- Alert on zero-item success streaks that are abnormal for that source
- Add a daily full reconcile if the poll window can skip
- Rename the SLA in the runbook so sales cannot quote “instant”
n8n pattern that works: Schedule Trigger plus an HTTP Request (or app node) keyed on the watermark. That beats a webhook fantasy on a vendor that only lists records.
If the list API cannot sort descending, you will eventually miss the head of the pile. Make’s trigger docs warn that ascending order on a large collection cannot reach the latest records. Believe them. Change the query or change the vendor.
Watermark procedure we actually ship:
- Persist
last_seen_atorlast_seen_idin a store the next run can read — not in the execution log. - Query
updated_at >= last_seen_at(orid > last_seen_id) with an overlap of one interval to absorb clock skew. - Page until empty. Do not stop at page one.
- Upsert by source id. Advance the watermark only after the last successful page.
- If a run fails mid-page, leave the watermark where it was. Re-processing overlap is cheaper than a silent skip.
| Watermark | Works when | Breaks when |
|---|---|---|
updated_at | The API stamps edits | Creates with a later created_at but stale updated_at |
| Monotonic id | Insert-only streams | Ids that recycle or arrive out of order |
event.id cursor | The vendor lists events | The list is not complete (filtered types) |
| “Unread” / “since last run with no store” | Never | Humans and other Zaps move the cursor |
How often should you poll?
Start from business lag, not from tool defaults.
- Ask: “If this is late by X minutes, what breaks?”
- Set interval ≤ X / 2 when the API and the bill allow.
- Cap interval by rate limits and billable poll cost.
- Prefer fewer, richer batch jobs over chatty empty polls.
- Revisit after you have a week of missed-event rate, not after the first demo.
| Max acceptable lag | Starting interval | Notes |
|---|---|---|
| 5 minutes | You probably need push | A 2-minute poll still has create-delete holes |
| 15–30 minutes | 10–15 minutes | Common CRM Watch default; write it down |
| 1–4 hours | 30–60 minutes | Batch windows; watch rate limits |
| Daily | Nightly + on-demand button | Finance, reporting, cleanup |
In n8n, changing a Schedule interval does nothing until you unpublish and publish again. n8n’s common issues page is explicit: the new cadence starts from publish time. Change “every 1 hour at :00” to “every 2 hours” at 11:30 and the next run is 13:30, not noon. Operators call that “cron is broken.” It is not. You moved the epoch.
Cron variables evaluate at publish too. Edit the variable later and the schedule stays stale until you republish.
How do Zapier and Make meter polling vs instant?
Do not compare invoices until you compare failure modes. Then read the meter, because the meter changes how people pick a bad trigger.
Zapier’s How Zap triggers work page (verify your workspace — plans change) publishes polling intervals by plan:
| Zapier plan (as documented there) | Polling interval |
|---|---|
| Free | 15 minutes |
| Professional | 2 minutes |
| Team | 1 minute |
| Enterprise | 1 minute |
Instant triggers are webhooks. Zapier does not poll those. You cannot flip a polling trigger to instant; the app’s API decides. Flood protection can hold a burst of poll results for review. Empty polls are not the same cost as n8n executions — Zapier meters tasks when the Zap runs work. Complexity and vendor rate limits remain even when a given empty check is not a task.
Make:
| Behavior | What to remember |
|---|---|
| Polling Watch | Runs on the scenario schedule; empty = a check run in History |
| Instant webhook | Default: run immediately, in parallel |
| Scheduled webhook | Queue, then drain on the schedule |
| Default Maximum number of results | 2 — an hourly schedule with a growing queue drains two items an hour |
| Queue full | 400 Queue is full; inbound over 300 / 10s is 429 |
If you “made it a webhook” on Make and then scheduled the scenario, you built a queue. That is a valid pattern. It is not instant. Raise Maximum number of results or you will debug “missing events” that are sitting in the queue.
n8n meters executions. A Schedule that always runs the HTTP node costs a run every tick, including “nothing new.” Build the cheap-exit in the graph or accept the tick cost.
Upgrading a Zapier plan only for faster polls is sometimes rational. Rebuilding on a push-capable rail is sometimes cheaper over a year. Do the math on your volume, not on a blog’s.
How do you detect a dead webhook subscription?
A webhook that has been quiet for three days is not “stable.” It is unproven.
| Signal | Healthy | Dead or dying |
|---|---|---|
| Events / day vs baseline | Inside the band | Zero during the business window |
| Vendor delivery log | 2xx | 4xx/5xx, or no attempts |
| n8n Executions | Production URL, published workflow | Only test-URL runs, or nothing |
| Synthetic / canary | Weekly hit lands | Canary missing; subscription gone in the vendor UI |
| Make hook | Attached to an active scenario | 410 after five idle days |
Checklist after go-live:
- Baseline: expected events/day (even a rough band)
- Alert if count = 0 for N hours during the business window
- Synthetic event weekly in staging, or a canary record in prod
- Log vendor delivery failures if the platform exposes them
- On credential or URL change, re-verify the subscription in the vendor UI
- Document who can re-create the webhook without guessing
- Confirm the vendor is on the production URL, not the test URL
- If GitHub: a scheduled job that lists failed deliveries and redelivers — they will not do it for you
Signature failures belong in webhook security. A 401/403 storm is a dead subscription with extra steps: the vendor is talking, then they stop.
Can n8n schedules pile up or fire at the wrong hour?
Yes. Overlap and timezone are the two schedule bugs that look like “n8n is random.”
If a schedule fires while the previous run still holds work, you can overlap writes unless you:
- Set workflow concurrency / queue settings for your hosting mode
- Make the job idempotent
- Use a lock row or run token in your backend
- Prefer “skip if running” where you have it
Overlapping crons that both “catch up” the same window are a classic duplicate-send source. Design for overlap. Do not assume serial perfection.
Timezone, from n8n’s Schedule docs and common issues:
| Layer | Who wins |
|---|---|
| Workflow timezone | If set, the Schedule uses it |
| Instance timezone | Fallback if the workflow has none |
| Self-hosted default | America/New_York unless you set GENERIC_TIMEZONE |
| n8n Cloud default | Detected at signup, else GMT |
Wrong-hour checklist:
- Workflow timezone set on purpose, not inherited by accident
- Self-hosted
GENERIC_TIMEZONEmatches the runbook (set the timezone) - Cron expression validated on crontab guru after dropping the optional seconds column
- Day-of-month
30or31accepted as “will not fire in February” - Interval change republished; next fire computed from publish time
- DST: America/New_York springs forward — a 2:30 AM job can vanish
n8n cron can use six fields (seconds optional). Copy-pasting a five-field Unix cron into a six-field box shifts every column. That is how a daily 9:00 becomes “every 9 seconds” in a war room.
When is email or IMAP a bad trigger?
Email-as-trigger looks flexible and fails dirty. Use it for notify paths. For money or CRM truth, prefer API webhooks or scheduled API pulls with a schema contract.
| Problem | Why it hurts |
|---|---|
| HTML vs plain variants | One vendor footer change breaks extraction |
| Duplicate delivers / threading | Harder idempotency than an event id |
| Latency and mailbox auth | OAuth expiry looks like “no mail” |
| Untrusted content | Automation now executes whoever can send to that inbox |
| No watermark | “Unread” is not a cursor; humans mark things read |
Decision list:
- Is the message body your schema? If yes, stop. Use an API.
- Is the side effect reversible? If no, stop.
- Can a stranger’s email fire the workflow? If yes, you built an open webhook with worse auth.
- If you still need mail, treat it as a human queue: notify, do not write the CRM from the MIME.
IMAP polling is still polling. It has every poll miss mode, plus a mailbox. That is not a third trigger type. It is the worst one.
Trigger choice worksheet
Print this. Fill it before you add the node.
- Does the vendor document webhooks or events?
- Max acceptable lag, in minutes, signed by the business owner?
- What does silence look like for this trigger?
- Who gets the heartbeat alert, and in which window?
- Is a reconciliation cron in scope for v1?
- Idempotency key defined — event id, or source id + verb?
- Vendor retry + timeout read and pasted into the runbook?
- Production URL (not test) confirmed in the vendor UI?
- Timezone named (
America/New_Yorkor other), not “server default”? - Dual-count query written (source count vs destination count)?
If (5) is “later” on a revenue path, you are accepting silent gaps. If (7) is blank, you are treating Stripe, GitHub, and a random SaaS as the same product.
Lane page for the rest of the operating system: automation.
FAQ
What if the vendor has no webhooks?
Use a schedule or poll with an explicit lag SLA, a watermark, pagination, and a reconcile job. Do not market it as real-time. Document the maximum delay the business accepted, and rename the runbook so nobody quotes “instant” from a Watch module.
How often should I poll?
Set the interval from business lag and rate limits, not from the fastest plan toggle. Start coarser, measure missed-event rate, then tighten. Faster polls without watermarks just fail faster, and they still miss create-then-delete gaps between ticks.
How do Zapier polling intervals affect cost?
Intervals are plan-dependent per Zapier’s trigger docs — Free is documented at 15 minutes, paid tiers tighter; verify your workspace. Tighter polls can detect sooner but do not remove reconciliation, and billable actions still meter when work runs. Instant triggers avoid poll cadence when the app supports them.
How do I detect a dead webhook subscription?
Alert on unexpected zero traffic, run a periodic synthetic event, and re-check the vendor subscription after any URL or credential change. Error workflows alone will not fire if no delivery attempt reaches you. On Make, an unattached hook can go 410 after five days.
Can schedule triggers pile up in n8n?
Yes when a previous execution is still running. Use concurrency controls, idempotency, and locks so overlapping ticks cannot double-apply. Assume overlap will happen under load, and treat a timezone miss as a separate bug from pile-up.
When is email/IMAP a bad trigger?
When the message body is your schema and the side effect is irreversible. Prefer API events or scheduled pulls for CRM and finance; keep mail for notifications and human-in-the-loop queues. Unread state is not a watermark.
CTA
Choose the trigger you can prove is alive — then add the other as a safety net when money moves.
Read the handbook, then use automation or book the audit.
What questions does this article answer?
- What if the vendor has no webhooks?
- Use a schedule or poll with an explicit lag SLA, a watermark, pagination, and a reconcile job. Do not market it as real-time. Document the maximum delay the business accepted, and rename the runbook so nobody quotes "instant" from a Watch module.
- How often should I poll?
- Set the interval from business lag and rate limits, not from the fastest plan toggle. Start coarser, measure missed-event rate, then tighten. Faster polls without watermarks just fail faster, and they still miss create-then-delete gaps between ticks.
- How do Zapier polling intervals affect cost?
- Intervals are plan-dependent per Zapier's trigger docs — Free is documented at 15 minutes, paid tiers tighter; verify your workspace. Tighter polls can detect sooner but do not remove reconciliation, and billable actions still meter when work runs. Instant triggers avoid poll cadence when the app supports them.
- How do I detect a dead webhook subscription?
- Alert on unexpected zero traffic, run a periodic synthetic event, and re-check the vendor subscription after any URL or credential change. Error workflows alone will not fire if no delivery attempt reaches you. On Make, an unattached hook can go `410` after five days.
- Can schedule triggers pile up in n8n?
- Yes when a previous execution is still running. Use concurrency controls, idempotency, and locks so overlapping ticks cannot double-apply. Assume overlap will happen under load, and treat a timezone miss as a separate bug from pile-up.
- When is email/IMAP a bad trigger?
- When the message body is your schema and the side effect is irreversible. Prefer API events or scheduled pulls for CRM and finance; keep mail for notifications and human-in-the-loop queues. Unread state is not a watermark.
Last reviewed
Automation
Automation After the show is not you at 1 a.m.
Post-show onboarding — thank-you, join path, merch nudge — belongs in a human-gated n8n rail, not your thumb at load-out.
Automation Paperwork that is not the plant
Invoice and PO matching, intake, and support triage in n8n with Metrc fences — the paperwork operators hate, not a menu widget.
Automation Saturday still books — the missed-call rail for trades
A missed-call text-back that routes zip and books a slot beats voicemail and Saturday desk coverage you cannot keep staffed. If a kid is cheaper, say so.
Automation Why doesn’t worker concurrency cap my n8n sub-workflows
Worker concurrency does not cap n8n sub-workflows. Each Execute Workflow child is a new execution the production limit skips, usually on the parent worker.
Will's Journal in your inbox.
What I learned this week building for shops, floors, and houses.
You're on the list.
Sign-up failed — try again.
By subscribing, you agree to the Privacy Policy.