# Webhook

A *Webhook* is a setting that automatically runs a predefined action when something happens in a *Space* (for example, a *Content* is created or published). The action is one of two things: it sends an HTTP request to an external URL (`url`), or it runs a *Script* inside the *Space* (`script`). You use it for integrating external systems or for automation. For instance, you can configure it to call your internal notification server every time a product *Content* is published, or to run follow-up work with a predefined *Script*.

You specify **exactly one** of `url` and `script`. Specifying both, or leaving both empty, is rejected. A *Webhook* is a sub-resource of *Space* in CMA, and its path is based on `/spaces/{spaceId}/webhooks`.

## Resource structure {#resource-structure}

The following is the single-read response for the *Webhook* "Product change notification". Along with `sys` (system properties), it has setting fields such as the delivery target, the subscribed events, and the trigger conditions.

```json
{
  "sys": {
    "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
    "type": "Webhook",
    "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
    "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
    "createdAt": "2026-06-18T11:30:00.000Z",
    "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
    "updatedAt": "2026-06-18T11:30:00.000Z",
    "version": 1
  },
  "name": "Product change notification",
  "filters": [
    { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
  ],
  "headers": [
    { "key": "X-Source", "value": "weegloo", "secret": false }
  ],
  "httpBasicUsername": "dailywear",
  "topics": ["Content.Create", "Content.Publish"],
  "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
  "url": "https://api.dailywear.example/webhooks/products",
  "activate": true,
  "runAs": "HookOwner"
}
```

Key properties:

