# Webhook rebuild & CDN purge

Source: https://docs.qiick.io/externa/webhook-rebuild-purge

Use [outbound webhooks](https://docs.qiick.io/externa/webhooks) when headless content changes should **rebuild a static host** or **purge a CDN**. Prefer a thin receiver that verifies HMAC, then calls the platform API — do not expose unsigned deploy hooks on the public internet.

> **Related**
> [Outbound webhooks](https://docs.qiick.io/externa/webhooks) · [Automation without Flows](https://docs.qiick.io/externa/automation-without-flows) · [Deployment](https://docs.qiick.io/externa/deployment) · [Content versioning](https://docs.qiick.io/externa/content-versioning)

## Event picks

| Goal                          | Listen for                             |
| ----------------------------- | -------------------------------------- |
| Content changed (draft saved) | `item.created` / `item.updated`        |
| Only when live                | `item.published` (manual or scheduled) |
| Collection schema change      | `collection.updated`                   |
| Smoke test                    | `ping` from Project settings           |

Filter in Make/n8n/your code on JSON `type`.

## Pattern A — HMAC receiver → deploy hook

1. Externa → **your** HTTPS URL (secret set in Project settings).
2. Receiver verifies `X-Externa-Signature` (see [Webhooks](https://docs.qiick.io/externa/webhooks)).
3. If `type` matches, `POST` the platform **Deploy Hook / Build Hook** (no body required for Vercel/Netlify hooks).

### Vercel Deploy Hook

```bash
# After HMAC OK:
curl -fsS -X POST "$VERCEL_DEPLOY_HOOK_URL"
```

Create the hook in Vercel → Project → Settings → Git → Deploy Hooks.

### Netlify Build Hook

```bash
curl -fsS -X POST "$NETLIFY_BUILD_HOOK_URL"
```

Site settings → Build & deploy → Build hooks.

> Deploy hooks usually **ignore** Externa’s HMAC. Anyone with the URL can trigger builds. Staging-only exception is OK; production should verify signature first.

### Minimal Node receiver sketch

```js
import crypto from 'node:crypto'
import http from 'node:http'

const secret = process.env.EXTERNA_WEBHOOK_SECRET
const deployHook = process.env.VERCEL_DEPLOY_HOOK_URL

http
  .createServer(async (req, res) => {
    const chunks = []
    for await (const c of req) chunks.push(c)
    const raw = Buffer.concat(chunks)
    const expected =
      'sha256=' + crypto.createHmac('sha256', secret).update(raw).digest('hex')
    if (req.headers['x-externa-signature'] !== expected) {
      res.writeHead(401)
      return res.end()
    }
    const body = JSON.parse(raw.toString('utf8'))
    if (['item.published', 'item.updated'].includes(body.type)) {
      await fetch(deployHook, { method: 'POST' })
    }
    res.writeHead(204)
    res.end()
  })
  .listen(8787)
```

## Pattern B — Cloudflare cache purge

After publish (or rebuild), purge paths or tags via Cloudflare API (token with **Cache Purge**).

```bash
curl -fsS -X POST \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://www.example.com/","https://www.example.com/blog/"]}'
```

Purge everything (use sparingly):

```bash
curl -fsS -X POST \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything":true}'
```

Wire this in the same receiver **after** HMAC + `type` filter (often only `item.published`). Map `collection` / item slug from the webhook payload to URLs your frontend uses.

## Make / n8n shortcut

1. Webhook trigger URL → Externa Project settings + secret.
2. Filter: `type` equals `item.published` (or list).
3. HTTP Request → Deploy Hook URL **or** Cloudflare purge endpoint with Bearer token.

Same as [Webhooks recipes](https://docs.qiick.io/externa/webhooks#recipes-make-n8n-zapier-build-hooks), with explicit purge step.
