Subscribe to events with webhooks
Use the Optimizely CMS (SaaS) REST API to subscribe to content lifecycle events and receive the event data at your own endpoint.
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 Optimizely Opal, 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.
Prerequisites
- You need an access token to call the Webhooks endpoints.
- You need an endpoint that accepts HTTP
POSTrequests and confirms the verification message that CMS (SaaS) sends to it. See Verify your endpoint.
Endpoints
ExperimentalThe 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.
POST https://api.cms.optimizely.com/v1/experimental/webhooks
Content-Type: application/json
Authorization: Bearer ACCESS_TOKEN{
"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:
{
"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. |
dataFilters | Narrows which events the subscription receives. See Data filters. |
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.
PATCH https://api.cms.optimizely.com/v1/experimental/webhooks/content-change-notifications
Content-Type: application/merge-patch+json
Authorization: Bearer ACCESS_TOKEN{
"isDisabled": true
}Delete a webhook subscription
Send a DELETE request to remove a subscription. CMS (SaaS) stops sending events to its endpoint.
DELETE https://api.cms.optimizely.com/v1/experimental/webhooks/content-change-notifications
Authorization: Bearer ACCESS_TOKENEvent 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 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.
{
"type": "object",
"properties": {
"key": { "type": "string" },
"isPermanent": { "type": "boolean" }
},
"required": ["key"]
}{
"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.
{
"type": "object",
"properties": {
"key": { "type": "string" },
"version": { "type": "string" }
},
"required": ["key", "version"]
}{
"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.
| 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. The field name cannot contain spaces, start or end with a period, or contain consecutive periods. |
operator | The comparison to apply. See Filter operators. |
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.
{
"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:
Authorization: Bearer YOUR_SHARED_SECRETConfirm 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.
Updated 3 days ago