Building a Custom CDN Cache Purge Pipeline with Webhooks
The Challenge of Cache Invalidation at Scale
Automating selective purging without exposing your origin database to layout-blocking load spikes requires high-performance orchestration. Bramsley optimizes edge caching pipelines using a structured invalidation workflow:
- Instant Webhook Validation: Edge-native signature checks verify webhook origins closer to the CMS, offloading cryptographical overhead.
- Queue-Based Purging: Batch dynamic invalidations using Cloudflare Queues to eliminate API rate limits and scale under traffic.
- Surrogate-Key Mapping: Intelligently tag cached assets and purge related posts, authors, and indexes simultaneously.
When an editor updates an article or modifies a product listing, the older cached version must be replaced. Purging the entire CDN cache is inefficient and forces downstream requests back to the origin database, causing load spikes. A selective cache purging pipeline resolves this by targeting only the modified assets, maintaining a high cache hit ratio.
Surrogate Keys and Selective Cache Purging
Selective cache invalidation is commonly achieved using Surrogate Keys (also known as Cache Tags). When the origin server processes an incoming request and generates a response, it adds a Surrogate-Key header containing identifiers for the data (e.g., Surrogate-Key: post-123 author-456).
The CDN intercepts this response, caches it, and associates the keys with the cached entry. When a specific resource changes, the developer calls the CDN's purge API using the matching keys, clearing only the relevant cached entries.
Implementing a Secure Purge Webhook Handler
To automate cache clearing, modern headless CMS platforms (such as Sanity, Contentful, or Strapi) can dispatch a webhook when content changes. The webhook receiver worker verifies the request, extracts the modified identifiers, and calls the CDN API to purge the corresponding surrogate keys.
The following TypeScript code illustrates a Cloudflare Worker that validates an incoming webhook using an HMAC signature, parses the payload, and sends a purge request to the CDN API:
export interface Env {
PURGE_SECRET: string;
CLOUDFLARE_API_KEY: string;
CLOUDFLARE_ZONE_ID: string;
}
async function verifySignature(body: string, signature: string, secret: string): Promise<boolean> {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const sigBuffer = hexToBuffer(signature);
return await crypto.subtle.verify("HMAC", key, sigBuffer, encoder.encode(body));
}
function hexToBuffer(hex: string): ArrayBuffer {
const matches = hex.match(/[\da-f]{2}/gi) || [];
const typedArray = new Uint8Array(matches.map(h => parseInt(h, 16)));
return typedArray.buffer;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const signature = request.headers.get("X-Webhook-Signature") || "";
const bodyText = await request.text();
// Validate webhook origin using the shared secret
const isValid = await verifySignature(bodyText, signature, env.PURGE_SECRET);
if (!isValid) {
return new Response("Invalid signature check failed", { status: 401 });
}
const payload = JSON.parse(bodyText);
const tagsToPurge = payload.tags || [];
if (tagsToPurge.length === 0) {
return new Response("No tags found in request", { status: 400 });
}
// Dispatch purge request to the Cloudflare Zone cache API
const cfResponse = await fetch(
`https://api.cloudflare.com/client/v4/zones/${env.CLOUDFLARE_ZONE_ID}/purge_cache`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${env.CLOUDFLARE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ tags: tagsToPurge }),
}
);
const result = await cfResponse.json();
return new Response(JSON.stringify(result), {
headers: { "Content-Type": "application/json" },
status: cfResponse.status,
});
}
};
Verifying Webhook Signatures and Queueing Purge Jobs
Verifying signatures is essential to prevent malicious actors from triggering continuous cache purges, which could overwhelm the origin server. In high-volume setups, multiple webhooks can be dispatched simultaneously. In these scenarios, routing incoming webhooks through an edge-native queue (such as Cloudflare Queues or Amazon SQS) allows the application to batch updates and run purge API calls in batches, preventing rate limit errors.
Optimizing Dynamic Content Caching with Bramsley
Automating selective purging without exposing your origin database to layout-blocking load spikes requires high-performance orchestration. Bramsley optimizes edge caching pipelines using a structured invalidation workflow:
- Instant Webhook Validation: Edge-native signature checks verify webhook origins closer to the CMS, offloading cryptographical overhead.
- Queue-Based Purging: Batch dynamic invalidations using Cloudflare Queues to eliminate API rate limits and scale under traffic.
- Surrogate-Key Mapping: Intelligently tag cached assets and purge related posts, authors, and indexes simultaneously.