Reference
Events and webhooks
The events the CMS sends when content changes, what each one carries, how it is delivered, and which reach webhooks.
Reference9 min read
When something changes in a space, such as a page going live or a menu being edited, the CMS records an event. Events let websites and other systems react to changes without asking the CMS over and over.
Where events go
Every event goes to two places.
| Destination | What it gets | Format |
|---|---|---|
| Orbit Exchange | Every event, in full | The event envelope below |
| Webhooks | Only events that have a webhook name, and only those the webhook listens for | A short signed message with the webhook name and a smaller set of data |
Orbit Exchange is the hub that connects Orbit products. It receives each event with everything the CMS knows about it.
Webhooks are addresses you register in Configuration › Webhooks. They are meant for websites that need to refresh a cache. See Refresh the website when content changes to set one up.
The envelope
Every event sent to Orbit Exchange has the same outer shape. Only data differs between events.
idtypecms.page.published.versiondata shape. It goes up only if the shape changes in a way that could break a receiver.timesourcecms for these events.sequencetenant.organisationIdtenant.spaceIdtenant.environmentKeyproduction.tenant.regionKeyde.tenant.localeCodede-DE.subject.resourceTypecms.page, cms.release, cms.redirect or cms.navigation.subject.idactor.typeuser, apiKey, agent, system or service.actor.idnull for system, for example a scheduled release.data| Field | Type | What it holds |
|---|---|---|
id | string (UUID) | A unique ID for this event. Use it to spot duplicates. |
type | string | The event name, such as cms.page.published. |
version | integer | The version of this event's data shape. It goes up only if the shape changes in a way that could break a receiver. |
time | string (ISO 8601, UTC) | When the change happened. |
source | string | Always cms for these events. |
sequence | integer | A number that goes up with every event the CMS sends. |
tenant.organisationId | string | The organisation the change happened in. |
tenant.spaceId | string or null | The space. |
tenant.environmentKey | string or null | The environment key, such as production. |
tenant.regionKey | string or null | The region key, such as de. |
tenant.localeCode | string or null | The language code, such as de-DE. |
subject.resourceType | string | What changed: cms.page, cms.release, cms.redirect or cms.navigation. |
subject.id | string | The ID of the thing that changed. |
actor.type | string | Who did it: user, apiKey, agent, system or service. |
actor.id | string or null | Their ID. It is null for system, for example a scheduled release. |
data | object | The details of the change. See the event families below. |
A full example, for a page going live:
{ "id": "3f2b6c1e-8d4a-4f7b-9a51-2c0e7d9b1a44", "type": "cms.page.published", "version": 1, "time": "2026-10-17T09:30:00.000Z", "source": "cms", "sequence": 48213, "tenant": { "organisationId": "6650f1c2a9e4b3d2c1f0e9a8", "spaceId": "6650f1c2a9e4b3d2c1f0e9b1", "environmentKey": "production", "regionKey": "germany", "localeCode": "de-DE" }, "subject": { "resourceType": "cms.page", "id": "6650f1c2a9e4b3d2c1f0ea07" }, "actor": { "type": "user", "id": "6650f1c2a9e4b3d2c1f0e011" }, "data": { "contentId": "6650f1c2a9e4b3d2c1f0ea07", "contentType": "page", "name": "Hiking boots", "url": "/products/hiking-boots", "locale": "de-DE", "region": "germany", "environment": "production", "version": 7, "revisionId": "6650f1c2a9e4b3d2c1f0eb42", "publishedAt": "2026-10-17T09:30:00.000Z", "redirectFrom": null }}Delivery guarantees
Orbit Exchange: at least once. Each event is kept until Orbit Exchange accepts it. If Orbit Exchange cannot be reached, the CMS tries again after 30 seconds, then waits twice as long each time, up to an hour between tries, for 12 tries in all (about five hours). Because a try can succeed even when the answer is lost, the same event can arrive more than once.
Webhooks: once, when the change happens. A webhook message is sent while the change is being saved. If your endpoint fails or does not answer within 10 seconds, the failure is shown in the webhook's Deliveries, but the message is not sent again. See Retries and failures in the webhooks guide.
Order. Events are sent in the order the changes happen, but a retried event can arrive after newer ones. Sort by sequence when order matters. The number is shared across the whole platform, so gaps between your events are normal. Only the order means anything.
Make receivers idempotent. Handling the same event twice should do no harm.
- In Orbit Exchange events, keep the
idof events you have handled and skip any you have seen. - Webhook messages carry no event ID. Refreshing a cache twice is harmless, so most receivers need no extra work. If yours does more, combine
event,contentIdandversion. - Treat an event as a sign that something changed, not as the final state. If two changes arrive out of order, fetch the current content from the delivery API instead of trusting the older message.
Event families
Pages and entries
These events are about one item of content in one region and language. "Pages" here also covers entries without an address, such as global content. Those have url set to null.
Every page event carries these fields in data:
contentIdcontentTypepage.nameurl/products/hiking-boots. null for entries without an address.localeregionenvironment| Field | Type | What it holds |
|---|---|---|
contentId | string | The item's ID. |
contentType | string | The content type key, such as page. |
name | string | The item's name in the CMS. |
url | string or null | The page address, such as /products/hiking-boots. null for entries without an address. |
locale | string or null | The language code. |
region | string or null | The region key. |
environment | string or null | The environment key. |
Each event then adds its own fields.
cms.page.publishedversion, revisionId, publishedAt, and redirectFrom, the old address if the address changed and a redirect was added.content.publishedcms.page.unpublishedrevisionIdcontent.unpublishedcms.page.rolledBacktoVersion, revisionId, and republished, which is true if the page was live and the older version went live too.content.publishedcms.page.promotedsourceContentId, fromEnvironment, toEnvironment, and created, which is true if the page was new there.content.published, to webhooks on the environment it was copied intocms.page.trashedcontent.deletedcms.page.restoredcms.page.purged| Event | Triggered when | Extra fields | Webhook |
|---|---|---|---|
cms.page.published | A page is published, or its changes are, by hand, on a schedule or in a release. | version, revisionId, publishedAt, and redirectFrom, the old address if the address changed and a redirect was added. | content.published |
cms.page.unpublished | A page is taken offline. | revisionId | content.unpublished |
cms.page.rolledBack | A page goes back to an earlier version. | toVersion, revisionId, and republished, which is true if the page was live and the older version went live too. | content.published |
cms.page.promoted | A page is copied into another environment. | sourceContentId, fromEnvironment, toEnvironment, and created, which is true if the page was new there. | content.published, to webhooks on the environment it was copied into |
cms.page.trashed | A page is moved to the bin. | None | content.deleted |
cms.page.restored | A page comes back from the bin. | None | None |
cms.page.purged | A page in the bin is erased for good. | None | None |
An unpublished event, as data:
{ "contentId": "6650f1c2a9e4b3d2c1f0ea07", "contentType": "page", "name": "Hiking boots", "url": "/products/hiking-boots", "locale": "de-DE", "region": "germany", "environment": "production", "revisionId": "6650f1c2a9e4b3d2c1f0eb57"}Releases
cms.release.published is sent once a release has run, by hand or on its schedule. Each page in the release also sends its own cms.page.published or cms.page.unpublished event first.
releaseIdnamepublishedunpublishedfailedcontentId and reason.scheduledtrue if the release ran on its schedule.| Field | Type | What it holds |
|---|---|---|
releaseId | string | The release's ID. |
name | string | The release name. |
published | array of strings | IDs of the items that went live. |
unpublished | array of strings | IDs of the items taken offline. |
failed | array of objects | Items that could not be published, each with contentId and reason. |
scheduled | boolean | true if the release ran on its schedule. |
{ "releaseId": "6650f1c2a9e4b3d2c1f0ec10", "name": "Autumn offers", "published": ["6650f1c2a9e4b3d2c1f0ea07", "6650f1c2a9e4b3d2c1f0ea19"], "unpublished": [], "failed": [], "scheduled": true}Webhook name: release.published.
Menus
cms.navigation.changed is sent when a menu is created, changed or deleted.
{ "navigationId": "6650f1c2a9e4b3d2c1f0ed21", "key": "main", "action": "updated" }action is created, updated or deleted. Webhook name: navigation.changed.
Redirects
cms.redirect.changed is sent when a redirect is created, changed or removed, including redirects the CMS adds itself when a page address changes.
{ "redirectId": "6650f1c2a9e4b3d2c1f0ee33", "action": "created", "source": "/products/hiking-boot", "destination": { "type": "content", "url": null, "contentId": "6650f1c2a9e4b3d2c1f0ea07" }, "statusCode": 301, "origin": null}destination.type is url for a fixed address, or content for a page in the CMS. Redirect events go to Orbit Exchange only. They do not reach webhooks.
Media and other changes
Uploading or changing media, and editing settings, schemas or other screens, does not send an event. Only the changes listed above do.
Webhook messages
A webhook gets a smaller message than Orbit Exchange. The body is JSON with three fields:
eventcontent.published, or webhook.test for a test.sentAtdata| Field | Type | What it holds |
|---|---|---|
event | string | The webhook name, such as content.published, or webhook.test for a test. |
sentAt | string (ISO 8601, UTC) | When the message was sent. |
data | object | The fields below. |
content.published, content.unpublished, content.deleteddata fieldscontentId, url, name, locale, region, and version when the page was publishedrelease.publisheddata fieldsreleaseId, name, publishednavigation.changeddata fieldsnavigationId, keywebhook.testdata fieldswebhook, the webhook's name| Webhook event | data fields |
|---|---|
content.published, content.unpublished, content.deleted | contentId, url, name, locale, region, and version when the page was published |
release.published | releaseId, name, published |
navigation.changed | navigationId, key |
webhook.test | webhook, the webhook's name |
{ "event": "content.published", "sentAt": "2026-10-17T09:30:00.412Z", "data": { "contentId": "6650f1c2a9e4b3d2c1f0ea07", "url": "/products/hiking-boots", "name": "Hiking boots", "locale": "de-DE", "region": "germany", "version": 7 }}The headers and how to check the signature are in Refresh the website when content changes.
What the SDK refreshes
The SDK's createRevalidateHandler turns each webhook message into the cache tags to refresh. It only works if the client is set up with cache: { tags: true }. See The SDK.
content.published, content.unpublished, content.deleted for a page with an addresscms:page:{region}:{url}, plus lists (cms:list), the sitemap (cms:sitemap) and everything read with references (cms:references). Without a region, everything (cms).cms:global), lists (cms:list), popups (cms:popups), banners (cms:banners) and everything read with references (cms:references)navigation.changedcms:nav:{key}release.publishedwebhook.testcms)| Webhook event | Tags refreshed |
|---|---|
content.published, content.unpublished, content.deleted for a page with an address | The page, cms:page:{region}:{url}, plus lists (cms:list), the sitemap (cms:sitemap) and everything read with references (cms:references). Without a region, everything (cms). |
| The same events for an entry without an address | Global content (cms:global), lists (cms:list), popups (cms:popups), banners (cms:banners) and everything read with references (cms:references) |
navigation.changed | That menu, cms:nav:{key} |
release.published | Lists, the sitemap and everything read with references. The pages themselves are refreshed by their own events. |
webhook.test | Nothing |
| Anything else | Everything (cms) |
For example, publishing /products/hiking-boots in region germany refreshes cms:page:germany:/products/hiking-boots, cms:list, cms:sitemap and cms:references.
Not using the SDK? Connect a website without Next.js shows how to refresh your own cache.
Every event
The table below is built from the live list of events, so it is always current.
cms.page.publishedversion 1content.publishedcms.page.unpublishedversion 1content.unpublishedcms.page.rolledBackversion 1content.publishedcms.page.promotedversion 1content.publishedcms.page.trashedversion 1content.deletedcms.page.restoredversion 1cms.page.purgedversion 1cms.release.publishedversion 1release.publishedcms.redirect.changedversion 1cms.navigation.changedversion 1navigation.changed| Event | When | Webhook event |
|---|---|---|
cms.page.publishedversion 1 | A page or entry went live, or its changes did. | content.published |
cms.page.unpublishedversion 1 | A page or entry was taken offline. | content.unpublished |
cms.page.rolledBackversion 1 | A page went back to an earlier version. | content.published |
cms.page.promotedversion 1 | A page was copied live into another environment. | content.published |
cms.page.trashedversion 1 | A page or entry was moved to the bin. | content.deleted |
cms.page.restoredversion 1 | A page or entry came back from the bin. | Orbit Exchange only |
cms.page.purgedversion 1 | A trashed page was erased for good. | Orbit Exchange only |
cms.release.publishedversion 1 | A release went live with all its pages. | release.published |
cms.redirect.changedversion 1 | A redirect was created, changed or removed. | Orbit Exchange only |
cms.navigation.changedversion 1 | A menu changed. | navigation.changed |
Was this page helpful?