# Webhooks

Payload and delivery facts for Source CMS [webhooks](/start-here/glossary/#webhook). Webhooks are **outgoing only**: your [site](/start-here/glossary/#site) sends HTTP requests to URLs you configure when content changes. There is no inbound webhook mechanism; inbound integration is the [JSON:API](/start-here/glossary/#jsonapi) (see the [Content API reference](/source-cms/reference/content-api/)).

For setting up a webhook and wiring it to frontend revalidation, see the [webhooks guide](/source-cms/content-api/webhooks/).

## Event types

Webhooks trigger on changes to four kinds of entities, each with three trigger events (the labels below are the trigger checkboxes shown when creating a webhook; a site can have multiple webhooks):

| Entity kind | Create | Update | Delete |
|---|---|---|---|
| Nodes ([CMS content](/start-here/glossary/#cms-content), entries of a [content type](/start-here/glossary/#content-type-bundle)) | Node creation | Node update | Node deletion |
| Media entities | Media creation | Media update | Media deletion |
| Taxonomy terms | Taxonomy term creation | Taxonomy term update | Taxonomy term deletion |
| [Pages](/start-here/glossary/#pages-canvas) (built with Drupal Canvas) | Page creation | Page update | Page deletion |

Each time a matching event occurs, the site sends an HTTP request to the webhook's configured URL.

Used in: [webhooks guide](/source-cms/content-api/webhooks/).

## Delivery request

Every delivery is a `POST` to the webhook's configured URL with these headers (the complete set; there is no signature or shared-secret header):

| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `Idempotency-Key` | SHA-1 of the payload. Retries of the same delivery carry the same key; deduplicate on it. It is a hash, not a keyed signature, so it does not authenticate the sender |
| `User-Agent` | `Acquia CMS` |

Because deliveries are unsigned, the receiver authenticates them by the secret you put in the registered URL (see the [webhooks guide](/source-cms/content-api/webhooks/)), and anything load-bearing should be re-fetched over [JSON:API](/start-here/glossary/#jsonapi) rather than trusted from the payload.

Used in: [webhooks guide](/source-cms/content-api/webhooks/).

## Payload

The payload is the changed entity's own JSON:API document, exactly as `GET /api/{entity_type}/{bundle}/{uuid}` would return it (anatomy in the [response document reference](/source-cms/reference/response-document/)), with three members added under `data.meta` and a top-level `timestamp`. The shape is identical for create, update, and delete; `data.meta.operation` tells them apart, and a delete payload carries the entity's last state, captured just before deletion.

Example payload (node creation):

```jsonc
{
  "jsonapi": { "version": "1.1", "meta": { … } },
  "data": {
    "type": "node--article",
    "id": "af22b856-b697-48e9-89e3-38327c36e2af",
    "links": { "self": { … } },
    "attributes": { "title": "Spring launch recap (webhook capture)", "status": true, … },
    "relationships": { … },
    "meta": {
      "operation": "create",     // "create" | "update" | "delete"
      "user": "admin",           // account name that triggered the event
      "edit_url": "https://your-site.example.com/node/6/edit"
    }
  },
  "links": { "self": { … } },
  "timestamp": 1783098941        // delivery time, UNIX seconds
}
```

One caveat (verified): the entity data is normalized with the *triggering user's* permissions. If that user cannot view the entity (for example, a workflow moved it to an unpublished state), the payload carries a JSON:API `errors` document in place of the entity data, and `data` contains only the `meta` block. Handle payloads without `data.attributes` by re-fetching over JSON:API with your own credentials.

Used in: [webhooks guide](/source-cms/content-api/webhooks/).

## Delivery and retries

| Fact | Value |
|---|---|
| Delivery | One HTTP request to the webhook URL per triggering event |
| Success condition | Any success (2xx) response after redirects; an error status (4xx/5xx) or connection failure counts as a failed delivery (verified) |
| Retry schedule | A failed delivery is retried **5 times, at 30-second intervals** (verified) |
| After the final retry | No further attempts; the failure is visible in the delivery history |

<ConceptDiagram name="webhook-delivery" height={380} />

So a delivery that never succeeds stops roughly two and a half minutes after the event. If your handler needs longer than that to become reachable (a deploy, a cold start), it will miss the event. Recover by re-fetching state over JSON:API rather than waiting for a redelivery.

Used in: [webhooks guide](/source-cms/content-api/webhooks/).

## Delivery history

Every webhook the site triggers is recorded on the `Webhooks History` page in the site's admin UI, which shows for each delivery:

- the status (whether it succeeded or failed),
- the payload that was sent,
- the retry attempts made.

This is where to confirm whether a "missing" delivery was ever sent, whether a retry eventually succeeded, and what the payload contained. The [webhooks guide](/source-cms/content-api/webhooks/) walks through reaching it from `API > Webhooks`.

Used in: [webhooks guide](/source-cms/content-api/webhooks/).

## Used in

- [Guide: react to content changes with webhooks](/source-cms/content-api/webhooks/): setup, handler verification, and the webhook → frontend revalidation pattern
