CMS Webhooks: Trigger Front-End Rebuilds on Publish

Stop redeploying by hand — let your CMS tell Vercel, Netlify, or Cloudflare Pages when content changes.

Hamed Pakdaman Hamed Pakdaman
June 18, 2026 · 11 min read
CMS Webhooks: Trigger Front-End Rebuilds on Publish

A static front-end only knows what existed at build time. Publish a post at 09:00 and the Vercel deployment you shipped at 08:45 will keep serving readers a site where that post doesn't exist — for hours, until someone remembers to click "Redeploy."

CMS webhooks fix that gap. The CMS notifies your infrastructure the moment content changes, and your front-end rebuilds itself with zero human involvement.

TL;DR

  • A CMS webhook is an HTTP POST your CMS sends to a URL you choose whenever a content event fires (post published, updated, deleted).
  • Static and ISR front-ends go stale without them — webhooks are how Vercel, Netlify, and Cloudflare Pages know to rebuild.
  • Always verify the HMAC signature before trusting a webhook. It's about 10 lines of Node.
  • Webhooks beat polling on latency and cost, and beat manual rebuilds on reliability. Comparison table below.
  • We build UnfoldCMS, which ships outgoing HMAC-signed webhooks — it's the worked example throughout this post.

What Are CMS Webhooks?

A CMS webhook is an outbound HTTP POST request the CMS fires at a URL you register whenever a specific content event happens — a post gets published, updated, or deleted. The request body describes the event, and a signature header proves it really came from your CMS. Your endpoint reacts: rebuild, purge, reindex.

Think of it as the inverse of an API call. With a REST API, your front-end asks the CMS "what's new?" With a webhook, the CMS tells you — exactly once, exactly when something changed.

A typical subscription has three parts:

  1. Target URL — where the POST goes (a deploy hook, a serverless function, your own server).
  2. Event list — which events you care about. Subscribing to post.published but not post.updated is normal if you only rebuild on new content.
  3. Signing secret — a shared secret used to compute an HMAC signature on every delivery, so your endpoint can reject forged requests.

In UnfoldCMS, each of these lives on a WebhookSubscription record you manage through the admin API. The secret is write-only — it's never serialized back out in API responses, so a leaked API token doesn't leak your signing secrets.


Why Do Static Front-Ends Go Stale Without Them?

Because static-site generators and ISR frameworks bake content into HTML at build time. The deployed site is a snapshot. If nothing triggers a new build, the snapshot never changes — your CMS database can be hours or days ahead of what visitors actually see.

This is the structural trade-off of the headless architecture: you decoupled the front-end from the CMS, which means the CMS can no longer re-render pages for you. Something has to bridge the gap, and you have three options:

Manual rebuild Polling Webhooks
Latency Minutes to hours (whenever a human remembers) Up to one full poll interval (often 5–60 min) Seconds after publish
Wasted builds None, but missed builds instead High — most polls find nothing changed None — builds only on real changes
Editor experience Editors need deploy access or must ping a dev Editors wait and wonder Publish button just works
Infrastructure cost Free but fragile Cron job + API quota burn One HTTP endpoint
Failure mode Stale site, silently Stale site for one interval Missed delivery (mitigated by logs + retries)

Polling deserves one honest concession: it's simple and it can't be blocked by a firewall, since the connection goes outward from your infra. For a hobby site rebuilding nightly, a cron job is fine. For anything where editors expect publish-means-live, webhooks win and it's not close.

The math is straightforward. A site polling every 10 minutes makes 4,320 API calls a month to detect maybe 20 real content changes — a 99.5% waste rate, with an average 5-minute staleness window on top. A webhook makes 20 calls with near-zero staleness.


How Do You Verify a Webhook Signature?

Recompute the HMAC-SHA256 of the raw request body using your shared signing secret, then compare your result against the signature header with a constant-time comparison. If they match, the payload came from someone holding the secret — your CMS. If they don't, drop the request with a 401.