- `sys.id`: The unique identifier of the *Webhook*. It goes into `{webhookId}` in the single-read, update, and delete paths.
- `url`: The external target URL to call when an event occurs. Specify exactly one of it and `script`.
- `script`: A reference to the *Script* to run instead of an external call. Specify exactly one of it and `url`. It is not present in the example above. Explained in [url and script (exactly one)](#url-xor-script) below.
- `runAs`: The user identity that `script` runs as. Explained in [runAs](#run-as) below.
- `topics`: An array that defines which events to subscribe to. The format is explained in [topics](#topics) below.
- `filters`: The conditions that actually trigger the *Webhook* among the subscribed events. Explained in [filters](#filters) below.
- `transformation`: A setting that changes the shape of the request going out to `url` (method, body, and so on). Explained in [transformation](#transformation) below.

## System properties (sys) {#system-properties-sys}

Every *Webhook* carries its common system properties in the `sys` object. `space`, `createdBy`, and `updatedBy` come in the `Refer` shape (`{ "sys": { "id", "type": "Refer", "targetType" } }`).

| Property | Type | Description |
|---|---|---|
| `id` | string | The unique identifier of the resource. |
| `type` | string | The kind of resource. For a *Webhook* it is always `"Webhook"`. |
| `space` | Refer&lt;Space&gt; | The *Space* this *Webhook* belongs to. |
| `createdBy` | Refer&lt;User&gt; | The user who created it. |
| `createdAt` | string (date-time) | The creation time. |
| `updatedBy` | Refer&lt;User&gt; | The user who last updated it. |
| `updatedAt` | string (date-time) | The last update time. |
| `version` | integer (≥1) | The resource version. It goes up by 1 on every update. |

A *Webhook* is a settings resource, so it has no concept of publishing. Unlike *Content* or *Content Type*, it has no publishing-state properties such as `publish`, `archive`, or `status`, only a `version` for change tracking. Turning it on and off is not controlled by publishing but by the body field `activate`.

## Body properties {#body-properties}

The body of a *Webhook* (the setting values you send on create and update, and that come back in the response) is made up of the following fields.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string (1-64) | ✅ | The *Webhook* name. |
| `url` | string (url) | △ | The external target URL to call when an event occurs. Exactly one of this and `script`. See [url and script (exactly one)](#url-xor-script) below. |
| `script` | Refer&lt;Script&gt; | △ | A reference to the *Script* to run instead of an external call. Exactly one of this and `url`. See [url and script (exactly one)](#url-xor-script) below. |
| `runAs` | WebhookRunAs | | The user identity that `script` runs as. `HookOwner` (default) or `EventUser`. See [runAs](#run-as) below. |
| `activate` | boolean | ✅ | Whether it is on. If `false`, nothing runs even when an event occurs. |
| `topics` | string[] | ✅ | The array of events to subscribe to. See [topics](#topics) below. |
| `filters` | Filter[] | ✅ | The array of trigger conditions. If empty, every subscribed event triggers. See [filters](#filters) below. |
| `headers` | WebhookHeader[] (0-30) | ✅ | The array of HTTP headers to send with the `url` call. |
| `httpBasicUsername` | string (1-32) | | The HTTP Basic authentication username for the `url` call. |
| `httpBasicPassword` | string (1-32) | | The HTTP Basic authentication password for the `url` call. **It is write-only. It does not appear in the response.** |
| `transformation` | Transformation | ✅ | Customizes the request going out to `url`. See [transformation](#transformation) below. |

You specify **exactly one** of the △-marked `url` and `script`. Specifying both, or leaving both empty, is rejected.

Each item in `headers` is made up of `key` (required), `value` (required), and `secret` (optional, boolean). If you set `secret` to `true`, that value stays masked in the delivery records (see [WebhookLog](#webhook-log) below). **Reading this *Webhook*, however, returns the value in plain text.** The only thing left out of the response is `httpBasicPassword`, so treat a value you put in a `secret` header as visible to every role that can read this *Webhook*, and keep that role narrow.

### topics {#topics}

Each item in `topics` has the format `{resource}.{action}`. For example: `Content.Create`, `Content.Publish`, `Media.Create`.

The action is one of the following, or `*`, which means all actions of the resource (for example, `Content.*`).

| Action | Meaning |
|---|---|
| `All` | All actions. |
| `Create` | Creation. |
| `Read` | Reading. |
| `Edit` | Editing. |
| `Save` | Saving (update). **The update event is `Save`. It is not `Update`.** |
| `Delete` | Deletion. |
| `Publish` | Publishing. |
| `Unpublish` | Unpublishing. |
| `Archive` | Archiving. |
| `Unarchive` | Unarchiving. |

### filters {#filters}

`filters` is an array that narrows down which of the subscribed `topics` actually trigger the *Webhook*. Each filter has the following shape.

```json
{ "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
```

- `doc`: The field path to compare. It is one of `sys.id`, `sys.contentType.sys.id`, `sys.createdBy.sys.id`, or `sys.updatedBy.sys.id`.
- `op`: The comparison operator. It is one of `EQ`, `NE`, `IN`, `NOT_IN`, `REGEX`, or `NOT_REGEX`.
- `value`: The comparison value. Give a string for `EQ`, `NE`, `REGEX`, `NOT_REGEX`, and an array of strings for `IN`, `NOT_IN`.

If you set several filters, all of them must be satisfied to trigger (AND). If you leave `filters` empty, every event of the subscribed `topics` triggers.

### transformation {#transformation}

`transformation` changes the shape of the HTTP request going out to `url` (it does not apply to a *Webhook* that uses `script`). If you do not specify it, the entire resource payload goes out as-is with the default `POST`.

| Key | Type | Description |
|---|---|---|
| `method` | string | The HTTP method. One of `GET`, `POST`, `PUT`, `DELETE`, `PATCH`. |
| `contentType` | string | The Content-Type of the request body. The body is serialized in this format ([below](#transformation-body-serialization)). |
| `body` | object | An object that composes the body to send using JSON Pointer templates. |
| `includeBody` | boolean | Whether to also send the body of the triggering resource. |

#### What format the body goes out in {#transformation-body-serialization}

`contentType` decides the serialization format of the body. The comparison ignores case and parameters such as `;charset=…`, and looks only at the leading part. If you do not specify it, or its value is empty, the body is sent as `application/json`. If `includeBody` is `false` or `method` is `GET`, no body is sent, and in that case no `Content-Type` is attached either.

The body a *Webhook* sends is **always an object**. This is because the `body` template is an object, and when you set no template the entire triggered resource goes out as-is.

| Declared `contentType` | Actual outgoing `Content-Type` | Outgoing body |
|---|---|---|
| (none) | `application/json` | JSON |
| `application/json` | The declared value as-is | JSON |
| `application/x-www-form-urlencoded` | The declared value as-is | `product[sku]=TUMBLER-500&product[price]=24000` |
| `text/plain` | **`application/json`** | JSON |
| Anything else (`text/xml` and so on) | The declared value as-is | JSON |

`text/plain` cannot hold an object, so the body is sent with the format corrected to one that can hold it. The header never states something different from the actual body. If the target has to receive the body as text, `contentType` does not solve it, so check the contract on the receiving side.

`form-urlencoded` expands an object into bracket keys and an array into indexes.

| Body | Expanded keys and values |
|---|---|
| `{ "product": { "sku": "TUMBLER-500", "price": 24000 } }` | `product[sku]=TUMBLER-500&product[price]=24000` |
| `{ "tags": ["kitchen", "insulated"] }` | `tags[0]=kitchen&tags[1]=insulated` |
| `{ "items": [{ "sku": "TUMBLER-500" }] }` | `items[0][sku]=TUMBLER-500` |
| `{ "memo": null }` | `memo=` |

Keys and values go out percent-encoded in UTF-8. The table above shows them decoded to make the key structure visible. Even when a value contains `&` or `+`, it is not mistaken for a pair separator or a space, and is delivered as-is.

**Expanding nesting into bracket keys is a widely used convention, not a specification of the format itself.** Check whether the receiving side restores `product[sku]` into a nested object, and if it does not, compose the `body` template with flat keys.

Here is an example of a `transformation` that sends a form.

```json
"transformation": {
  "method": "POST",
  "contentType": "application/x-www-form-urlencoded",
  "includeBody": true,
  "body": {
    "sku": "{ /payload/fields/sku/ko-KR }",
    "price": "{ /payload/fields/price/ko-KR }"
  }
}
```

When a *Content* whose `sku` is `TUMBLER-500` and `price` is `24000` triggers it, the body goes out as `sku=TUMBLER-500&price=24000`.

### url and script (exactly one) {#url-xor-script}

When a *Webhook* is triggered, it performs one of two things. If you specify `url`, it sends an HTTP request to that external URL (the request shape is set by [transformation](#transformation), `headers`, and `httpBasic*`). If you specify `script`, it does not go out externally but runs one *Script* inside the *Space*.

- `url`: The external target URL (`http`/`https`). Blocked targets such as private networks or loopback are rejected.
- `script`: The `Refer` to the *Script* to run.

You must specify **exactly one** of the two. Specifying both, or leaving both empty, is rejected, and the code you get back differs by path (see [Errors](#errors)).

```json
"script": { "sys": { "id": "3trmXRMZcTAjDnphewjj1AaxYcaxlK", "type": "Refer", "targetType": "Script" } }
```

When triggered, the *Script* runs with delegated permissions, and per-statement resource permissions are not re-checked at run time. What is allowed is already checked when the *Script* is authored. For the detailed execution and permission model, see [Script execution semantics, limits, and security](/api/reference/script/execution-and-limits.md#security-model).

### runAs {#run-as}

`runAs` sets **which user identity `script` runs as**. This identity becomes the `createdBy`/`updatedBy` of any resource created or updated during execution, and the `createdBy: ":self"` filter inside a *Script* also resolves against this identity. **It is attribution only, not a permission boundary.** What it can do is determined by the permission check performed when the *Script* is authored.

| Value | Execution identity |
|---|---|
| `HookOwner` | The user who created the *Webhook* (`sys.createdBy`). The default. |
| `EventUser` | The user who caused that event (change), that is, the `sys.updatedBy` of the triggered resource. |

For a *Webhook* that uses only `url`, `runAs` is ignored. If not specified, it is `HookOwner`.

## WebhookLog {#webhook-log}

Each time a *Webhook* attempts a delivery, one record is left behind. It is read-only and has no create, update, or delete endpoints. Its path is `/spaces/{spaceId}/webhooks/{webhookId}/logs`.

```json
{
  "sys": {
    "id": "3trmXRM3RqbgSnifyg7PWhc01Exam",
    "type": "WebhookLog",
    "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
    "requestId": "3trmXRM3qWnLb7Vd1yPcYs04kKrjq",
    "statusCode": 200,
    "errors": [],
    "eventType": "Create",
    "url": "https://api.dailywear.example/webhooks/products",
    "requestAt": "2026-06-18T11:35:00.100Z",
    "responseAt": "2026-06-18T11:35:00.350Z",
    "request": {
      "url": "https://api.dailywear.example/webhooks/products",
      "method": "POST",
      "headers": { "Content-Type": "application/json", "X-Source": "weegloo" },
      "body": "{\"sys\":{\"type\":\"Content\"}}"
    },
    "response": {
      "url": "https://api.dailywear.example/webhooks/products",
      "headers": { "Content-Type": "application/json" },
      "body": "{\"ok\":true}",
      "statusCode": 200
    },
    "createdBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
    "createdAt": "2026-06-18T11:35:00.350Z",
    "updatedBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
    "updatedAt": "2026-06-18T11:35:00.350Z"
  }
}
```

All values are inside `sys`, and there are no body properties. A key with no value is omitted from the response.

`sys.createdBy` points to the *Webhook* that left the record. It is a `Refer` to that *Webhook*, not to a user, and `sys.updatedBy` is the same *Webhook*.

| Property | Type | Description |
|---|---|---|
| `id` | string | The unique identifier of the record. |
| `type` | string | Always `"WebhookLog"`. |
| `space` | Refer&lt;Space&gt; | The *Space* this record belongs to. |
| `requestId` | string | The tracing identifier of this delivery attempt. |
| `statusCode` | integer | The HTTP status code of the response received. |
| `errors` | string[] | The list of failure reasons. In the records of a *Webhook* that sends to a URL it is **always empty** (the status code is what tells you about failure). Only the records of a *Webhook* that runs a *Script* via `script` carry that *Script*'s failure messages. |
| `eventType` | string | The **action name** that caused this delivery (for example, `Create` or `Publish`). It carries only the trailing action, not the `Content.Create` form you write in `topics`. |
| `url` | string | The delivery target URL. |
| `requestAt` | string (date-time) | The time the request was sent. |
| `responseAt` | string (date-time) | The time the response was received. |
| `request` | object | The request that was sent. Its inner structure is below. **It is left out of the list read.** |
| `response` | object | The response that was received. Its inner structure is below. **It is left out of the list read.** |
| `createdBy` | Refer&lt;Webhook&gt; | The *Webhook* that left this record. |
| `createdAt` | string (date-time) | The record creation time. |
| `updatedBy` | Refer&lt;Webhook&gt; | Same as `createdBy`. |
| `updatedAt` | string (date-time) | Same as `createdAt`. |

`request` and `response` each have the following keys.

- `request`: `url` (the target URL the request was sent to) · `method` (the HTTP method) · `headers` (the map of headers sent) · `body` (the body string sent).
- `response`: `url` (the URL the response came from) · `headers` (the map of headers received) · `body` (the body string received) · `statusCode` (the status code received).

The records of a *Webhook* that runs a *Script* via `script` have a different shape. There is no address to send to, so there is no `url`, and the `method` of `request` is fixed to `"SCRIPT"`. The `body` of `request` carries the payload that caused the delivery, and the `body` of `response` carries the value that *Script* returned (or the failure message).

The value of a header with `secret` turned on is stored masked. The real value is not left in the record.

**A long body is stored shortened.** The thresholds are 65,536 characters for the `body` of `request` and 8,192 characters for the `body` of `response`. Anything longer **keeps the start and the end and omits the middle**, and the number of omitted characters is written in that place. If the body is JSON, only long string values are shortened the same way so the structure does not break, so keys and short values are left as they are.

**What separates success from failure differs by integration style.** A *Webhook* that sends to a URL succeeds when the response is 2xx or 3xx. A *Webhook* that runs a *Script* via `script` succeeds when `statusCode` is lower than 400 and `errors` is empty. That single verdict decides both the retention period below and the success rate in [delivery status](#api).

**The list read returns records without `request` and `response`.** That is because the `select` default of the list endpoint is `-sys.response,-sys.request`. To also see the bodies of the request sent and the response received, use the single read, or specify `select` yourself to override that default.

The record of a successful delivery disappears after 1 hour, and the record of a failed delivery after 3 days. No field carrying the expiry time is in the response; the record disappears on its own when the time comes. Values you need to keep longer than that should be stored separately on the receiving server, or saved as *Content* from the *Script* you run via `script`.

## Errors {#errors}

These are the codes you meet when working with a *Webhook*. For codes that are common to every resource, see [common errors](/api/reference/common/errors.md).

| Code | Condition |
|---|---|
| `WGL400042` | On create (`POST`) or full update (`PUT`), both `url` and `script` were specified, or both were left empty. |
| `WGL422061` | On partial update (`PATCH`), both `url` and `script` were specified, or both were left empty. |
| `WGL422050` | `url` points to a blocked target such as a private network or loopback. |

## API {#api}

The base URL for all endpoints below is `https://cma.weegloo.com/v1`, and the `Authorization` header needs a Bearer token that authenticates against CMA. Update (`PUT`) and partial update (`PATCH`) must also send the `X-Weegloo-Version` header (the current resource's `sys.version`) for optimistic concurrency control.

```api-endpoint
{
  "title": "List Webhooks",
  "method": "GET",
  "path": "/spaces/{spaceId}/webhooks",
  "description": "Reads the list of Webhooks in a Space, page by page.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true }
  },
  "queryParameterSchema": {
    "limit": { "type": "integer", "description": "Number of items to return per page (1-100)", "default": 15 },
    "skip": { "type": "integer", "description": "Number of items to skip", "default": 0 },
    "next": { "type": "string", "description": "Next-page cursor (the value from links.next of the previous response)" },
    "prev": { "type": "string", "description": "Previous-page cursor (the value from links.prev of the previous response)" },
    "order": { "type": "string", "description": "Sort order. Comma-separated for multi-level sorting (e.g. sys.createdAt,sys.id)" },
    "select": { "type": "string", "description": "Fields to include (sys.id,sys.createdAt) or exclude (-sys.id). Do not mix include and exclude." },
    "include": { "type": "integer", "description": "Level of related resources to include. 0=default, 1=related resources, 2=nested relations, 3=all", "default": 0 }
  },
  "additionalQueryParams": true,
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "responseExample": {
    "sys": { "type": "TotalPageResponse" },
    "limit": 15,
    "totalCount": 1,
    "items": [
      {
        "sys": {
          "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
          "type": "Webhook",
          "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
          "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
          "createdAt": "2026-06-18T11:30:00.000Z",
          "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
          "updatedAt": "2026-06-18T11:30:00.000Z",
          "version": 1
        },
        "name": "Product change notification",
        "filters": [
          { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
        ],
        "headers": [
          { "key": "X-Source", "value": "weegloo", "secret": false }
        ],
        "httpBasicUsername": "dailywear",
        "topics": ["Content.Create", "Content.Publish"],
        "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
        "url": "https://api.dailywear.example/webhooks/products",
        "activate": true,
        "runAs": "HookOwner"
      }
    ],
    "links": { "self": "/v1/spaces/HnQ32YiH/webhooks" }
  }
}
```

```api-endpoint
{
  "title": "Read one Webhook",
  "method": "GET",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}",
  "description": "Reads one Webhook by its sys.id.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "queryParameterSchema": {
    "include": { "type": "integer", "description": "Level of related resources to include (0-3). 0=default, 1=related resources, 2=nested relations, 3=all", "default": 0 }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
      "type": "Webhook",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "createdAt": "2026-06-18T11:30:00.000Z",
      "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "updatedAt": "2026-06-18T11:30:00.000Z",
      "version": 1
    },
    "name": "Product change notification",
    "filters": [
      { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
    ],
    "headers": [
      { "key": "X-Source", "value": "weegloo", "secret": false }
    ],
    "httpBasicUsername": "dailywear",
    "topics": ["Content.Create", "Content.Publish"],
    "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
    "url": "https://api.dailywear.example/webhooks/products",
    "activate": true,
    "runAs": "HookOwner"
  }
}
```

```api-endpoint
{
  "title": "Create a Webhook",
  "method": "POST",
  "path": "/spaces/{spaceId}/webhooks",
  "description": "Creates a new Webhook. httpBasicPassword is write-only, so it does not appear in the response. The value of a header whose secret is true does appear in plain text in read responses.",
  "responseStatus": 201,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "requestBodySchema": {
    "type": "object",
    "required": ["name", "activate", "topics", "filters", "headers", "transformation"],
    "properties": {
      "name": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The Webhook name" },
      "url": { "type": "string", "description": "The external URL to call when an event occurs. Exactly one of this and script" },
      "script": { "type": "object", "description": "A Refer to the Script to run instead of an external call. Exactly one of this and url", "properties": { "sys": { "type": "object", "properties": { "id": { "type": "string" }, "type": { "type": "string", "enum": ["Refer"] }, "targetType": { "type": "string", "enum": ["Script"] } } } } },
      "runAs": { "type": "string", "enum": ["HookOwner", "EventUser"], "default": "HookOwner", "description": "The user identity that script runs as. HookOwner=the webhook creator, EventUser=the user who caused the change" },
      "activate": { "type": "boolean", "description": "Whether it is on" },
      "topics": { "type": "array", "items": { "type": "string" }, "description": "The events to subscribe to. Format is {resource}.{action} (e.g. Content.Create)" },
      "filters": {
        "type": "array",
        "description": "Trigger conditions. If empty, every event of the subscribed topics triggers",
        "items": {
          "type": "object",
          "required": ["doc", "op", "value"],
          "properties": {
            "doc": { "type": "string", "enum": ["sys.id", "sys.contentType.sys.id", "sys.createdBy.sys.id", "sys.updatedBy.sys.id"], "description": "The field path to compare" },
            "op": { "type": "string", "enum": ["EQ", "NE", "IN", "NOT_IN", "REGEX", "NOT_REGEX"], "description": "The comparison operator" },
            "value": { "description": "The comparison value (a string or an array of strings)" }
          }
        }
      },
      "headers": {
        "type": "array",
        "maxItems": 30,
        "description": "The HTTP headers to send with the request",
        "items": {
          "type": "object",
          "required": ["key", "value"],
          "properties": {
            "key": { "type": "string", "minLength": 1 },
            "value": { "type": "string" },
            "secret": { "type": "boolean", "description": "If true, the value is masked in the delivery records. It appears in plain text in read responses" }
          }
        }
      },
      "httpBasicUsername": { "type": "string", "minLength": 1, "maxLength": 32, "description": "HTTP Basic authentication username (optional)" },
      "httpBasicPassword": { "type": "string", "minLength": 1, "maxLength": 32, "description": "HTTP Basic authentication password (optional, write-only)" },
      "transformation": {
        "type": "object",
        "description": "Customizes the request going out to url",
        "properties": {
          "method": { "type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "PATCH"] },
          "contentType": { "type": "string" },
          "body": { "type": "object", "description": "A body composed with JSON Pointer templates" },
          "includeBody": { "type": "boolean" }
        }
      }
    },
    "example": {
      "name": "Product change notification",
      "url": "https://api.dailywear.example/webhooks/products",
      "activate": true,
      "topics": ["Content.Create", "Content.Publish"],
      "filters": [
        { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
      ],
      "headers": [
        { "key": "X-Source", "value": "weegloo", "secret": false }
      ],
      "httpBasicUsername": "dailywear",
      "httpBasicPassword": "<password>",
      "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true }
    }
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
      "type": "Webhook",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "createdAt": "2026-06-18T11:30:00.000Z",
      "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "updatedAt": "2026-06-18T11:30:00.000Z",
      "version": 1
    },
    "name": "Product change notification",
    "filters": [
      { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
    ],
    "headers": [
      { "key": "X-Source", "value": "weegloo", "secret": false }
    ],
    "httpBasicUsername": "dailywear",
    "topics": ["Content.Create", "Content.Publish"],
    "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
    "url": "https://api.dailywear.example/webhooks/products",
    "activate": true,
    "runAs": "HookOwner"
  }
}
```

```api-endpoint
{
  "title": "Update a Webhook (full replace)",
  "method": "PUT",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}",
  "description": "Replaces the entire Webhook. Since it is a full replace, you must include every value you want to keep in the body. Use PATCH to change only part of it.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" },
    "X-Weegloo-Version": { "type": "integer", "description": "The current resource's sys.version (optimistic concurrency control)" }
  },
  "requestBodySchema": {
    "type": "object",
    "required": ["name", "activate", "topics", "filters", "headers", "transformation"],
    "properties": {
      "name": { "type": "string", "minLength": 1, "maxLength": 64 },
      "url": { "type": "string", "description": "Exactly one of this and script" },
      "script": { "type": "object", "description": "Exactly one of this and url. A Refer to the Script" },
      "runAs": { "type": "string", "enum": ["HookOwner", "EventUser"], "default": "HookOwner" },
      "activate": { "type": "boolean" },
      "topics": { "type": "array", "items": { "type": "string" } },
      "filters": { "type": "array", "items": { "type": "object" } },
      "headers": { "type": "array", "maxItems": 30, "items": { "type": "object" } },
      "httpBasicUsername": { "type": "string", "minLength": 1, "maxLength": 32 },
      "httpBasicPassword": { "type": "string", "minLength": 1, "maxLength": 32 },
      "transformation": { "type": "object" }
    },
    "example": {
      "name": "Product change notification",
      "url": "https://api.dailywear.example/webhooks/products",
      "activate": false,
      "topics": ["Content.Create", "Content.Publish"],
      "filters": [
        { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
      ],
      "headers": [
        { "key": "X-Source", "value": "weegloo", "secret": false }
      ],
      "httpBasicUsername": "dailywear",
      "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true }
    }
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
      "type": "Webhook",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "createdAt": "2026-06-18T11:30:00.000Z",
      "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "updatedAt": "2026-06-18T11:31:00.000Z",
      "version": 2
    },
    "name": "Product change notification",
    "filters": [
      { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
    ],
    "headers": [
      { "key": "X-Source", "value": "weegloo", "secret": false }
    ],
    "httpBasicUsername": "dailywear",
    "topics": ["Content.Create", "Content.Publish"],
    "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
    "url": "https://api.dailywear.example/webhooks/products",
    "activate": false,
    "runAs": "HookOwner"
  }
}
```

```api-endpoint
{
  "title": "Partially update a Webhook (JSON Patch)",
  "method": "PATCH",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}",
  "description": "Updates only part of a Webhook with RFC 6902 JSON Patch. The request Content-Type header must be application/json-patch+json.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "requestContentType": "application/json-patch+json",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" },
    "Content-Type": { "type": "string", "description": "application/json-patch+json (RFC 6902 JSON Patch)" },
    "X-Weegloo-Version": { "type": "integer", "description": "The current resource's sys.version (optimistic concurrency control)" }
  },
  "requestBodySchema": {
    "type": "array",
    "description": "An array of RFC 6902 JSON Patch operations (op / path / value / from). path is an RFC 6901 JSON Pointer.",
    "items": {
      "type": "object",
      "required": ["op", "path"],
      "properties": {
        "op": { "type": "string", "enum": ["add", "remove", "replace", "move", "copy", "test"], "description": "The operation kind" },
        "path": { "type": "string", "description": "The target location (JSON Pointer). e.g. /activate" },
        "value": { "description": "The value used by add/replace/test" },
        "from": { "type": "string", "description": "The source location for move/copy" }
      }
    },
    "example": [
      { "op": "replace", "path": "/activate", "value": false }
    ]
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhk01Examp",
      "type": "Webhook",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "createdAt": "2026-06-18T11:30:00.000Z",
      "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
      "updatedAt": "2026-06-18T11:32:00.000Z",
      "version": 3
    },
    "name": "Product change notification",
    "filters": [
      { "doc": "sys.contentType.sys.id", "op": "EQ", "value": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA" }
    ],
    "headers": [
      { "key": "X-Source", "value": "weegloo", "secret": false }
    ],
    "httpBasicUsername": "dailywear",
    "topics": ["Content.Create", "Content.Publish"],
    "transformation": { "method": "POST", "contentType": "application/json", "includeBody": true },
    "url": "https://api.dailywear.example/webhooks/products",
    "activate": false,
    "runAs": "HookOwner"
  }
}
```

```api-endpoint
{
  "title": "Delete a Webhook",
  "method": "DELETE",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}",
  "description": "Deletes a Webhook. On success it responds with 204 No Content and no response body.",
  "responseStatus": 204,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  }
}
```

```api-endpoint
{
  "title": "Read Webhook delivery status",
  "method": "GET",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}/status",
  "description": "Reads the Webhook's delivery-status summary. calls.total is the number of deliveries, and calls.healthy is how many of those succeeded.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhst01Exa",
      "type": "WebhookStatus",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "createdBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
      "createdAt": "2026-06-18T11:30:00.000Z",
      "updatedBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
      "updatedAt": "2026-06-18T11:35:00.000Z",
      "calls": { "total": 12, "healthy": 12 }
    }
  }
}
```

```api-endpoint
{
  "title": "List WebhookLogs",
  "method": "GET",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}/logs",
  "description": "Reads the delivery records (WebhookLog) of one Webhook, page by page. The select default is -sys.response,-sys.request, so the request sent and the response received are left out of the response. A record disappears 1 hour after a successful delivery and 3 days after a failed one.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true }
  },
  "queryParameterSchema": {
    "limit": { "type": "integer", "description": "Number of items to return per page (1-100)", "default": 15 },
    "skip": { "type": "integer", "description": "Number of items to skip", "default": 0 },
    "next": { "type": "string", "description": "Next-page cursor (the value from links.next of the previous response)" },
    "prev": { "type": "string", "description": "Previous-page cursor (the value from links.prev of the previous response)" },
    "order": { "type": "string", "description": "Sort order. Comma-separated for multi-level sorting (e.g. sys.createdAt,sys.id)" },
    "select": { "type": "string", "description": "Fields to include or exclude. Do not mix include and exclude. The default for this list read is -sys.response,-sys.request; to also receive the request and the response, specify this value yourself to override that default.", "default": "-sys.response,-sys.request" },
    "include": { "type": "integer", "description": "Level of related resources to include. 0=default, 1=related resources, 2=nested relations, 3=all", "default": 0 }
  },
  "additionalQueryParams": true,
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "responseExample": {
    "sys": { "type": "TotalPageResponse" },
    "limit": 15,
    "totalCount": 1,
    "items": [
      {
        "sys": {
          "id": "3trmXRM3RqbgSnifyg7PWhc01Exam",
          "type": "WebhookLog",
          "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
          "requestId": "3trmXRM3qWnLb7Vd1yPcYs04kKrjq",
          "statusCode": 200,
          "errors": [],
          "eventType": "Create",
          "url": "https://api.dailywear.example/webhooks/products",
          "requestAt": "2026-06-18T11:35:00.100Z",
          "responseAt": "2026-06-18T11:35:00.350Z",
          "createdBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
          "createdAt": "2026-06-18T11:35:00.350Z",
          "updatedBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
          "updatedAt": "2026-06-18T11:35:00.350Z"
        }
      }
    ],
    "links": { "self": "/v1/spaces/HnQ32YiH/webhooks/3trmXRM3RqbgSnifyg7PWhk01Examp/logs" }
  }
}
```

```api-endpoint
{
  "title": "Read one WebhookLog",
  "method": "GET",
  "path": "/spaces/{spaceId}/webhooks/{webhookId}/logs/{webhookLogId}",
  "description": "Reads one delivery record. It also returns sys.request (the request sent) and sys.response (the response received), which the list read leaves out.",
  "responseStatus": 200,
  "baseUrl": "https://cma.weegloo.com/v1",
  "pathParameterSchema": {
    "spaceId": { "type": "string", "description": "The Space's sys.id", "required": true },
    "webhookId": { "type": "string", "description": "The Webhook's sys.id", "required": true },
    "webhookLogId": { "type": "string", "description": "The WebhookLog's sys.id", "required": true }
  },
  "requestHeaderSchema": {
    "Authorization": { "type": "string", "description": "Bearer token (CMA authentication)" }
  },
  "responseExample": {
    "sys": {
      "id": "3trmXRM3RqbgSnifyg7PWhc02Exam",
      "type": "WebhookLog",
      "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
      "requestId": "3trmXRM3qWnLb7Vd1yPcYs05mNtpw",
      "statusCode": 500,
      "errors": [],
      "eventType": "Publish",
      "url": "https://api.dailywear.example/webhooks/products",
      "requestAt": "2026-06-18T14:02:11.480Z",
      "responseAt": "2026-06-18T14:02:11.912Z",
      "request": {
        "url": "https://api.dailywear.example/webhooks/products",
        "method": "POST",
        "headers": { "Content-Type": "application/json", "X-Source": "weegloo" },
        "body": "{\"sys\":{\"type\":\"Content\"}}"
      },
      "response": {
        "url": "https://api.dailywear.example/webhooks/products",
        "headers": { "Content-Type": "application/json" },
        "body": "{\"message\":\"internal error\"}",
        "statusCode": 500
      },
      "createdBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
      "createdAt": "2026-06-18T14:02:11.912Z",
      "updatedBy": { "sys": { "id": "3trmXRM3RqbgSnifyg7PWhk01Examp", "type": "Refer", "targetType": "Webhook" } },
      "updatedAt": "2026-06-18T14:02:11.912Z"
    }
  }
}
```

## Related documents {#related-documents}

- [Content](/api/reference/cma/content.md): The body data that triggers a *Webhook*.
- [Media](/api/reference/cma/media.md): The file resource that can trigger a *Webhook*.
- [Script](/api/reference/script.md): The declarative backend endpoint to run via `script`. Includes the execution and permission model.
- [SpaceRole](/api/reference/cma/space-role.md): The role setting that holds permissions such as *Script* execution (`Execute`).
