Instant updates
The content feed changes only when you publish. Instead of waiting on a revalidation timer, you can subscribe to a signed webhook and refresh the instant a rebuild lands.
The feed.updated webhook
Section titled “The feed.updated webhook”When a site’s static feed rebuilds, Ghost Writr POSTs a small JSON body to your registered URL:
interface FeedUpdatedPayload { event: "feed.updated"; siteId: string; buildId: string;}buildId is the new build the manifest now points at. Your handler doesn’t need to trust the body’s contents beyond “something changed” — the next read resolves the manifest and picks up the new build automatically. Your job is just to invalidate whatever cached the previous build.
Verify the signature
Section titled “Verify the signature”Each delivery carries three headers — Webhook-Id, Webhook-Timestamp, and Webhook-Signature (v1=<hex>, an HMAC-SHA256 over the id, timestamp, and raw request body under a shared secret). Signing the id and timestamp together makes it replay-safe. Verify against the raw bytes before acting — parsing the body first and re-serializing it will not match.
import { verifyWebhook } from "@ghostwritr/feed";
const raw = await request.text(); // the RAW body — verify before JSON.parse// verifyWebhook reads Webhook-Id / Webhook-Timestamp / Webhook-Signature from the headers itselfconst ok = await verifyWebhook(process.env.GHOSTWRITR_FEED_SECRET!, raw, request.headers);if (!ok) return new Response("invalid signature", { status: 401 });
const { siteId, buildId } = JSON.parse(raw); // now safe to parseverifyWebhook is constant-time and returns false for a missing, stale-timestamp, or mismatched delivery (rejecting a timestamp outside the ±300s window as replay defense). The verifier (verifyWebhook) and the header constants (WEBHOOK_ID_HEADER / WEBHOOK_TIMESTAMP_HEADER / WEBHOOK_SIGNATURE_HEADER) are re-exported from @ghostwritr/feed, @ghostwritr/next, @ghostwritr/react-router, @ghostwritr/svelte, and @ghostwritr/vue, so both ends sign identically by construction. They run on Web Crypto — the same in Node 20+ and on the edge.
Delivery and retries
Section titled “Delivery and retries”Every webhook delivery (feed.updated, article.published) is recorded, and one that doesn’t land is retried — so a deploy that briefly breaks your endpoint catches up on its own.
- Any
2xxis a success. Respond fast and do slow work (a rebuild, a purge) after responding; each attempt waits up to 5 seconds for an answer. - Everything else is retried —
3xx,4xx,5xx, timeouts, and connection errors. After each failed attempt the next one waits at least 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, then 10 hours (plus a little jitter, and up to a few minutes until the retry queue picks it up): 8 attempts across about a day. Retry-Afteris honoured on a429or503: the next attempt waits at least that long (up to an hour, or twice the scheduled delay when that is longer).- Register the final URL. A
3xxis logged as a failed attempt together with the address it redirects to (so a moved endpoint is easy to spot), and Send test flags it too. 410 Goneturns the webhook off. Answer410from an endpoint you’ve retired and Ghost Writr stops sending to it at once, with a notice in your dashboard.- Only the newest
feed.updatedkeeps retrying. It means “the feed changed”, so when a newer one goes out, older ones still waiting are marked Replaced — your handler re-reads the manifest either way. - Dedupe on
Webhook-Id. A retry reuses the delivery’s id and is re-signed with a freshWebhook-Timestamp, so it always passes the ±300s window. - A webhook whose deliveries keep failing for 5 days is paused, with a notice in your dashboard: once failures have run 5 days with no
2xxin between, the next failed attempt pauses it (a quiet spell of more than 3 days between failures starts the count again). Fix the endpoint and switch it back on: deliveries that were waiting resume, and the delivery log resends any event on demand.
Three freshness strategies
Section titled “Three freshness strategies”How you refresh depends on how your site renders. Each SDK has a dedicated page for the wiring.
| Render model | Strategy | What the handler does |
|---|---|---|
| Next.js (ISR) | On-demand revalidation | Verify, then revalidateTag / revalidatePath so the next request re-fetches. See Next instant revalidation. |
| Astro (static) | Redeploy hook | Verify, then trigger a rebuild/redeploy of the static site so the new build is generated. See Astro: redeploy on publish. |
| React Router / SSR | CDN purge | Verify, then purge the affected paths from your CDN/cache so the edge re-fetches the feed. See React Router feed-freshness webhook. |
The throughline is the same: verify the signature, then invalidate the cache layer your renderer relies on. The next read walks the manifest and serves the new buildId.