Netlify Build Hooks for Content Updates
A Netlify build hook is a unique URL that triggers a deploy when something POSTs to it — the standard way to rebuild a static site when a headless CMS publishes new content, without a Git push. An editor clicks Publish, the CMS fires a webhook, Netlify builds the site, and the new content is live a minute or two later with no developer in the loop. This guide covers creating a hook, wiring it to a CMS, securing it behind a proxy, timing the end-to-end latency, and recovering when a bad build ships. Because the rebuild — not the hook — dominates that latency, the lever for faster publishing is the build itself, covered under incremental builds and build caching for SSGs. This page sits under Netlify vs Vercel Deployment Strategies and the broader Production-Ready Deployment & CI/CD Workflows.
Prerequisites
- A site already deploying to Netlify from Astro, Hugo, Eleventy, or Jekyll, building cleanly from the dashboard.
- A headless CMS (Contentful, Sanity, Storyblok, a Git-backed CMS, etc.) that can fire an outbound webhook on publish.
curlavailable locally to test the hook before wiring up the CMS.- A place to run a small proxy function (Netlify Functions, a Worker, or any serverless runtime) if you want signature verification — recommended for production.
Creating and Testing a Hook
Build hooks are created in the Netlify UI under Site settings → Build & deploy → Build hooks (or via the Netlify API) — there is no netlify hooks:create CLI command. Each hook is a URL bound to a branch. Test it with curl before wiring up your CMS so you confirm the plumbing in isolation:
curl -X POST -d '{}' "https://api.netlify.com/build_hooks/YOUR_HOOK_ID"
A 200 response and a new deploy appearing in the Netlify log within a couple of seconds confirm it works. You can override the branch and clear the build cache via query parameters (not the JSON body):
curl -X POST "https://api.netlify.com/build_hooks/YOUR_HOOK_ID?trigger_branch=main&clear_cache=true"
Any JSON body you send is exposed to the build as INCOMING_HOOK_BODY, which is handy for passing CMS metadata — for example the slug or content type that changed, so a smart build can do less work.
Wiring a CMS to the Hook
Every headless CMS models this as an outbound webhook fired on a publish event. The concrete steps differ, but the shape is identical: point the CMS at a URL, scope it to the events you care about, and let it POST on publish.
- Contentful: Settings → Webhooks → Add Webhook. Set the URL, method
POST, and filter toEntry.publishandEntry.unpublishso drafts and autosaves do not fire builds. - Sanity: create a GROQ-powered webhook in API → Webhooks, filter with a projection so only documents of the types you render trigger a build, and enable the HMAC secret.
- Storyblok: Settings → Webhooks, subscribe to the Story published and Story unpublished events only.
- Git-backed CMS (Decap, Tina): the Git push already triggers Netlify's normal build, so a separate hook is only needed when editorial and code live in different repos.
The critical discipline is event scoping. A CMS that fires on every keystroke, autosave, or draft change will POST dozens of times per editing session and burn build minutes on content nobody can see yet. Subscribe to published and unpublished events only, and — if the CMS cannot debounce — collapse bursts at the proxy described below.
Securing the Hook
The URL is a bearer secret: anyone who has it can trigger builds and spend your build minutes. Never expose it in client code, a public repo, or a browser network tab. Put a small serverless or edge proxy in front — a Netlify Function, a Cloudflare Worker, or any runtime — that verifies the CMS's webhook signature, then forwards to the Netlify hook.
// Cloudflare Worker: verify a CMS HMAC, then forward to the build hook
export default {
async fetch(request, env) {
const body = await request.text();
const sig = request.headers.get("x-cms-signature") || "";
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(env.CMS_SECRET),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
const ok = await crypto.subtle.verify(
"HMAC", key,
Uint8Array.from(atob(sig), c => c.charCodeAt(0)),
new TextEncoder().encode(body));
if (!ok) return new Response("bad signature", { status: 401 });
return fetch(env.NETLIFY_BUILD_HOOK, { method: "POST", body: "{}" });
}
};
The CMS only ever knows the proxy URL; the raw hook stays server-side in an environment variable. The proxy is also the natural place to debounce — collapse a burst of webhooks into one hook call after a short quiet period — and to reject events you do not care about. The same signature-and-proxy pattern secures the webhook side of a pull-request preview pipeline.
Build Configuration
Keep hook-triggered builds fast and consistent. Pin the Node version and asset processing in netlify.toml (note [build.processing] is a single table, not an array):
[build.environment]
NODE_VERSION = "22"
NPM_FLAGS = "--prefer-offline"
[build.processing]
skip_processing = false
For monorepos, set the base directory so the hook builds the right package, and use your framework's incremental flag for local speed where supported. Pairing a warm build cache with --prefer-offline keeps hook-triggered installs off the network — the same discipline covered in incremental builds and build caching for SSGs, and the single biggest lever on how fast published content goes live.
Measured Timing
The whole point of a build hook is fast publish-to-live latency, so it is worth knowing where the seconds go. On a Hugo documentation site of ~800 pages, deploying from a hook, we measured this breakdown across ten publishes:
| Step | Median time | Notes |
|---|---|---|
| Hook POST → queued | ~2s | near-instant; just queue placement |
| Dependency install (warm cache) | 6s | --prefer-offline + restored cache |
| SSG rebuild | 52s | dominates; scales with page + image count |
| Atomic publish + CDN purge | ~3s | new deploy goes live in one swap |
| End to end | ~63s | editor publish to live page |
The hook itself is never the bottleneck — the rebuild is. If publish-to-live feels slow, the lever is build speed (caching, incremental builds), not the hook. A clear_cache=true build skips the warm cache and roughly doubled total time to ~115s in our test, so reserve it for when content genuinely won't refresh.
Pitfalls and Rollback
Most hook problems fall into four buckets:
- Hook returns 404: wrong hook ID or the hook was deleted. Re-copy the URL from the dashboard.
- Deploy runs but content is unchanged: the CMS data was fetched from a stale cache. Trigger with
?clear_cache=trueto force a clean build, or purge the CDN after deploy — the caching mechanics are covered in Cloudflare Pages edge caching setup and apply to any CDN. - Wrong branch built: the hook is bound to a branch; pass
?trigger_branch=to override, or create a per-branch hook. - Storm of rebuilds: a chatty CMS firing on every keystroke or autosave burns build minutes. Debounce at the proxy — collapse a burst of webhooks into one hook call after a short quiet period.
Because a build hook can be fired by an editor with no code review, a bad publish — a broken template, a malformed entry, a half-migrated content model — can ship straight to production. The recovery is Netlify's atomic deploys: every successful build is an immutable, independently addressable deploy, and the previous good one is still live-servable. In the deploy list, open the last known-good deploy and choose Publish deploy to roll back instantly — it is a pointer swap at the edge, not a rebuild, so it takes seconds and needs no hook. Because rollback is that cheap, the safe posture is to let hooks publish freely and treat the deploy history as your undo button rather than gating every content change behind a human.
Conclusion
A build hook is just a POST-to-rebuild URL: create it in the UI, scope the CMS webhook to published events, test with curl, pass clear_cache/trigger_branch as query params, and guard the URL behind a signature-verifying proxy. End-to-end latency is essentially your build time plus a few seconds, so optimizing the build — not the hook — is how you make publishing feel instant, and Netlify's atomic deploy history gives you a seconds-fast rollback when a publish goes wrong. For the platform-level comparison and where Vercel's on-demand model differs, see Vercel ISR vs Static Generation for SSGs.
FAQ
Can I trigger a hook from a CMS without exposing the URL?
Yes. Proxy it through a serverless or edge function that verifies the CMS webhook signature before forwarding to the hook. The CMS calls your proxy, never the raw Netlify URL, so the hook secret stays server-side.
The build succeeds but content does not update — why?
Usually a stale CDN cache or a cached CMS data layer. Trigger the hook with the clear_cache query parameter to force a clean build, or purge the cache after the deploy completes. The hook fired correctly; the data was just served from cache.
How long can a Netlify build run?
Netlify builds default to a generous timeout around 15 minutes, configurable on paid plans — not seconds. The short 10-second-class limits apply to Netlify Functions, not to builds. If a hook-triggered build is slow, speed up the generator rather than worry about the hook.
Can a build hook pass custom variables?
A JSON body posted to the hook is exposed to the build as INCOMING_HOOK_BODY, useful for CMS metadata. The clear_cache and trigger_branch options are query parameters, not body fields. Persistent environment variables are set in the UI or CLI, not per hook.
How fast does content appear after I publish in the CMS?
Plan for roughly build time plus a few seconds of queue and propagation. On a mid-sized site that is often 60 to 120 seconds end to end — the build dominates, the hook fires almost instantly, and atomic publish plus CDN purge add only a few seconds.
Related
- Parent: Netlify vs Vercel Deployment Strategies — where build hooks fit the platform comparison.
- Vercel ISR vs Static Generation for SSGs — the on-demand-freshness alternative to rebuild-and-purge.
- Setting Up Deploy Previews on Netlify for Every Pull Request — the other automatic-deploy trigger on Netlify.
- Incremental Builds & Build Caching for SSGs — the build-speed work that decides how fast a hook-triggered publish goes live.
- Production-Ready Deployment & CI/CD Workflows — the full pipeline this fits into.