Webhooks, Poll SCM, Cron, and Build Causes
Scenario: Hundreds of jobs poll each minute, loading SCM while still delivering delayed feedback.
Three ways a build can start, and why the choice matters
A build doesn't just start because someone clicked a button. Jenkins supports several different ways a job can be triggered automatically, and each one makes a genuinely different promise about when and why a build happens - mixing them up carelessly causes real, confusing incidents.
Analogy: Think of three different ways a restaurant kitchen learns it has a new order. A webhook is like a waiter running straight to the kitchen the instant a customer orders - fast, event-driven, but if the waiter trips and never makes it (a dropped webhook delivery), the kitchen never finds out. Poll SCM is like the kitchen periodically walking out to check the order board itself, on its own schedule, whether or not anything actually changed - reliable, but slower and wasteful if checked too often. Cron is a kitchen that simply cooks a fresh batch of soup every hour on the hour, completely regardless of whether any new orders came in at all - useful for "always keep something fresh" tasks, but the wrong tool if what you actually care about is reacting to a specific new order.
Choosing the right trigger type - and understanding what happens when it fails - is exactly what this section is about.
Pipeline example
pipeline { agent any; triggers { pollSCM('H/15 * * * *'); cron('H 2 * * *') }; stages { stage('Cause') { steps { echo 'inspect cause' } } } }
Push, poll, and schedule make different promises
Webhooks deliver low-latency notification the instant a relevant SCM event happens, and should always carry a validated cryptographic signature from the provider so a forged request can't fake a trigger. Poll SCM asks, on its own schedule, "has anything relevant actually changed since last time?" and only builds if the answer is yes. Cron schedules a build regardless of whether the source changed at all - genuinely useful for periodic maintenance tasks, nightly builds, or scheduled report generation, but the wrong choice if what you actually want is "build only when code changes." Jenkins' hash syntax H (as in H/15 ) assigns a stable, per-job pseudo-random offset within the schedule, deliberately spreading many jobs' scheduled times apart instead of creating a stampede of jobs all firing at exactly midnight or exactly the top of the hour.
Triggered by SCM change: refs/heads/main
Polling SCM changes on main
Received commit notification for a1b2c3d
Scheduling a build...
This log fragment shows a webhook-driven trigger firing cleanly - a specific commit was received and a build was scheduled in direct response, not on a fixed timer.
When webhooks fail, polling is the safety net - if it exists
Webhooks can genuinely be lost - during a maintenance window, a network blip, or a misconfiguration on the provider's side - so a lower-frequency periodic scan exists specifically to reconcile any changes that were missed. Enabling several overlapping trigger mechanisms carelessly (webhook and frequent polling and a cron schedule, all pointed at the same job) risks building the exact same revision twice, unless deduplication and concurrency controls are explicit about it. A purely timer-driven maintenance build must record precisely which revision it actually selected and built, rather than implicitly and silently deploying "whatever the current head happens to be" at the moment it ran - which can differ from what you expect if a commit landed in the gap between the timer firing and the checkout completing.
Causes are the audit trail worth trusting
Jenkins attaches a cause - user-triggered, timer, SCM change, upstream job, or remote API call - to every numbered build. Preserve these causes (and any non-secret parameters) for routing decisions and later audit review; free-text console log messages are not a reliable substitute for structured cause data, since they can be inconsistent or missing entirely. Plugin-specific cause classes vary across different trigger plugins, so any policy logic reading causes should fail safely - defaulting to the more cautious behavior - when it encounters a cause type it doesn't specifically recognize.
A pitfall: "the webhook returned 200, so the build must have happened"
Trace a genuinely missing build methodically, in this order: provider delivery logs, reverse-proxy logs, Jenkins' own webhook endpoint and its signature validation, branch indexing (if using Multibranch), the job's trigger configuration, any configured quiet period, the build queue, and finally executor allocation. A webhook provider reporting a 200 response from Jenkins is evidence the HTTP request was received - it is not proof that a build actually started, since Jenkins could have accepted the request and then rejected it for an unrelated reason further down that chain. Similarly, a queue entry existing is not proof that execution ever actually began. Make revision-triggering logic idempotent wherever practical, so a reconciliation scan catching a missed webhook doesn't end up publishing a duplicate version of something that already shipped through the webhook path moments earlier.
Goal: Practice the concrete behavior in jenkins-scm-trigger-recovery. Learn sections have no Jenkins terminal; the Jenkins lab opens the real shared service in a new full-screen tab inside your private folder.