Docs

Webhooks

Have Capa call your server when content changes, signed so you can prove the request came from Capa.

View as Markdown

A webhook endpoint is a URL on your server that Capa calls when something changes in your project: an entry is published, a model changes, a file is uploaded, a scheduled publish fails. Your site can rebuild a page, purge a cache or update a search index the moment it happens.

Add an endpoint

  1. Open Developers > Webhooks in the Capa admin and press Add endpoint.
  2. Enter the URL. It must be https.
  3. Pick the events. A new endpoint starts with instance.published, instance.unpublished and instance.deleted ticked.
  4. Save, and copy the signing secret (whsec_…). It is shown in full once. Store it on your server as CAPA_WEBHOOK_SECRET.
  5. Choose Send test event. A webhook.test event goes to this endpoint and to no other.

An endpoint hears about events that happen after it was created. Adding one never replays history.

What arrives

One event is one POST with a JSON body. The key order is fixed, because the exact bytes are what is signed:

{
  "id": "evt_00000000-0000-4000-8000-00000000abcd",
  "type": "instance.published",
  "apiVersion": "2026-09-22",
  "occurredAt": "2026-09-23T14:02:11.004Z",
  "tenantId": "3f6c1b4e-2a71-4d0e-9c55-8b0a1f2e7d34",
  "actor": { "kind": "user", "id": "6d2e4f8a-0c91-4b11-8a77-5c3e0f9b1d22" },
  "data": {
    "instanceId": "8b0a1f2e-7d34-4c55-9e51-0f9b1d225c3e",
    "modelId": "2c7e5b8f-0a13-4d31-9b6a-a4e7c2108f52",
    "modelNamespace": "blog_post",
    "versionId": "5c3e0f9b-1d22-4a77-8b11-6d2e4f8a0c91",
    "versionNumber": 7,
    "publishedAt": "2026-09-23T14:02:11.004Z",
    "title": "Summer Guide",
    "scheduledActionId": null
  },
  "links": {
    "self": "https://api.capacms.com/v2/agent/model-instances/8b0a1f2e-7d34-4c55-9e51-0f9b1d225c3e",
    "public": "https://cdn.capacms.com/api/entries/blog_post/8b0a1f2e-7d34-4c55-9e51-0f9b1d225c3e"
  }
}
FieldRead it as
idthe event's id. It is the same on every retry and every redelivery, and it is also the Idempotency-Key header. Store it and ignore a repeat
typeone of the events
apiVersionthe envelope's version. It changes only when the envelope's shape does
occurredAtwhen the change happened, not when this attempt was sent
actorwho did it: kind is user, api_key, schedule or system, with an id where there is one
dataids, the model's namespace, title, versionId, versionNumber, publishedAt, and whatever else the type carries
linkspublic reads the published entry with your read key. It is sent on instance.published only. self is the resource on the agent API. Either may be absent

The namespace key is modelNamespace on instance.published, instance.unpublished and model.published, and namespace on every other type. Read both.

The body says what changed. It does not carry the content, unless you turn on Include content data for the endpoint: then instance.published carries the published entry in data.data. Leave it off unless your receiver cannot call back, since a body that carries content leaks it into every log on the way.

Fetch what changed

Read the entry by its id, with your own key:

curl "https://cdn.capacms.com/api/entries/$NAMESPACE/$INSTANCE_ID" \
  -H "x-api-key: $CAPA_KEY" \
  -H 'Capa-Version: 2026-10-01'

Take the namespace from data.modelNamespace or data.namespace, and the id from data.instanceId. With a production key the read returns the published entry. After instance.unpublished or instance.deleted it answers 404 entry_not_found. A read in the same moment as a publish can still see the old copy. See Caching.

On instance.published, links.public is this same read, so you can follow it. links.self is the entry on the agent API, /v2/agent, which a legacy key reads. A cap_ key reads it once agent keys are switched on for Capa.

Next