Contentful Webhooks
Verify Contentful's signed requests before parsing their bodies, and optionally invalidate every matching native Delivery cache before acknowledging a publishing event.
Verify a webhook route
Install the middleware on the exact receiving route before any body-consuming middleware:
import { contentfulWebhookMiddleware } from "brix/contentful";
app.post(
"/webhooks/contentful",
(c, next) => contentfulWebhookMiddleware(c.env.CONTENTFUL_WEBHOOK_SECRET)(c, next),
async (c) => c.json({ accepted: true, body: await c.req.json() }),
);
The middleware verifies the raw HMAC-SHA256 signature with a five-minute replay window, rejects invalid UTF-8, and then normalizes Contentful's vendor media type so Hono can parse JSON. The default retained-body limit is 1 MiB; raise it deliberately when the endpoint needs larger events:
contentfulWebhookMiddleware(secret, { maxBodySize: 2 * 1024 * 1024 });
Contentful signs the receiving path and query. Configure proxies not to rewrite either, and leave payload transformations disabled.
Invalidate native Delivery caches
Register the request-derived clients whose space and environment should be invalidated:
app.post(
"/webhooks/contentful",
(c, next) =>
contentfulWebhookMiddleware(c.env.CONTENTFUL_WEBHOOK_SECRET, {
clients: [getContentful(c), getAnotherContentful(c)],
})(c, next),
(c) => c.json({ accepted: true }),
);
Clients must be cached native Delivery clients on cdn.contentful.com or its EU equivalent. Preview, Management, custom-host, uncached, and empty client lists are rejected as configuration errors.
Invalidation mode accepts signed Entry and Asset publish, unpublish, and delete topics. It validates event metadata, matches explicit space and environment IDs, and awaits every matching client's invalidateCache() before the handler acknowledges. Register all stores and namespaces used by the application; a payload cannot discover them.
Keep failures non-2xx so Contentful retries. Duplicate deliveries invalidate again, which also recovers partially completed prior attempts. Whole-environment invalidation intentionally covers lists, empty results, pagination, and linked dependencies. Shared-store invalidation is eventual, and this operation does not purge Contentful's CDN, browser memory, or edge-rendered responses.
Expected rejection statuses are useful when testing configuration: invalid signatures and unsigned topics return 401, malformed metadata or unsupported topics return 400, an unregistered source returns 403, wrong methods return 405, and oversized bodies return 413.
Verify outside Hono
For another runtime or focused tests, call the asynchronous Web Crypto verifier with the exact request values:
import { verifyContentfulWebhook } from "brix/contentful";
const valid = await verifyContentfulWebhook(
secret,
request.method,
pathIncludingQuery,
rawUtf8Body,
request.headers,
);
This verifies the signature and timestamp only. It does not validate event metadata or invalidate caches.