Search documentation

Search documentation

Developers

Webhooks

Respond securely to Contoprix publish and content events.

Refresh your application when editors publish#

A webhook is an HTTP POST from Contoprix to your application after a selected CMS event. The most common use is refreshing a cached page when an editor publishes a change.

A typical page-publish flow
Editor publishes /about
  -> Contoprix queues a signed webhook
  -> your app verifies the signature
  -> your app invalidates the relevant cache
  -> visitors receive the new published page

Webhooks are asynchronous. Your handler must be safe to receive the same delivery more than once.

Create an endpoint safely#

When a website webhook endpoint is created, Contoprix generates a signing secret and returns it in the creation response. Copy it immediately into your application's secret store. The normal endpoint response does not expose the secret afterward.

Choose:

  • a clear name, such as Production Next.js revalidation;
  • a publicly reachable destination URL for your application;
  • only the events the destination needs; and
  • an active or inactive state.

The destination must be an absolute HTTP or HTTPS URL that resolves to a public global address. Localhost, private-network addresses, metadata hosts, and URL-embedded credentials are blocked. Use HTTPS for production. For local development, expose a temporary public URL rather than configuring localhost as the destination.

Events that are currently dispatched#

EventWhen it is sentUseful data in data
page.publishedA page version is publishedpageId, slug, languageCode, publishedAtUtc
content.publishedA content entry version is publishedentryId, contentTypeId, slug, versionId, publishedAtUtc
media.uploadedA media item is uploadedmediaId, fileName, url, mimeType, size
content.review.submittedAn entry enters reviewWorkflow and entry information
content.review.approvedA review is approvedWorkflow and entry information
content.review.changes_requestedA reviewer asks for changesWorkflow and entry information
content.review.cancelledA review is cancelledWorkflow and entry information

Event data can grow over time. Read the fields your handler needs and ignore unfamiliar fields so additions do not break it.

Request format#

Contoprix sends a JSON body similar to this:

Example page.published delivery
{
  "id": "delivery-event-id",
  "event": "page.published",
  "websiteId": "website-id",
  "timestamp": "2026-08-22T10:15:30.0000000+00:00",
  "data": {
    "pageId": "page-id",
    "slug": "/about",
    "languageCode": "en",
    "publishedAtUtc": "2026-08-22T10:15:29.0000000+00:00"
  }
}

It also sends these headers:

HeaderMeaning
x-contoprix-signatureLowercase hexadecimal HMAC-SHA256 signature of the exact raw UTF-8 body
x-contoprix-eventInternal event enum name, such as PagePublished
x-contoprix-delivery-idThe delivery record ID, useful for idempotency logs

Next.js: the quickest safe handler#

Install the Next.js integration, then add a route handler:

app/api/contoprix/webhook/route.ts
import { handleContoprixWebhook } from "@contoprix/next/server";

export async function POST(request: Request) {
  return handleContoprixWebhook(request, {
    secret: process.env.CONTOPRIX_WEBHOOK_SECRET!,
    onPagePublished(event) {
      console.info("Published page", event.data.slug);
    },
    onContentPublished(event) {
      console.info("Published content", event.data.entryId);
    },
  });
}

The helper verifies x-contoprix-signature against the raw body using a timing-safe comparison, parses the envelope, and returns appropriate 401 or 400 responses for bad requests. By default, it also revalidates the published page path and Contoprix page, navigation, and content tags for page.published and content.published events.

Set the same generated signing secret in your deployment environment:

.env.local
CONTOPRIX_WEBHOOK_SECRET=the-secret-returned-when-the-endpoint-was-created

Verify manually in another server framework#

The important rule is to verify the raw request body before parsing JSON. Parsing and re-serializing changes the bytes being signed.

A framework-neutral Node.js example
import { createHmac, timingSafeEqual } from "node:crypto";

export function isValidContoprixWebhook(
  rawBody: string,
  signature: string | null,
  secret: string,
) {
  if (!signature) return false;

  const supplied = signature.trim().replace(/^sha256=/i, "").toLowerCase();
  if (!/^[a-f0-9]{64}$/.test(supplied)) return false;

  const expected = createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");

  const suppliedBuffer = Buffer.from(supplied, "hex");
  const expectedBuffer = Buffer.from(expected, "hex");

  return suppliedBuffer.length === expectedBuffer.length &&
    timingSafeEqual(suppliedBuffer, expectedBuffer);
}

After verification, parse the JSON, record the event or delivery ID, and perform the smallest needed cache refresh or background action.

Make processing idempotent#

Contoprix marks a 2xx HTTP response as delivered. Failed deliveries can be retried, up to 10 total attempts, with increasing delays (about 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours, then daily retries).

Store a processed event ID or x-contoprix-delivery-id before doing irreversible work:

Idempotent handler idea
If delivery ID was already processed:
  return 200

Store delivery ID as processing
Refresh the relevant cache or queue the work
Mark delivery ID completed
Return 200

Do not use a webhook to trigger arbitrary URLs, clear every cache entry, or trust unverified request data.

SituationResponseWhy
Signature is missing or invalid401Tells Contoprix the request was not accepted.
JSON body is invalid400The payload cannot be processed.
Delivery already completed200Makes retries safe.
Cache refresh or queue succeeded200 or another 2xxMarks the delivery complete.
Temporary database or provider outage5xxAllows a retry.

Keep the handler fast. Queue slow work such as search indexing, image analysis, or third-party synchronization after signature verification.

Troubleshooting#

ProblemCheck this
No delivery reaches the appConfirm the endpoint is active, public, and configured for the event.
Destination is rejected when configuredIt may resolve to localhost, a private address, an unsafe host, or contain embedded credentials.
Every request returns 401Confirm the raw body is verified with the exact generated signing secret.
The page remains staleConfirm the handler revalidates the route or cache tag matching data.slug.
Duplicate work happensDeduplicate by envelope id or x-contoprix-delivery-id.
Delivery keeps retryingReturn a 2xx only after the event is safely accepted or queued. Check endpoint delivery logs for status and error details.

Use SDK server helpers for day-to-day data access, and use a webhook only to react to a real CMS event after it has been verified.