Feed-freshness webhook
React Router SSR reads the current feed on every request, so you only need a webhook when you cache the feed at a CDN. Register a resource route (a route with an action and no UI), point your site’s feed-freshness subscription at it, verify the signature, and purge your cache. Then new articles appear in seconds — see Instant updates.
How it works
Section titled “How it works”When a site’s static feed rebuilds, the engine POSTs a feed.updated body to your registered URL with an X-GW-Signature header — a hex HMAC-SHA256 of the raw request body, keyed by your shared secret. You verify it with verifyFeedSignature against the same raw bytes, then purge.
The resource route
Section titled “The resource route”import { verifyFeedSignature, FEED_SIGNATURE_HEADER, type FeedUpdatedPayload,} from "@ghostwritr/react-router";import type { Route } from "./+types/webhooks.gw-feed";
export async function action({ request }: Route.ActionArgs) { if (request.method !== "POST") { throw new Response("Method not allowed", { status: 405 }); }
// 1. Read the RAW body — HMAC is over these exact bytes. const rawBody = await request.text(); const signature = request.headers.get(FEED_SIGNATURE_HEADER);
// 2. Verify before trusting anything (constant-time, returns boolean). const ok = await verifyFeedSignature(process.env.GHOSTWRITR_FEED_SECRET!, rawBody, signature); if (!ok) { throw new Response("Bad signature", { status: 401 }); }
// 3. Now it's safe to parse. const payload = JSON.parse(rawBody) as FeedUpdatedPayload; // payload.event === "feed.updated", payload.siteId, payload.buildId
// 4. Purge your CDN cache for this site's feed / blog routes. await purgeCdn(payload.siteId);
return Response.json({ ok: true });}FEED_SIGNATURE_HEADER is the constant "X-GW-Signature". verifyFeedSignature(secret, rawBody, signature) returns false for a missing or mismatched signature (constant-time), and true only on a genuine match. It uses Web Crypto, so it runs identically in Node 20+ and on edge runtimes.
Wire it up
Section titled “Wire it up”-
Pick a shared secret and store it as
GHOSTWRITR_FEED_SECRETin your environment. This is the HMAC key both sides use. -
Deploy the resource route at a stable, public path (e.g.
/webhooks/gw-feed). -
Subscribe your site’s feed to that URL via the Ghost Writr API:
PUT /v1/sites/{siteId}/feed-subscriptionwith your webhook URL and the shared secret. The engine then signs every
feed.updatedPOST with it.
The payload
Section titled “The payload”FeedUpdatedPayload is small and stable:
interface FeedUpdatedPayload { event: "feed.updated"; siteId: string; buildId: string;}You generally only need siteId to scope your purge. The buildId identifies the new immutable feed snapshot the next request will read.