Why bother? Your webhook endpoint is a public URL that does something expensive (trigger a build) or destructive (purge a cache). Without verification, anyone who discovers the URL can hammer it. With Vercel and Netlify billing build minutes, an unverified rebuild endpoint is an open invitation to drain your quota.

The verification steps:

  1. Read the raw request body as bytes — before any JSON parsing.
  2. Read the signature header. UnfoldCMS sends a hex-encoded HMAC-SHA256 digest in the Signature header; other systems use names like X-Hub-Signature-256.
  3. Compute HMAC-SHA256(rawBody, secret) yourself.
  4. Compare the two with crypto.timingSafeEqual — never ===, which leaks timing information byte by byte.
  5. Only then parse the JSON and act on it.

Here's the whole thing in Node, framework-agnostic:

import crypto from "node:crypto";

export function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHeader ?? "", "hex");

  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

And wired into a serverless-style handler:

export async function POST(request) {
  const rawBody = await request.text(); // raw — do NOT use request.json() first

  if (!verifyWebhook(rawBody, request.headers.get("signature"), process.env.WEBHOOK_SECRET)) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(rawBody);
  // event describes what changed — trigger your rebuild here

  return new Response("ok", { status: 200 });
}

The classic mistake: parsing the JSON, then re-serializing it to compute the HMAC. Key ordering and whitespace differences will produce a different digest than the sender's, and every delivery fails verification. Always sign-check the exact bytes that arrived.


What Can You Trigger With a Publish Webhook?

Anything with an HTTP endpoint. In practice, three patterns cover almost every headless setup: deploy hooks that rebuild the front-end, cache purges that evict stale HTML at the edge, and search reindex jobs that keep your site search in sync with content.

1. Deploy hooks (the big one). Every major host gives you a unique build-trigger URL:

  • Vercel — create a Deploy Hook under Project Settings → Git, paste it as your webhook target, and every publish rebuilds your site. Full walkthrough in our Vercel deployment guide.
  • Netlify — Build Hooks under Site Configuration → Build & deploy work identically.
  • Cloudflare Pages — Deploy Hooks under Settings → Builds & deployments. We cover the end-to-end setup in the Cloudflare Pages guide.

One caveat: deploy hook URLs accept bare POSTs with no signature check — the secrecy of the URL is the auth. That's acceptable because the worst an attacker can do is trigger builds. For your own endpoints that do more than rebuild, verify signatures as shown above.

2. Cache purge. If you render server-side behind a CDN, a publish webhook can call the Cloudflare or Fastly purge API for the affected URLs instead of rebuilding anything. Latency drops from a minutes-long build to a sub-second purge.

3. Search reindex. Sites running Algolia, Meilisearch, or Typesense need the index updated when content changes. A small webhook consumer that upserts the changed post (or deletes it on post.deleted) keeps search results honest without nightly full reindexes.

You can chain these: one webhook subscription pointing at a tiny serverless function that purges cache, reindexes search, and then pings the deploy hook. See how the pieces fit on a live install — explore the UnfoldCMS demo and poke at the admin API yourself.


What Happens When a Webhook Delivery Fails?

Deliveries fail — endpoints time out, DNS hiccups, your serverless function cold-starts past the sender's timeout. A webhook system you can trust needs three things: a record of every delivery attempt, visible success/failure status per subscription, and a way to re-fire an event so a missed delivery doesn't mean permanently stale content.

When you evaluate any CMS's webhooks (ours included), check for:

  • Delivery logs. Can you see that the publish event at 09:02 actually got a 200 back? UnfoldCMS records every delivery attempt, and each subscription tracks total deliveries, failed deliveries, and last-delivered / last-failed timestamps.
  • A test fire. Debugging by repeatedly publishing real posts is miserable. UnfoldCMS exposes POST /api/v1/admin/webhooks/{id}/test so you can send a test delivery to your endpoint on demand and watch the logs.
  • Retry behavior. Many webhook senders retry failed deliveries with backoff. Don't assume — read your CMS's docs and design your consumer to be idempotent either way: receiving the same event twice should produce the same end state. For rebuild triggers that's automatic (two builds of the same content are identical); for search upserts, key on the post ID.
  • Failure isolation. One dead subscriber shouldn't block the others. UnfoldCMS logs the failure, bumps the failure counter, and keeps fanning out to remaining subscriptions.

