Invalidate your CDN
If you render job pages behind a CDN or a page cache, the Publish API sends no Cache-Control for you to lean on (Caching). Invalidate on webhooks instead.
Which URLs to purge
Section titled “Which URLs to purge”Every event names the job and the index pages that list it. Purge all of them, every time:
| From the payload | URL to purge |
|---|---|
job.slug |
{jobs_base_path}/{job.slug} |
| always | {jobs_base_path} (the index of all jobs) |
job.service_type, if present |
category_index_paths.by_service with {slug} replaced |
job.service_city, if present |
category_index_paths.by_location with {slug} replaced |
each entry in job.sibling_slugs, if present |
{jobs_base_path}/{sibling} |
each entry in job.sibling_categories, if present |
the same two index patterns, with that entry’s slugs |
| always, if you serve one | your sitemap |
jobs_base_path and category_index_paths come from the Business descriptor; cache that once, it rarely changes.
Treat the three events the same way. On job.unpublished the job will answer 404 from the API on the next fetch, so your origin should start serving its own not-found page for that URL; purging guarantees the cached copy does not outlive it.
Handler shape
Section titled “Handler shape”- Verify the signature (guide). Reject with
401on failure. - Check
delivery.idagainst the IDs you have already handled. If seen, return200and stop. - Record
delivery.id. - Queue the purge and return
200immediately. Do not call your CDN inside the request; the delivery times out at 10 seconds and a slow purge would be counted as a failure and retried. - In the queued job, build the URL list above and call your CDN’s purge API. If the purge fails, retry it on your side; do not rely on SimplifyLocal’s retry, which only fires when you answered non-
2xx.
// Node, after verification and dedupefunction urlsToPurge(business, payload) { const base = `${business.canonical_domain}${business.jobs_base_path}`; const byService = (s) => `${business.canonical_domain}${business.category_index_paths.by_service.replace("{slug}", s)}`; const byCity = (c) => `${business.canonical_domain}${business.category_index_paths.by_location.replace("{slug}", c)}`;
const { job } = payload; const urls = new Set([base, `${base}/${job.slug}`]); if (job.service_type) urls.add(byService(job.service_type)); if (job.service_city) urls.add(byCity(job.service_city)); for (const s of job.sibling_slugs ?? []) urls.add(`${base}/${s}`); for (const c of job.sibling_categories ?? []) { if (c.service_type) urls.add(byService(c.service_type)); if (c.service_city) urls.add(byCity(c.service_city)); } return [...urls];}Re-warming
Section titled “Re-warming”After the purge, fetch the job with Get a job and render it, or let the next visitor’s request do it. Either way, store the response’s ETag so later renders can revalidate with If-None-Match.
If a delivery never arrives
Section titled “If a delivery never arrives”Deliveries are retried for up to about four days, but an endpoint that fails 50 times in a row is disabled. As a backstop, run a periodic sync with List jobs and updated_since, and purge anything whose updated_at moved.