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

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.

{
  "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) below.
  • runAs: The user identity that script runs as. Explained in runAs below.
  • topics: An array that defines which events to subscribe to. The format is explained in topics below.
  • filters: The conditions that actually trigger the Webhook among the subscribed events. Explained in filters below.
  • transformation: A setting that changes the shape of the request going out to url (method, body, and so on). Explained in transformation below.

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" } }).

PropertyTypeDescription
idstringThe unique identifier of the resource.
typestringThe kind of resource. For a Webhook it is always "Webhook".
spaceRefer<Space>The Space this Webhook belongs to.
createdByRefer<User>The user who created it.
createdAtstring (date-time)The creation time.
updatedByRefer<User>The user who last updated it.
updatedAtstring (date-time)The last update time.
versioninteger (≥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

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.

FieldTypeRequiredDescription
namestring (1-64)The Webhook name.
urlstring (url)The external target URL to call when an event occurs. Exactly one of this and script. See url and script (exactly one) below.
scriptRefer<Script>A reference to the Script to run instead of an external call. Exactly one of this and url. See url and script (exactly one) below.
runAsWebhookRunAsThe user identity that script runs as. HookOwner (default) or EventUser. See runAs below.
activatebooleanWhether it is on. If false, nothing runs even when an event occurs.
topicsstring[]The array of events to subscribe to. See topics below.
filtersFilter[]The array of trigger conditions. If empty, every subscribed event triggers. See filters below.
headersWebhookHeader[] (0-30)The array of HTTP headers to send with the url call.
httpBasicUsernamestring (1-32)The HTTP Basic authentication username for the url call.
httpBasicPasswordstring (1-32)The HTTP Basic authentication password for the url call. It is write-only. It does not appear in the response.
transformationTransformationCustomizes the request going out to url. See 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 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

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.*).

ActionMeaning
AllAll actions.
CreateCreation.
ReadReading.
EditEditing.
SaveSaving (update). The update event is Save. It is not Update.
DeleteDeletion.
PublishPublishing.
UnpublishUnpublishing.
ArchiveArchiving.
UnarchiveUnarchiving.

filters

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

{ "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 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.

KeyTypeDescription
methodstringThe HTTP method. One of GET, POST, PUT, DELETE, PATCH.
contentTypestringThe Content-Type of the request body. The body is serialized in this format (below).
bodyobjectAn object that composes the body to send using JSON Pointer templates.
includeBodybooleanWhether to also send the body of the triggering resource.

What format the body goes out in

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 contentTypeActual outgoing Content-TypeOutgoing body
(none)application/jsonJSON
application/jsonThe declared value as-isJSON
application/x-www-form-urlencodedThe declared value as-isproduct[sku]=TUMBLER-500&product[price]=24000
text/plainapplication/jsonJSON
Anything else (text/xml and so on)The declared value as-isJSON

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.

BodyExpanded 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.

"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)

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, 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).

"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.

runAs

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.

ValueExecution identity
HookOwnerThe user who created the Webhook (sys.createdBy). The default.
EventUserThe 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

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.

{
  "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.

PropertyTypeDescription
idstringThe unique identifier of the record.
typestringAlways "WebhookLog".
spaceRefer<Space>The Space this record belongs to.
requestIdstringThe tracing identifier of this delivery attempt.
statusCodeintegerThe HTTP status code of the response received.
errorsstring[]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.
eventTypestringThe 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.
urlstringThe delivery target URL.
requestAtstring (date-time)The time the request was sent.
responseAtstring (date-time)The time the response was received.
requestobjectThe request that was sent. Its inner structure is below. It is left out of the list read.
responseobjectThe response that was received. Its inner structure is below. It is left out of the list read.
createdByRefer<Webhook>The Webhook that left this record.
createdAtstring (date-time)The record creation time.
updatedByRefer<Webhook>Same as createdBy.
updatedAtstring (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.

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

These are the codes you meet when working with a Webhook. For codes that are common to every resource, see common errors.

CodeCondition
WGL400042On create (POST) or full update (PUT), both url and script were specified, or both were left empty.
WGL422061On partial update (PATCH), both url and script were specified, or both were left empty.
WGL422050url points to a blocked target such as a private network or loopback.

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.

  • Content: The body data that triggers a Webhook.
  • Media: The file resource that can trigger a Webhook.
  • Script: The declarative backend endpoint to run via script. Includes the execution and permission model.
  • SpaceRole: The role setting that holds permissions such as Script execution (Execute).