---
title: Subscribe to events with webhooks
slug: cms-saas/docs/subscribe-to-events-with-webhooks
docTags: 
createdAt: 2026-09-08T20:14:48.000Z
---

A webhook sends event data from CMS (SaaS) to a system of your choice when something happens to the content. Subscribe to the events your integration cares about. CMS (SaaS) then sends an HTTP `POST` request to your endpoint each time one of those events occurs, so your integration reacts to the event instead of polling CMS (SaaS) for changes.

Webhooks are useful when a change to content should trigger work somewhere else. Examples include sending content to Mark (Optimizely Agent Platform), pushing a published page to a translation service, and rebuilding a downstream cache when content is deleted.

Manage webhooks with the REST API, as described in this article, or [from the CMS (SaaS) UI](docId\:p3ZC2m-nWzuSy4Q22uKUc).

## Prerequisites

- You need an [access token](docId:2uVhCnJr4BdL9e5UNkN4L) to call the Webhooks endpoints.
- You need an endpoint that accepts HTTP `POST` requests and confirms the verification message that CMS (SaaS) sends to it. See [Verify your endpoint](docId\:AIKAVvMJzvYuQkngZb1rM).

## Endpoints

:::hint{type="warning"}
**Experimental**
The Webhooks endpoints are experimental and subject to change.
:::

The Webhooks endpoints let you list the available event types and manage your subscriptions.

| Endpoint                              | Method   | Description                                 |
| ------------------------------------- | -------- | ------------------------------------------- |
| `/v1/experimental/webhookdefinitions` | `GET`    | Lists the event types you can subscribe to. |
| `/v1/experimental/webhooks`           | `GET`    | Lists webhook subscriptions.                |
| `/v1/experimental/webhooks`           | `POST`   | Creates a webhook subscription.             |
| `/v1/experimental/webhooks/{key}`     | `GET`    | Gets a webhook subscription by its key.     |
| `/v1/experimental/webhooks/{key}`     | `PATCH`  | Updates a webhook subscription.             |
| `/v1/experimental/webhooks/{key}`     | `DELETE` | Deletes a webhook subscription.             |

## Create a webhook subscription

Send a `POST` request with the subscription in the request body. The following example subscribes to two content events and filters them to a single content item.

**HTTP headers**

```null
POST https://api.cms.optimizely.com/v1/experimental/webhooks
Content-Type: application/json
Authorization: Bearer ACCESS_TOKEN
```

**Body**

```json
{
  "key": "content-change-notifications",
  "displayName": "Content change notifications",
  "description": "Notifies the integration service when content is created or deleted.",
  "endpoint": "https://example.com/hooks/cms",
  "eventTypes": [
    "content:preview1:created",
    "content:preview1:deleted"
  ],
  "dataFilters": [
    {
      "field": "key",
      "operator": "StringIn",
      "values": ["6946107a8ad6414f8f1786364dab1ec2"]
    }
  ],
  "bearerToken": "YOUR_SHARED_SECRET"
}
```

CMS (SaaS) returns the stored subscription:

**Response**

```json
{
  "key": "content-change-notifications",
  "displayName": "Content change notifications",
  "description": "Notifies the integration service when content is created or deleted.",
  "endpoint": "https://example.com/hooks/cms",
  "eventTypes": [
    "content:preview1:created",
    "content:preview1:deleted"
  ],
  "dataFilters": [
    {
      "field": "key",
      "operator": "StringIn",
      "values": ["6946107a8ad6414f8f1786364dab1ec2"]
    }
  ],
  "bearerToken": "YOUR_SHARED_SECRET",
  "isVerified": false,
  "isDisabled": false,
  "created": "2026-08-26T10:30:00+00:00",
  "createdBy": "USER_NAME",
  "lastModified": "2026-08-26T10:30:00+00:00",
  "lastModifiedBy": "USER_NAME"
}
```

Each subscription covers events from one category and one version of that category. Do not mix event types from different categories in the same subscription.

### Subscription fields

A subscription has the following fields.

