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 field Required Description urlYes HTTPS endpoint that receives webhook payloads eventsYes Array of event types to subscribe to secretRecommended Shared secret for payload signature verification activeNo Whether the webhook is enabled (default: true) descriptionNo Human-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 (Node.js)
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:
Field Type Description event_idstring Unique event identifier (UUID) event_typestring Event type identifier (e.g., content.published) timestampISO 8601 When the event occurred sourcestring Originating system: cmp or cms-saas account_idstring Your Optimizely account ID dataobject Event-specific payload (varies by event type)
Event type Fires when Key data fields content.createdA new content item is created content_id, content_type, title, created_bycontent.updatedContent fields are modified content_id, changed_fields[], updated_by, versioncontent.deletedContent is moved to trash content_id, deleted_bycontent.restoredContent is restored from trash content_id, restored_by
Event type Fires when Key data fields workflow.transitionedContent moves to a new workflow stage content_id, from_stage, to_stage, transitioned_byworkflow.approvedA reviewer approves content content_id, stage, approved_by, commentworkflow.rejectedA reviewer rejects content content_id, stage, rejected_by, comment, reasonworkflow.changes_requestedA reviewer requests changes content_id, stage, requested_by, commentworkflow.assignedContent is assigned to a new reviewer content_id, stage, assignee_id, assigned_byworkflow.overdueContent exceeds a stage time limit content_id, stage, deadline, assignee_id
Event type Fires when Key data fields publish.startedA publish operation begins content_id, channel_id, initiated_bypublish.completedContent is successfully published content_id, channel_id, external_url, external_idpublish.failedA publish operation fails content_id, channel_id, error_code, error_messagepublish.scheduledContent is scheduled for future publish content_id, channel_id, scheduled_at
Event type Fires when Key data fields asset.uploadedA new asset is uploaded asset_id, file_name, mime_type, file_size, uploaded_byasset.updatedAsset metadata is modified asset_id, changed_fields[], updated_byasset.deletedAn asset is deleted asset_id, deleted_by
Event type Fires when Key data fields campaign.createdA new campaign is created campaign_id, name, created_bycampaign.status_changedCampaign status changes campaign_id, from_status, to_statuscampaign.completedAll campaign content is published campaign_id, content_count
Event type Fires when Key data fields cms.content.createdA new content item is created in CMS content_id, content_type, locale, created_bycms.content.updatedContent is saved in CMS content_id, version, changed_properties[], updated_bycms.content.publishedContent is published in CMS content_id, version, published_by, urlcms.content.unpublishedContent is taken offline content_id, unpublished_bycms.content.deletedContent is permanently deleted content_id, deleted_bycms.content.movedContent is moved in the content tree content_id, from_parent, to_parent, moved_by
Event type Fires when Key data fields cms.media.uploadedMedia is uploaded to CMS media_id, file_name, mime_type, folder_idcms.media.deletedMedia is deleted from CMS media_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"
}
}
Guarantee Behavior Delivery order Events are delivered in approximate chronological order but strict ordering is not guaranteed At-least-once delivery Each event is delivered at least once; duplicates are possible Idempotency Use the event_id field to deduplicate events in your handler Timeout Webhook endpoints must respond within 10 seconds Expected response Return 2xx status to acknowledge receipt
If your endpoint does not return a 2xx response, the system retries with exponential backoff:
Attempt Delay after failure 1st retry 30 seconds 2nd retry 2 minutes 3rd retry 10 minutes 4th retry 1 hour 5th retry 6 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.