Skip to content

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.

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.

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 itself
const 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 parse

verifyWebhook 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.

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 2xx is 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-After is honoured on a 429 or 503: 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 3xx is 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 Gone turns the webhook off. Answer 410 from an endpoint you’ve retired and Ghost Writr stops sending to it at once, with a notice in your dashboard.
  • Only the newest feed.updated keeps 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 fresh Webhook-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 2xx in 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.

How you refresh depends on how your site renders. Each SDK has a dedicated page for the wiring.

Render modelStrategyWhat the handler does
Next.js (ISR)On-demand revalidationVerify, then revalidateTag / revalidatePath so the next request re-fetches. See Next instant revalidation.
Astro (static)Redeploy hookVerify, then trigger a rebuild/redeploy of the static site so the new build is generated. See Astro: redeploy on publish.
React Router / SSRCDN purgeVerify, 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.