Skip to content

Webhook Events Reference

intermediate

Webhooks deliver real-time notifications when events occur in CMP or CMS SaaS. Instead of polling APIs for changes, your application receives HTTP POST requests with event data as it happens. Use webhooks to trigger downstream workflows, sync external systems, or build custom integrations.

Register a webhook endpoint to receive events. See the CMP API Reference for the registration endpoint.

Configuration fieldRequiredDescription
urlYesHTTPS endpoint that receives webhook payloads
eventsYesArray of event types to subscribe to
secretRecommendedShared secret for payload signature verification
activeNoWhether the webhook is enabled (default: true)
descriptionNoHuman-readable label for this webhook

When a secret is configured, every webhook delivery includes a signature header for payload verification.

Header: X-Optimizely-Signature

Algorithm: HMAC-SHA256 of the raw request body using the shared secret.

Verify webhook signature
javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
  .createHmac('sha256', secret)
  .update(payload, 'utf8')
  .digest('hex');

return crypto.timingSafeEqual(
  Buffer.from(signature),
  Buffer.from(expected)
);
}

// In your webhook handler
app.post('/webhooks/optimizely', (req, res) => {
const signature = req.headers['x-optimizely-signature'];
const isValid = verifyWebhookSignature(
  req.rawBody,
  signature,
  process.env.WEBHOOK_SECRET
);

if (!isValid) {
  return res.status(401).send('Invalid signature');
}

// Process the event
const event = req.body;
console.log(`Received: ${event.event_type}`);
res.status(200).send('OK');
});

Every webhook event follows this envelope format:

FieldTypeDescription
event_idstringUnique event identifier (UUID)
event_typestringEvent type identifier (e.g., content.published)
timestampISO 8601When the event occurred
sourcestringOriginating system: cmp or cms-saas
account_idstringYour Optimizely account ID
dataobjectEvent-specific payload (varies by event type)
Event typeFires whenKey data fields
content.createdA new content item is createdcontent_id, content_type, title, created_by
content.updatedContent fields are modifiedcontent_id, changed_fields[], updated_by, version
content.deletedContent is moved to trashcontent_id, deleted_by
content.restoredContent is restored from trashcontent_id, restored_by
Event typeFires whenKey data fields
workflow.transitionedContent moves to a new workflow stagecontent_id, from_stage, to_stage, transitioned_by
workflow.approvedA reviewer approves contentcontent_id, stage, approved_by, comment
workflow.rejectedA reviewer rejects contentcontent_id, stage, rejected_by, comment, reason
workflow.changes_requestedA reviewer requests changescontent_id, stage, requested_by, comment
workflow.assignedContent is assigned to a new reviewercontent_id, stage, assignee_id, assigned_by
workflow.overdueContent exceeds a stage time limitcontent_id, stage, deadline, assignee_id
Event typeFires whenKey data fields
publish.startedA publish operation beginscontent_id, channel_id, initiated_by
publish.completedContent is successfully publishedcontent_id, channel_id, external_url, external_id
publish.failedA publish operation failscontent_id, channel_id, error_code, error_message
publish.scheduledContent is scheduled for future publishcontent_id, channel_id, scheduled_at
Event typeFires whenKey data fields
asset.uploadedA new asset is uploadedasset_id, file_name, mime_type, file_size, uploaded_by
asset.updatedAsset metadata is modifiedasset_id, changed_fields[], updated_by
asset.deletedAn asset is deletedasset_id, deleted_by
Event typeFires whenKey data fields
campaign.createdA new campaign is createdcampaign_id, name, created_by
campaign.status_changedCampaign status changescampaign_id, from_status, to_status
campaign.completedAll campaign content is publishedcampaign_id, content_count
Event typeFires whenKey data fields
cms.content.createdA new content item is created in CMScontent_id, content_type, locale, created_by
cms.content.updatedContent is saved in CMScontent_id, version, changed_properties[], updated_by
cms.content.publishedContent is published in CMScontent_id, version, published_by, url
cms.content.unpublishedContent is taken offlinecontent_id, unpublished_by
cms.content.deletedContent is permanently deletedcontent_id, deleted_by
cms.content.movedContent is moved in the content treecontent_id, from_parent, to_parent, moved_by
Event typeFires whenKey data fields
cms.media.uploadedMedia is uploaded to CMSmedia_id, file_name, mime_type, folder_id
cms.media.deletedMedia is deleted from CMSmedia_id, deleted_by
Example: content.published payload
json
{
"event_id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event_type": "publish.completed",
"timestamp": "2026-03-25T14:30:00Z",
"source": "cmp",
"account_id": "acct_12345",
"data": {
  "content_id": "cnt_98765",
  "title": "Spring Product Launch Announcement",
  "content_type": "blog-post",
  "channel_id": "ch_cms_prod",
  "channel_name": "Production CMS",
  "external_id": "cms_page_4567",
  "external_url": "https://example.com/blog/spring-launch",
  "initiated_by": {
    "user_id": "usr_11111",
    "email": "jane@example.com"
  },
  "campaign_id": "cmp_22222",
  "version": 3
}
}
Example: workflow.approved payload
json
{
"event_id": "evt_f1e2d3c4-b5a6-7890-fedc-ba0987654321",
"event_type": "workflow.approved",
"timestamp": "2026-03-25T10:15:00Z",
"source": "cmp",
"account_id": "acct_12345",
"data": {
  "content_id": "cnt_98765",
  "title": "Spring Product Launch Announcement",
  "stage": "legal-review",
  "approved_by": {
    "user_id": "usr_33333",
    "email": "legal@example.com"
  },
  "comment": "Approved. Disclaimer language is compliant.",
  "next_stage": "final-approval"
}
}
GuaranteeBehavior
Delivery orderEvents are delivered in approximate chronological order but strict ordering is not guaranteed
At-least-once deliveryEach event is delivered at least once; duplicates are possible
IdempotencyUse the event_id field to deduplicate events in your handler
TimeoutWebhook endpoints must respond within 10 seconds
Expected responseReturn 2xx status to acknowledge receipt

If your endpoint does not return a 2xx response, the system retries with exponential backoff:

AttemptDelay after failure
1st retry30 seconds
2nd retry2 minutes
3rd retry10 minutes
4th retry1 hour
5th retry6 hours

After 5 failed retries, the event is marked as failed. Failed events are available in the webhook log for manual inspection and replay.

If a webhook endpoint fails consistently (more than 95% of deliveries fail over 24 hours), the webhook is automatically disabled. You receive an email notification and can re-enable it after fixing the endpoint.

  • Respond quickly — Return 200 OK immediately and process the event asynchronously. Long-running handlers risk timeouts.
  • Verify signatures — Always validate the X-Optimizely-Signature header to ensure payloads are authentic.
  • Handle duplicates — Store processed event_id values and skip events you have already handled.
  • Use specific event subscriptions — Subscribe only to the events you need rather than all events. This reduces unnecessary traffic and processing.
  • Monitor your endpoint — Track response times and error rates. Set up alerts for elevated failure rates.