# Webhooks

SuperDuperSEO calls your site when a page changes, so it can drop cached copies. Add one with the MCP tool `add_webhook` (or on the site's Content API page). It returns the signing secret once: store it as `SUPERDUPERSEO_WEBHOOK_SECRET`.

- **Request:** `POST` with a JSON body `{ id, event, site, path, url, version, at }`.
- **Events:** `page.published`, `page.updated`, `page.unpublished`, `site.updated` (redirects, settings, many pages), `ping` (`test_webhook`).
- **Signature:** header `SuperDuperSEO-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`. Compare in constant time and reject when `t` is more than 5 minutes off. The SDK's `verifyWebhook` does all of this.
- **Delivery:** answer `2xx`. We try 3 times: at once, after 2 seconds, after 10 seconds. Every delivery is logged and can be replayed from the app.
- **Missed one?** `changes.json` lists recent changes by cursor.

```ts
import { verifyWebhook } from '@superduperseo/content'

export async function POST(req: Request) {
  const raw = await req.text()
  const event = await verifyWebhook(process.env.SUPERDUPERSEO_WEBHOOK_SECRET!, raw, req.headers.get('SuperDuperSEO-Signature'))
  if (!event) return new Response('bad signature', { status: 401 })
  // drop cached copies of event.path, the blog index, the sitemap and the feed
  return new Response('ok')
}
```
