# Webhook Configuration

> Source: https://agilitycms.com/docs/training-guide/admin-webhooks

This guide covers configuring webhooks in Agility CMS for cache invalidation and automation.

## Webhook Overview

Webhooks allow Agility CMS to notify your application when content changes, enabling automatic cache invalidation and other automation.

## Webhook Events

When you add a webhook you choose which categories of event it subscribes to:

- **Content Publish Events**: a content item or page is published or unpublished
- **Content Save Events**: a content item or page is saved or deleted
- **Content Workflow Events**: content is requested for approval, approved, or declined

The `state` value in the payload tells you which specific event occurred.

## Webhook Configuration

### Step 1: Create Webhook Endpoint

Create a webhook endpoint in your application:

```typescript
// app/api/revalidate/route.ts
export async function POST(request: Request) {
  // Verify the signature (see Securing your webhook, below)
  // Process webhook event
  // Revalidate cache
  return Response.json({ revalidated: true })
}
```

### Step 2: Configure Webhook in Agility CMS

1. Navigate to **Settings** → **Webhooks**
2. Click **"Add Webhook"**
3. Configure webhook:
   - **URL**: Your webhook endpoint URL
   - **Events**: Select which categories of event should trigger it
   - **Enable secure delivery**: recommended — see below
   - **Enable retries**: recommended — see below

## Securing your webhook

Your webhook endpoint is a public URL. Turn on **secure delivery** so your application can prove that a request genuinely came from your Agility instance and was not modified in transit.

When you enable it, Agility generates a signing secret and signs every delivery using the open [Standard Webhooks](https://www.standardwebhooks.com) specification. Your endpoint verifies the signature with any off-the-shelf `standard-webhooks` library.

1. Edit the webhook and tick **Enable secure delivery**
2. Save — Agility generates the signing secret and shows it immediately
3. Copy it into your application's configuration
4. Verify the signature on every request

Full instructions, including verification samples for Node.js, C#, Python and PHP: **[Verifying Signed Webhooks](/docs/developers/verifying-signed-webhooks)**.

> **Viewing or rolling a signing secret requires Full Control.** Users with a lower permission level can still create and manage webhooks, but the secret is hidden from them.

> ⚠️ **There is no way to secure a webhook with a security key or a custom header.** Agility does not send `AGILITY_SECURITY_KEY`, or any other shared secret, with a webhook — that key is used for **preview** authentication and is unrelated. An earlier version of this guide showed a validation step based on it; that check could never succeed, because the header it looked for was never sent. Secure delivery is the way to verify a webhook.

## Retries

By default a delivery is attempted once. If your endpoint is briefly unavailable, that delivery is lost.

Tick **Enable retries** on the webhook to have Agility retry a failed delivery, and choose:

- **Retry count** — 1 to 8 attempts
- **Retry speed** — `fast` (30 seconds), `standard` (5 minutes) or `slow` (30 minutes) base interval, growing exponentially with jitter

Delivery is **at-least-once**, so design your endpoint to tolerate receiving the same event twice. Use the `webhook-id` header as an idempotency key — it is unique per event, stable across retries, and sent on every delivery whether signed or not.

## Webhook Payload

### Content Published Event

```json
{
  "state": "Published",
  "instanceGuid": "your-instance-guid",
  "languageCode": "en-us",
  "referenceName": "posts",
  "contentID": 204,
  "contentVersionID": 1287,
  "changeDateUTC": "2025-12-08T15:12:10.883Z"
}
```

### Page Published Event

```json
{
  "state": "Published",
  "instanceGuid": "your-instance-guid",
  "languageCode": "en-us",
  "pageID": 2,
  "pageVersionID": 114,
  "changeDateUTC": "2025-12-08T15:12:10.883Z"
}
```

## Webhook Handler Implementation

### Cache Revalidation

```typescript
export async function POST(request: Request) {
  // Read the raw body first if you are verifying the signature —
  // re-serializing the JSON will break verification.
  const data = await request.json()

  // Only process publish events
  if (data.state === "Published") {
    // Revalidate content tags
    if (data.referenceName) {
      revalidateTag(`agility-content-${data.referenceName}-${data.languageCode}`)
      revalidateTag(`agility-content-${data.contentID}-${data.languageCode}`)
    }

    // Revalidate page tags
    if (data.pageID) {
      revalidateTag(`agility-page-${data.pageID}-${data.languageCode}`)
    }

    // Revalidate paths
    if (data.path) {
      revalidatePath(data.path)
    }
  }

  return Response.json({ revalidated: true })
}
```

## Webhook Security

### Security Best Practices

1. **Enable secure delivery** and verify the signature on every request — see [Verifying Signed Webhooks](/docs/developers/verifying-signed-webhooks)
2. **Compare signatures in constant time**, and reject deliveries whose timestamp is far from your clock
3. **Be idempotent** — use `webhook-id` to discard events you have already handled
4. **Return quickly** — do the work asynchronously and respond, rather than holding the connection open
5. **Monitor activity** — check delivery history when something looks wrong

## Troubleshooting

### Start with delivery history

Every webhook has a **History** action in **Settings → Webhooks**. It shows each delivery attempt: the status code your endpoint returned, which attempt it was, when the next retry is scheduled, and the payload and response bodies. That is usually faster than reproducing the problem.

> Delivery history is **go-forward only**. A webhook created before this feature shipped shows an empty history until it fires again — that is expected, not a fault.

### Webhook Not Firing

**Issue**: Webhook not receiving events

**Solutions:**
- Verify the webhook URL is correct and publicly reachable
- Check the webhook is enabled
- Verify the right event categories are selected
- Open **History** to see whether Agility attempted a delivery and what came back

### Signature Verification Failing

**Issue**: Your endpoint rejects deliveries as unsigned or invalid

**Solutions:**
- Confirm **Enable secure delivery** is on for that webhook
- Verify against the **raw request body** — a JSON body parser that re-serializes the payload will break the signature
- Accept **any** matching signature in the header: during the 24 hours after a secret roll, two space-separated signatures are sent
- Confirm your stored secret matches the one on the webhook — roll it if in doubt

---

**Next**: [Troubleshooting](/docs/training-guide/admin-troubleshooting) - Admin troubleshooting