| Field                                                    | Description                                                                                                                                                                                                                  |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`                                                    | The unique identifier of the subscription. Three to 64 characters, and it can contain letters, numbers, and dashes. This is not the same as the content `key` in the event payloads.                                         |
| `displayName`                                            | Required. The name that identifies the subscription in the UI. Up to 255 characters.                                                                                                                                         |
| `description`                                            | Describes what the subscription is for. Up to 512 characters.                                                                                                                                                                |
| `endpoint`                                               | Required. The URL that receives the HTTP `POST` requests.                                                                                                                                                                    |
| `eventTypes`                                             | The event types the subscription receives. See [Event types](docId\:AIKAVvMJzvYuQkngZb1rM).                                                                                                                                  |
| `dataFilters`                                            | Narrows which events the subscription receives. See [Data filters](docId\:AIKAVvMJzvYuQkngZb1rM).                                                                                                                            |
| `bearerToken`                                            | A secret you choose. CMS (SaaS) sends it in the `Authorization` header of every request, so your endpoint can confirm the request came from CMS (SaaS). This is not the access token you use to call the Webhooks endpoints. |
| `isVerified`                                             | Read-only. Whether the endpoint has confirmed the verification message.                                                                                                                                                      |
| `isDisabled`                                             | Whether the subscription is turned off. When `true`, CMS (SaaS) does not send events to the endpoint.                                                                                                                        |
| `created`, `createdBy`, `lastModified`, `lastModifiedBy` | Read-only audit fields.                                                                                                                                                                                                      |

## Update a webhook subscription

Send a `PATCH` request to change a subscription. The following example turns a subscription off without deleting it.

**HTTP headers**

```http
PATCH https://api.cms.optimizely.com/v1/experimental/webhooks/content-change-notifications
Content-Type: application/merge-patch+json
Authorization: Bearer ACCESS_TOKEN
```

**Body**

```json
{
  "isDisabled": true
}
```

## Delete a webhook subscription

Send a `DELETE` request to remove a subscription. CMS (SaaS) stops sending events to its endpoint.

**HTTP headers**

```http
DELETE https://api.cms.optimizely.com/v1/experimental/webhooks/content-change-notifications
Authorization: Bearer ACCESS_TOKEN
```

## Event types

An event type has the format `{resource}:{version}:{action}`, such as `contentVersion:preview1:published`. The resource is the category of entity the event applies to.

Call `GET /v1/experimental/webhookdefinitions` to list the event types your instance supports, along with their display names and descriptions.

The **Filterable fields** column lists the fields you can use in [data filters](docId\:AIKAVvMJzvYuQkngZb1rM) for that event type.

### Content

Events for a content item. The payload identifies the content item by its `key`.

| Event type                 | Fires when                                                                  | Payload fields       | Filterable fields    |
| -------------------------- | --------------------------------------------------------------------------- | -------------------- | -------------------- |
| `content:preview1:created` | Content is created.                                                         | `key`                | `key`                |
| `content:preview1:updated` | Content moves to a different parent, excluding a move to the wastebasket.   | `key`                | `key`                |
| `content:preview1:deleted` | Content is deleted, either moved to the wastebasket or deleted permanently. | `key`, `isPermanent` | `key`, `isPermanent` |

The content payload has the following schema and sample.

**Schema**

```json
{
  "type": "object",
  "properties": {
    "key": { "type": "string" },
    "isPermanent": { "type": "boolean" }
  },
  "required": ["key"]
}
```

**Sample**

```json
{
  "key": "6946107a8ad6414f8f1786364dab1ec2",
  "isPermanent": false
}
```

The `isPermanent` field only appears on `content:preview1:deleted`. It is `true` when the content is deleted permanently, and `false` when the content moves to the wastebasket.

Deleting a content item together with its children sends one `content:preview1:deleted` event, not one event for each child.

### Content version

Events for a specific version of a content item. The payload identifies the version by the content `key` and the `version`.

| Event type                              | Fires when                                            | Payload fields   | Filterable fields |
| --------------------------------------- | ----------------------------------------------------- | ---------------- | ----------------- |
| `contentVersion:preview1:published`     | Content is published.                                 | `key`, `version` | `key`, `version`  |
| `contentVersion:preview1:scheduled`     | Content is scheduled for publishing at a future date. | `key`, `version` | `key`, `version`  |
| `contentVersion:preview1:localeDeleted` | A language branch of content is deleted.              | `key`, `version` | `key`, `version`  |

The content version payload has the following schema and sample.

**Schema**

```json
{
  "type": "object",
  "properties": {
    "key": { "type": "string" },
    "version": { "type": "string" }
  },
  "required": ["key", "version"]
}
```

**Sample**

```json
{
  "key": "6946107a8ad6414f8f1786364dab1ec2",
  "version": "456"
}
```

### Display template

Events for a display template. The payload is the display template resource.

| Event type                   | Fires when                               | Filterable fields                                                         |
| ---------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- |
| `displayTemplate:v1:created` | A display template is created.           | `id`, `key`, `name`, `nodeType`, `baseType`, `contentTypeID`, `isDefault` |
| `displayTemplate:v1:updated` | An existing display template is updated. | `id`, `key`, `name`, `nodeType`, `baseType`, `contentTypeID`, `isDefault` |
| `displayTemplate:v1:deleted` | An existing display template is deleted. | `id`, `key`, `name`, `nodeType`, `baseType`, `contentTypeID`, `isDefault` |

### Webhook

Events for the webhook subscriptions themselves, so you can track configuration changes. The payload is the subscription resource described in [Subscription fields](docId\:AIKAVvMJzvYuQkngZb1rM).

| Event type                       | Fires when                                   | Filterable fields                                                           |
| -------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------- |
| `webhookSubscription:v1:created` | A webhook subscription is created.           | `key`, `displayName`, `description`, `endpoint`, `isVerified`, `isDisabled` |
| `webhookSubscription:v1:updated` | An existing webhook subscription is updated. | `key`, `displayName`, `description`, `endpoint`, `isVerified`, `isDisabled` |
| `webhookSubscription:v1:deleted` | An existing webhook subscription is deleted. | `key`, `displayName`, `description`, `endpoint`, `isVerified`, `isDisabled` |

## Data filters

A data filter narrows which events reach your endpoint by matching a field in the event payload. A subscription only receives an event when the event matches the filters.

Each filter has the following fields.

| Field      | Description                                                                                                                                                                                                                                          |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `field`    | The payload field to match, such as `key`. Use the **Filterable fields** listed for the event type in [Event types](docId\:AIKAVvMJzvYuQkngZb1rM). The field name cannot contain spaces, start or end with a period, or contain consecutive periods. |
| `operator` | The comparison to apply. See [Filter operators](docId\:AIKAVvMJzvYuQkngZb1rM).                                                                                                                                                                       |
| `values`   | The values to compare the field against. Range operators take each range as two numbers separated by a comma, such as `"1,10"`.                                                                                                                      |

The following limits apply to filters:

- A maximum of 20 values across the filters on a subscription.
- A maximum of 475 characters for each value.

### Filter operators

The following operators are available.

| Operator                    | Matches when the field                  | Values              |
| --------------------------- | --------------------------------------- | ------------------- |
| `StringIn`                  | Equals one of the values.               | One or more strings |
| `StringNotIn`               | Equals none of the values.              | One or more strings |
| `StringContains`            | Contains one of the values.             | One or more strings |
| `StringNotContains`         | Contains none of the values.            | One or more strings |
| `StringBeginsWith`          | Begins with one of the values.          | One or more strings |
| `StringNotBeginsWith`       | Begins with none of the values.         | One or more strings |
| `StringEndsWith`            | Ends with one of the values.            | One or more strings |
| `StringNotEndsWith`         | Ends with none of the values.           | One or more strings |
| `NumberIn`                  | Equals one of the numbers.              | One or more numbers |
| `NumberNotIn`               | Equals none of the numbers.             | One or more numbers |
| `NumberInRange`             | Falls within one of the ranges.         | One or more ranges  |
| `NumberNotInRange`          | Falls outside every range.              | One or more ranges  |
| `NumberLessThan`            | Is less than the number.                | One number          |
| `NumberLessThanOrEquals`    | Is less than or equal to the number.    | One number          |
| `NumberGreaterThan`         | Is greater than the number.             | One number          |
| `NumberGreaterThanOrEquals` | Is greater than or equal to the number. | One number          |
| `BoolEquals`                | Equals the boolean value.               | `true` or `false`   |
| `IsNullOrUndefined`         | Has no value.                           | None                |
| `IsNotNull`                 | Has a value.                            | None                |

## Callback delivery

CMS (SaaS) delivers each event to your endpoint as a CloudEvent, which is a standard envelope format for event data. The `type` field holds the event type. The `subject` field holds the path of the affected entity, in the form `/{resource}/{version}/{key}`. For `contentVersion` events, the subject ends with the version identifier when the event has one. The entity itself is in the `data` field.

The following example shows the body of a delivered event.

**Example request body**

```json
{
  "type": "contentVersion:preview1:published",
  "subject": "/contentVersion/preview1/6946107a8ad6414f8f1786364dab1ec2/456",
  "data": {
    "key": "6946107a8ad6414f8f1786364dab1ec2",
    "version": "456"
  }
}
```

CMS (SaaS) sends an event only when the subscription is verified, is not disabled, and matches both the event type and any data filters.

Optimizely provisions and manages the delivery infrastructure. This includes the retries for failed deliveries and the dead-letter queue for events that cannot be delivered. These settings are not configurable. Because a failed delivery is retried, your endpoint can receive the same event more than once. Handle events idempotently, so that processing the same event twice has the same result as processing it one time.

### Authenticate the requests

When the subscription sets `bearerToken`, CMS (SaaS) sends that value in the `Authorization` header of every request:

**Request header**

```http
Authorization: Bearer YOUR_SHARED_SECRET
```

Confirm the token before you act on the request. This token is the static secret stored on the subscription, not the access token you use to call the Webhooks endpoints.

### Verify your endpoint

CMS (SaaS) confirms that you control an endpoint before it sends events to it. When you create a subscription, `isVerified` is `false` and CMS (SaaS) sends a verification message to the endpoint. When your endpoint confirms the message, `isVerified` becomes `true` and CMS (SaaS) starts delivering events.

Verification is tied to the endpoint URL, so changing the URL of an existing subscription starts the verification again.
