Skip to content

Purging the Cache

Turbulence caches transformed HTML so repeat visits are fast. When you publish or deploy, that cache can keep serving the previous page until the TTL elapses. For an immediate refresh, purge it.

There are two tools, both scoped to one hostname:

  • Bust all caches on the domain page in the dashboard — a one-click whole-host purge.
  • Cache purge webhook on the domain’s Settings tab — a URL you POST when a publish or deploy is already live at the origin Turbulence will fetch.

Purging does not delete self-hosted /pp-static/ CSS, JS, or fonts. Those files are content-addressed; a fresh HTML optimize will re-link them as needed.

On the domain page, Bust all caches queues a whole-host purge: stored HTML for that hostname and the edge cache that serves it.

Use this when you just published something important and you are sitting in the dashboard. For CI, Ghost, or GitHub Actions, use the webhook instead.

  1. Open the domain in the dashboard and go to Settings.
  2. Under Cache purge webhook, click Generate purge webhook.
  3. Copy the URL immediately. It is shown once. Possession of the URL is authorization for that hostname — there is no extra API key header.

The URL looks like:

https://go.picperf.io/api/turbulence/hooks/tbk_…/purge

Rotate if it leaks (the old URL stops working immediately). Revoke to disable it until you generate a new one.

How visitors get HTMLCall the webhook
Live origin — WordPress, Ghost, Statamic, Laravel, etc. served through TurbulenceRight after publish. The origin already has the new HTML.
Static / Cloudflare Pages / Netlify — a CMS feeds a build, then a host swaps the siteAfter the new production deploy is serving. Not in the Pages/Netlify build command (that runs before the new files are live). Not on Ghost post.published if Ghost is only a content API for an SSG.

A Ghost custom webhook (empty body, no custom headers) is the right fit when Ghost is the live origin. If Ghost only feeds Astro/Eleventy/Next that deploys to Pages, wait until that deploy succeeds, then POST.

POST https://go.picperf.io/api/turbulence/hooks/{token}/purge

Only POST is accepted (GET/PUT/HEAD return 405). Extra JSON keys are ignored, so Ghost’s post.published payload is fine.

Whole host — omit paths, send an empty body, or include "/" in the list ("/" always means the entire site, not the homepage alone):

Terminal window
curl -sS -X POST "$TURBULENCE_PURGE_URL"

Specific paths — up to 50 paths. Each must start with /. Trailing slashes are stripped (/blog/ is /blog). That prefix also covers children (/blog purges /blog, /blog/, and /blog/my-post). Do not send "/" next to other paths.

Terminal window
curl -sS -X POST "$TURBULENCE_PURGE_URL" \
-H "Content-Type: application/json" \
-d '{"paths":["/blog","/blog/new-post"]}'

You can also pass a comma-separated query string if the caller cannot set a JSON body. JSON paths wins if both are present:

POST .../purge?paths=/blog,/blog/new-post

Optional header Idempotency-Key (max 128 characters): successful 200 and 202 responses are remembered for 10 minutes. Failed attempts (429, 503) are not cached, so a retry can proceed.

StatusMeaning
200 { "status": "purged" }Path-scoped purge finished; the edge HTML cache for those paths was invalidated.
202 { "status": "accepted" }Whole-host (or a large path list) queued. Visitors update within seconds.
401Unknown or revoked token.
422Invalid paths (must start with /; no .., //, hostnames, or more than 50).
429Slow down. Honor Retry-After. The edge purge is retried automatically.
503Edge purge did not complete; it will retry. Check the dashboard or try again shortly.

Path-scoped calls are throttled to 5 per minute per webhook; whole-host to 1 per minute. Invalid tokens share a 20 per minute limit per IP.

Paste the full webhook URL into Ghost Custom Integrations → Webhooks on post.published / post.updated. An empty Ghost body purges the whole host.

For a listing plus the new slug, after the origin is already serving the post:

Terminal window
curl -sS -X POST "$TURBULENCE_PURGE_URL" \
-H "Content-Type: application/json" \
-d '{"paths":["/blog","/blog/new-post"]}'

Do not call the webhook from the Pages build command. That runs before the new dist/ is uploaded and swapped live, so Turbulence can recache the previous deploy.

Direct upload / cloudflare/pages-action: POST the webhook as the next step after the action succeeds.

Git-integrated Pages / Deploy Hook: snapshot the current production deployment before you trigger the hook, wait until a newer production deploy has latest_stage.name == deploy and status == success, then POST. If that wait times out, fail the job — do not purge.

Store the copied webhook URL as TURBULENCE_PURGE_URL. Replace CF_PAGES_PROJECT and the path list with yours:

env:
CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_PAGES_PROJECT: ${{ secrets.CF_PAGES_PROJECT }}
DEPLOYMENTS_URL: https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CF_PAGES_PROJECT }}/deployments?env=production
- name: Snapshot current production deployment
run: |
echo "TRIGGERED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV"
echo "PREV_ID=$(curl -fsS -H "Authorization: Bearer $CF_API_TOKEN" "$DEPLOYMENTS_URL" | jq -r '.result[0].id')" >> "$GITHUB_ENV"
- name: Trigger Pages Deploy Hook
run: curl -fsS -X POST "${{ secrets.WEBHOOK_URL }}"
- name: Wait until a newer production deploy stage is success
run: |
ok=0
for i in $(seq 1 60); do
json=$(curl -fsS -H "Authorization: Bearer $CF_API_TOKEN" "$DEPLOYMENTS_URL")
id=$(echo "$json" | jq -r '.result[0].id')
created=$(echo "$json" | jq -r '.result[0].created_on | sub("\\.[0-9]+Z$"; "Z")')
stage=$(echo "$json" | jq -r '.result[0].latest_stage.name')
status=$(echo "$json" | jq -r '.result[0].latest_stage.status')
if [ "$id" != "$PREV_ID" ] \
&& [ "$created" \> "$TRIGGERED_AT" ] \
&& [ "$stage" = "deploy" ] \
&& [ "$status" = "success" ]; then
ok=1
break
fi
sleep 10
done
[ "$ok" = "1" ]
- name: Purge Turbulence cache
run: |
curl -fsS -X POST "${{ secrets.TURBULENCE_PURGE_URL }}" \
-H "Content-Type: application/json" \
-d '{"paths":["/your-listing-path"]}'

For Netlify or other hosts, the same rule applies: purge as the step after the new production URL is serving.

  1. Confirm the origin (or Pages *.pages.dev URL) already has the new HTML.
  2. Hard-refresh or try a private window — the browser may still have the previous response (Cache-Control on fresh HTML is one hour).
  3. Check X-PicPerf-Cache on the document request (Verifying It Works). After a successful purge, the next visit should be a MISS (or a new HIT of the updated HTML).
  4. If you used path-scoped purge, make sure you included the listing URL (for example /blog) as well as the new post.