Skip to content

Set Up Graph Webhooks

⏱ 20 minutes intermediate
📜CoreGraph

Graph indexes content and makes it queryable, but your frontend may need to react to content changes proactively. Webhooks send HTTP notifications to your systems when content is created, updated, or deleted in Graph. This enables automatic static site rebuilds, CDN cache purges, and downstream system updates without polling.

Create an HTTPS endpoint that accepts POST requests. The endpoint must respond with a 2xx status code within 10 seconds to confirm receipt.

Webhook receiver endpoint
javascript
import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.GRAPH_WEBHOOK_SECRET;

app.post('/webhooks/graph', (req, res) => {
  // Verify the webhook signature
  const signature = req.headers['x-graph-signature'];
  const payload = JSON.stringify(req.body);
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { type, data } = req.body;
  console.log(`Received: ${type} for ${data.contentType}`);

  // Handle the event
  switch (type) {
    case 'content/updated':
    case 'content/created':
      triggerRebuild(data);
      break;
    case 'content/deleted':
      purgeCache(data);
      break;
  }

  res.status(200).json({ received: true });
});

app.listen(3001);

Register your endpoint with Graph through the admin API. Specify which content events should trigger notifications.

Register a webhook
bash
curl -X POST \
  https://cg.optimizely.com/api/content/v3/webhooks \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Basic {base64(AppKey:Secret)}' \
  -d '{
    "url": "https://your-app.com/webhooks/graph",
    "events": [
      "content/created",
      "content/updated",
      "content/deleted"
    ],
    "secret": "your-webhook-secret",
    "enabled": true
  }'
EventTriggerUse case
content/createdNew content published for the first timeAdd to search index, send notifications
content/updatedExisting content republishedRebuild affected pages, purge caches
content/deletedContent unpublished or removedRemove from search, purge caches
sync/completedFull sync operation finishedTrigger complete site rebuild

For large sites, you may not want every content change to trigger a webhook. Filter notifications to specific content types.

Filtered webhook registration
bash
curl -X POST \
  https://cg.optimizely.com/api/content/v3/webhooks \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Basic {base64(AppKey:Secret)}' \
  -d '{
    "url": "https://your-app.com/webhooks/graph",
    "events": ["content/updated", "content/created"],
    "filters": {
      "contentTypes": ["ArticlePage", "ProductPage", "LandingPage"]
    },
    "secret": "your-webhook-secret",
    "enabled": true
  }'
Next.js on-demand revalidation
javascript
// pages/api/revalidate.js
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).end();
  }

  const { data } = req.body;

  try {
    // Revalidate the specific page
    if (data.url) {
      await res.revalidate(data.url);
    }

    // Revalidate listing pages that may include this content
    await res.revalidate('/articles');
    await res.revalidate('/');

    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).json({ error: 'Revalidation failed' });
  }
}
CDN cache purge on content change
javascript
async function purgeCache(data) {
  const urlsToPurge = [
    data.url,
    '/articles',
    '/sitemap.xml',
  ].filter(Boolean);

  await fetch('https://api.cdn-provider.com/purge', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${CDN_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ urls: urlsToPurge }),
  });

  console.log(`Purged cache for: ${urlsToPurge.join(', ')}`);
}

List, update, and remove webhook registrations through the admin API.

Manage webhooks
bash
# List all registered webhooks
curl -s \
  https://cg.optimizely.com/api/content/v3/webhooks \
  -H 'Authorization: Basic {base64(AppKey:Secret)}'

# Disable a webhook temporarily
curl -X PATCH \
  https://cg.optimizely.com/api/content/v3/webhooks/{webhook-id} \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Basic {base64(AppKey:Secret)}' \
  -d '{"enabled": false}'

# Delete a webhook
curl -X DELETE \
  https://cg.optimizely.com/api/content/v3/webhooks/{webhook-id} \
  -H 'Authorization: Basic {base64(AppKey:Secret)}'
IssueCauseResolution
No webhooks receivedEndpoint not reachableVerify the URL is publicly accessible over HTTPS
401 responses loggedSignature mismatchVerify the webhook secret matches between Graph config and your endpoint
Duplicate notificationsRetry after timeoutEnsure your endpoint responds within 10 seconds; implement idempotent handling
Missed eventsWebhook disabled after failuresRe-enable the webhook; fix the endpoint issue that caused consecutive failures