Skip to content
Delivery API
Before you plan around a feature, check the capability status.

Reference

Webhooks

Signed notifications Atlas sends to your website when content is published, so you can refresh exactly the affected pages.

Create webhooks in Studio under Integrations → Webhooks. Each one has an HTTPS URL, the events it receives and a signing secret starting with whsec_, shown once.

Atlas only delivers to public HTTPS addresses. Private network addresses are refused; plain HTTP is allowed only to localhost while you develop.

POST https://your-site.example/api/atlas/webhook
Content-Type: application/json
X-Atlas-Event: content.page.published
X-Atlas-Delivery: <unique delivery id>
X-Atlas-Signature: t=1761210000,v1=5f2b…
app/api/atlas/webhook/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto';
import { revalidatePath, revalidateTag } from 'next/cache';
const seen = new Set<string>(); // use a shared store (Redis, KV) when you run several instances
export async function POST(request: Request) {
const body = await request.text(); // the raw body, before any JSON parsing
const header = request.headers.get('x-atlas-signature') ?? '';
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=', 2)));
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300)
return new Response(null, { status: 400 });
const expected = createHmac('sha256', process.env.ATLAS_WEBHOOK_SECRET!)
.update(`${timestamp}.${body}`)
.digest();
const given = Buffer.from(String(parts.v1 ?? ''), 'hex');
if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
return new Response(null, { status: 401 });
}
const delivery = request.headers.get('x-atlas-delivery') ?? '';
if (seen.has(delivery)) return new Response(null, { status: 200 }); // already handled
seen.add(delivery);
const payload = JSON.parse(body); // e.g. { pageId, route, revision } for content.page.published
if (typeof payload.route === 'string') revalidatePath(payload.route);
else revalidateTag('atlas'); // entries, globals, translations: refresh everything tagged 'atlas'
return new Response(null, { status: 200 });
}

The checks, in order:

  1. Use the raw request body.
  2. Reject timestamps more than 5 minutes old.
  3. Compute HMAC-SHA256("<t>.<raw body>") with your signing secret and compare in constant time.
  4. Ignore repeated X-Atlas-Delivery ids.

Log the event type and delivery id, never the body or the secret.

The request body is the event’s payload. For content.page.published it is { "pageId", "route", "revision" }. The event type is in the X-Atlas-Event header.

Webhooks are set up once, in Development, and also receive events from the live website. Each payload has environment: "development" or "production", so you can tell them apart.

Event When
content.page.published A page is published or rolled back
content.page.unpublished A page is taken off the website
content.page.route.changed A page moves to a new address (a redirect is added)
content.entry.published A collection entry is published
content.entry.unpublished A collection entry is taken off the website
content.global.published The header, footer or another global is published
content.translation.published A translation is published
contract.activated A new site contract is activated
seo.redirects.changed Redirects change
media.asset.uploaded A media file is uploaded
form.published A form is published
form.submission.accepted A form submission is accepted
lead.created A new lead arrives (ids only, no personal data)
lead.merged A lead is merged into another (ids only)
bot.published A chatbot is published

Atlas waits up to 10 seconds for your response and does not follow redirects. Use Studio’s delivery history to inspect payloads and failures.