Webhooks
Have Capa call your server when content changes, signed so you can prove the request came from Capa.
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
- Open Developers > Webhooks in the Capa admin and press Add endpoint.
- Enter the URL. It must be
https. - Pick the events. A new endpoint starts with
instance.published,instance.unpublishedandinstance.deletedticked. - Save, and copy the signing secret (
whsec_…). It is shown in full once. Store it on your server asCAPA_WEBHOOK_SECRET. - Choose Send test event. A
webhook.testevent 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"
}
}| Field | Read it as |
|---|---|
id | the 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 |
type | one of the events |
apiVersion | the envelope's version. It changes only when the envelope's shape does |
occurredAt | when the change happened, not when this attempt was sent |
actor | who did it: kind is user, api_key, schedule or system, with an id where there is one |
data | ids, the model's namespace, title, versionId, versionNumber, publishedAt, and whatever else the type carries |
links | public 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.