Respond fast, too. Return 200 before doing slow work — acknowledge the delivery, then run the build trigger asynchronously. A consumer that takes 30 seconds to respond looks like a failed delivery to the sender.


How Do You Set This Up in UnfoldCMS?

Create a webhook subscription through the admin API with your target URL, the events you want, and a signing secret — done. Every matching content event then fires a signed POST at your URL. Full disclosure: we build UnfoldCMS, so this is the implementation we know line by line.

The whole flow, assuming a Sanctum admin token (covered in the API v1 launch post):

curl -X POST https://your-site.com/api/v1/admin/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.vercel.com/v1/integrations/deploy/prj_xxx/yyy",
    "events": ["post.published", "post.updated", "post.deleted"],
    "secret": "your-signing-secret"
  }'

A few implementation details worth knowing:

  • The secret is stored on the subscription and used to sign every payload with HMAC-SHA256 (hex digest in the Signature header) — but it's never returned by the API after creation. Write-only by design.
  • Outbound URLs pass an SSRF guard at send time: targets resolving to private or reserved IP ranges are blocked and logged, which stops a compromised admin token from turning your CMS into an internal-network probe.
  • Subscriptions are full CRUD — list, update the event list, rotate the secret, delete — all under /api/v1/admin/webhooks.

That's the entire integration surface: one API call on the CMS side, one deploy hook (or ~25 lines of verified handler) on the front-end side. Compare that with babysitting a polling cron forever. Browse the docs to wire up your first subscription, or check the feature list for everything else the API ships.


FAQ

Do I need signature verification if I'm only triggering a Vercel deploy hook? No — deploy hook URLs are themselves secrets, and the only thing a forged request can do is trigger a build. Add verification the moment your endpoint does anything more: purging caches, writing to a search index, or touching any system where a forged payload causes real damage.

Should I rebuild on post.updated or only on post.published? Both, almost always. Fixing a typo in a live post is an updated event, and readers should see the fix. Skip updated only if your build budget is tight and your team batches edits — then pair it with a scheduled daily rebuild as a safety net.

Webhooks fire instantly, but my build takes 4 minutes. Is that a problem? That's normal — the webhook removes the human delay, not the build time. If 4 minutes is too slow, switch the heavy pages to ISR or server rendering with a cache-purge webhook instead of full rebuilds; purges land in under a second.

What's the difference between incoming and outgoing webhooks? Outgoing webhooks are what this post covers: the CMS sends events out to your endpoints. Incoming webhooks would be external services pushing data into the CMS. UnfoldCMS ships outgoing content webhooks; payment processor callbacks (Stripe, BTCPay) are the only incoming hooks it handles, and those are internal to checkout.


Sources and Methodology

Implementation details about UnfoldCMS (the Signature header, hex HMAC-SHA256 over the JSON body, write-only secrets, per-subscription delivery counters, the SSRF guard, and the /api/v1/admin/webhooks endpoints) were verified directly against the UnfoldCMS source code in June 2026 — we build it, so the code is the source of truth. Deploy hook behavior references the official Vercel, Netlify, and Cloudflare Pages documentation as of the same date. The polling math (4,320 calls/month at a 10-minute interval) is arithmetic, not measurement: 6 polls/hour × 24 × 30. Node.js verification code targets the built-in node:crypto module, no dependencies.

Related: Headless CMS Architecture Explained · REST vs GraphQL for an API-First CMS · UnfoldCMS Public API v1 Launch

Share this post:

Discussion

Comments (0)

Leave a Comment

Please log in to leave a comment.

Don't have an account? Register here

No comments yet. Be the first to share your thoughts!

Keep Reading

Related Posts

Back to all posts