Netlify Build Hooks: Automate Deploys with Webhooks
TL;DR: A Netlify build hook is a unique URL that triggers a deploy when you send a POST request to it. That is the whole mechanism, and its simplicity is the point. You create a hook in your site's build settings, choose which branch it builds, and get a URL containing a random identifier. Anything that can make an HTTP request can now deploy your site: a headless CMS webhook when content is published, a cron job for scheduled rebuilds, a CI pipeline in another repository, a script on your laptop. The URL is the credential, so treat it as a secret: anyone holding it can trigger unlimited builds against your account. The failure modes are predictable. Build storms from chatty webhooks burn through build minutes. Hooks pointed at the wrong branch deploy the wrong thing. And a hook committed to a public repository is an open invitation to exhaust your quota.
What is a build hook and what is it for?
A build hook is a URL. POST to it and Netlify starts a build of the branch you configured when you created it. There is no authentication beyond possession of the URL, no payload requirement, and no complexity.
That makes it the right tool for one specific job: triggering a deploy from something that is not a Git push. Git pushes already trigger builds through the repository connection. Build hooks cover everything else.
The realistic uses:
- Headless CMS publishing. An editor publishes a post in Contentful, Sanity, Storyblok or similar, the CMS fires a webhook at your build hook, and the site rebuilds with the new content. This is the single most common use and the reason build hooks exist.
- Scheduled rebuilds. A site that displays time-sensitive data needs periodic regeneration even when nothing changed. A cron job hitting the hook daily handles it.
- Cross-repository dependencies. A shared component library publishes a new version and the sites consuming it need rebuilding.
- Manual triggers. A button in an internal tool, a Slack command, a script you run when you want a fresh build without an empty commit.
What build hooks are not for: they do not pass data into your build in any meaningful way, they do not replace environment variables, and they do not give you conditional build logic. They start a build. That is it.
How do I create one?
- Open your site in the Netlify dashboard and go to the build and deploy settings.
- Find the build hooks section and add a new hook.
- Give it a descriptive name. This name appears in your deploy log as the trigger reason, so "Contentful publish" tells you something and "hook 1" does not.
- Choose the branch to build. This defaults to your production branch and is easy to leave wrong.
- Save. Netlify generates a URL containing a random identifier.
The URL has this shape:
https://api.netlify.com/build_hooks/YOUR_BUILD_HOOK_ID
Copy it into your secrets store immediately. The dashboard will show it again, but you want it where your other credentials live rather than retrieved from a browser each time.
Trigger it with a plain POST. No body, no headers, no auth:
# Minimal trigger
curl -X POST -d '{}' https://api.netlify.com/build_hooks/YOUR_BUILD_HOOK_ID
That is the entire API. The response tells you the build was queued. Check the deploys list and you will see it running, labelled with the hook name you chose.
Can I pass information to the build?
Partially, and less than you would like.
Netlify supports a title parameter that appears as the deploy message, which is genuinely useful for traceability:
# Label the deploy so the log tells you why it happened
curl -X POST -d '{}' \
"https://api.netlify.com/build_hooks/YOUR_BUILD_HOOK_ID?trigger_title=Content+published"
Now your deploy list reads "Content published" rather than a generic hook message, and when you are looking at six builds from this morning trying to work out which one broke something, that matters.
Some webhook payloads sent to the hook are made available to the build environment, but the reliable pattern is not to depend on it. If your build needs to know something, have it fetch that something from the source of truth rather than trusting it to arrive through a trigger. A build that pulls the current state of your CMS at build time is correct regardless of what triggered it. A build that acts on data embedded in a trigger payload is fragile and hard to reproduce.
Treat build hooks as a signal, not a channel.
How do I wire a CMS to a build hook?
The pattern is the same across every headless CMS.
- Create a build hook in Netlify, named after the CMS.
- In the CMS, find the webhook configuration. It is usually under settings, sometimes under integrations.
- Add a webhook with the build hook URL as the target and POST as the method.
- Choose the events that should trigger it. This is the step that matters.
- Publish something and verify a build starts.
On step four: be restrictive. Most CMS platforms will happily fire a webhook on every event they have, including autosaves and draft updates. Wire that to a build hook and you get a build every few seconds while someone is typing. Trigger on publish and unpublish only. Do not trigger on save, draft creation or preview.
If your CMS does not offer that granularity, put a filter between them: a small serverless function that receives the CMS webhook, decides whether it warrants a build, and forwards to the hook if so. That function is twenty lines and it will save you from a bad afternoon.
How do I schedule periodic builds?
Netlify does not include a general-purpose cron scheduler for triggering full site builds, so you provide the schedule from outside. Any of these work:
- A GitHub Actions workflow on a schedule, calling the hook with
curl. Store the hook URL as a repository secret, never inline. - A scheduled function on any platform that offers one.
- A cron job on a machine that is reliably up. Your laptop is not one.
- A scheduled Claude task, which is a reasonable fit when the rebuild is part of a larger content workflow. The setup is covered in schedule-recurring-claude-tasks.
A GitHub Actions example, with the secret handled properly:
name: Scheduled rebuild
on:
schedule:
- cron: '0 6 * * *' # 06:00 UTC daily
workflow_dispatch: # allow manual runs too
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger Netlify build
run: |
curl -X POST -d '{}' \
"${{ secrets.NETLIFY_BUILD_HOOK_URL }}?trigger_title=Scheduled+rebuild"
The hook URL goes in repository secrets. It never appears in the workflow file, never in the log, never in a commit. Note that scheduled workflows on shared CI runners often start late under load, so do not build anything time-critical on the assumption that 06:00 means 06:00.
Ask yourself whether you need the schedule at all. A daily rebuild of a site whose content changes weekly is six wasted builds. If content changes drive the need, trigger on content change and skip the timer.
How do I secure the hook URL?
The URL is the credential. There is nothing else. Anyone who has it can trigger builds against your account until your minutes run out.
The rules:
- Never commit it. Not in a workflow file, not in a script, not in a comment. Repository secrets, environment variables from a secrets manager, or your platform's secret storage.
- Never put it in client-side code. A hook URL in a browser bundle is public. There is no scenario where a browser should trigger a deploy directly.
- Never log it. A
curl -vin CI prints the full URL into a log that may be publicly readable on an open-source repository. - Use placeholders when sharing.
YOUR_BUILD_HOOK_IDin anything you document, paste or screenshot. - Rotate on exposure. Delete the hook and create a new one. It takes thirty seconds and the old URL dies immediately. There is no partial mitigation here, because the URL is the only thing protecting it.
- One hook per consumer. Separate hooks for your CMS, your cron job and your CI pipeline. Then rotating one does not break the others, and the deploy log tells you which consumer triggered what.
The same reasoning as everywhere else in the deployment stack: bounded scope, cheap rotation, no secrets in artefacts. The Vercel-side version of this argument is in vercel-environment-variables-guide, and the general principle is set out in skill-security-best-practices.
How do I avoid build storms?
A build storm is what happens when a chatty trigger meets an unfiltered hook. Your minutes evaporate, your deploy queue backs up, and the site you actually wanted to update sits behind forty pointless builds.
Prevention:
Filter at the source. Trigger on publish, not on save. This alone prevents most storms.
Debounce if you cannot filter. A serverless function that swallows triggers within a short window of the last one is straightforward and effective. Publish five posts in a minute, get one build.
Watch your usage. Build minutes are finite on every plan. Check them occasionally rather than discovering the limit through a failed deploy.
Cancel stale builds. Netlify can cancel superseded builds so that ten queued builds resolve to one. Enable it.
Question every scheduled trigger. Hourly rebuilds of content that changes weekly is 160 wasted builds a week.
If you find yourself needing a lot of filtering logic, that is a signal the trigger is wrong rather than the hook. Fix the source.
What should happen after the build?
A deploy is not the end of the workflow for a content site. Two things usually need to follow.
Regenerate and submit the sitemap. If your build produces a sitemap, it is current by construction. Telling search engines about it is a separate step, and doing it automatically on deploy meaningfully shortens the gap between publishing and indexing. The pattern is set out in indexnow-sitemap-automation.
Notify on failure. Netlify can send outgoing notifications on deploy events. Wire failures to somewhere you will actually see them. A build triggered by a CMS webhook fails silently by default: the editor thinks they published, nobody is watching the deploy log, and the change is simply not live. That is a worse failure than a noisy one because it can persist for days.
If you are comparing hosts for this kind of content workflow, the equivalent mechanics on Cloudflare are covered in deploy-static-site-cloudflare-pages. The concepts transfer: a trigger URL, a branch target, a build, a deploy.
Frequently Asked Questions
Is a Netlify build hook URL a secret?
Yes, and it is the only protection the hook has. There is no signature, no token and no authentication step beyond possession of the URL. Anyone who obtains it can trigger builds against your site until your build minutes are exhausted. Store it in repository secrets or a secrets manager, never in a committed file or client-side code, and use placeholders like YOUR_BUILD_HOOK_ID in anything you share. If it is ever exposed, delete the hook and create a new one, which immediately invalidates the old URL.
Can I pass data to my build through the hook?
Not reliably, and you should not design around it. Netlify supports a trigger title parameter that labels the deploy in your log, which is worth using for traceability. Beyond that, treat the hook as a signal that something changed rather than a channel for carrying what changed. A build that fetches the current state from your CMS at build time works regardless of what triggered it. A build that depends on data embedded in a trigger payload is difficult to reproduce and breaks when triggered by hand.
How do I schedule a nightly rebuild?
Netlify does not provide a built-in cron scheduler for full site builds, so supply the schedule externally. A GitHub Actions workflow with a schedule trigger calling the hook with curl is the most common approach, with the hook URL stored as a repository secret. Scheduled workflows on shared runners often start late under load, so avoid depending on precise timing. Before setting one up, check whether you need it at all: if content changes are what drive rebuilds, trigger on content change and skip the timer entirely.
Why is my CMS triggering dozens of builds?
Because the webhook is firing on more events than you intended, most likely including saves, drafts or autosaves rather than just publishes. Open the webhook configuration in the CMS and restrict it to publish and unpublish events. If the platform does not offer that granularity, put a small serverless function between the CMS and the hook that decides whether an event warrants a build and forwards only when it does. Also enable Netlify's option to cancel superseded builds so a burst resolves to one deploy.
How do I know when a hook-triggered build fails?
You do not, unless you configure it. A build triggered by a CMS webhook fails silently by default: the editor believes they published, nobody is reading the deploy log, and the change never goes live. Set up Netlify's outgoing notifications for failed deploys and point them somewhere you will actually see, such as email or a chat channel you read. Naming each hook descriptively also helps, because the deploy log then tells you which trigger produced the failing build without any guesswork.