Skip to content

Create a webhook ​

POST/api/v2/workspaces/{slug}/webhooks/

Create a webhook and store the secret_key from the response. Plane signs deliveries with it and returns it only this once. Reads never include it, and the only way to get a new one is Regenerate a webhook secret.

Path Parameters ​

slug:requiredstring

The workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.

Body Parameters ​

url:requiredstring (uri)

The endpoint Plane posts events to. It must be a publicly reachable http or https URL, and it must be unique within the workspace.

name:optionalstring

Display name.

Maximum 255 characters. Nullable.

scopes:optionalarray of string

The events the webhook receives, such as workitem.created, workitem.comment.created, or cycle.updated. An unknown scope returns 400. Defaults to no scopes.

is_active:optionalboolean

Whether Plane delivers events to the webhook. Defaults to true.

content_type:optionalstring
  • application/json - application/json
  • application/x-www-form-urlencoded - application/x-www-form-urlencoded

One of application/json, application/x-www-form-urlencoded.

version:optionalstring

The payload version. Defaults to v2 when you leave it out.

Maximum 10 characters.

rich_filters:optionalobject

A structured filter. Plane delivers an event only if it matches. Each key names a field of the event with an optional lookup, for example {"priority__in": ["urgent", "high"]}. Combine conditions with and, or, and not. {} matches every event. See Rich filters.

A function Plane can't evaluate against an event returns 400.

pql_filters:optionalobject

A filter written in PQL, as {"stripped": "<query>"}, for example {"stripped": "priority = \"urgent\""}. Only stripped is evaluated, and an empty stripped matches every event. See PQL filters.

The request returns 400 if the value isn't an object with a string stripped, if the query has invalid syntax, or if the query uses a function that needs a signed-in user, such as currentUser().

last_used_filter:optionalstring

Which filter Plane evaluates when it delivers an event: rich_filters or pql_filters. Plane evaluates only this one. Defaults to rich_filters.

Plane doesn't validate the value. Any value other than pql_filters makes Plane evaluate rich_filters.

If you send exactly one of rich_filters and pql_filters and leave this out, Plane sets it to the filter you sent.

Response shaping ​

fields:optionalstring

Accepted but ignored. The response always has every webhook field plus secret_key, and an unknown name doesn't return 400.

Scopes ​

webhooks:write

Errors ​

StatusCodeCause
400invalid_requesturl is missing or not allowed, a scope is unknown, or a filter is invalid or can't be evaluated.
401unauthorizedMissing or invalid credentials.
402payment_requiredThe feature this endpoint belongs to isn't enabled on your plan or is switched off.
403forbiddenYour role or token scope doesn't allow this.
404not_foundNo such resource, or it's outside your tenant.
406not_acceptableThe Accept header asks for a representation the API can't produce.
409conflictA webhook with this URL already exists in the workspace.
413payload_too_largeThe request body is over the size limit.
415unsupported_media_typeThe Content-Type isn't one this endpoint accepts.
429rate_limitedThrottled. Honor the Retry-After header before retrying.
Create a webhook
bash
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/webhooks/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Urgent work items to on-call",
  "url": "https://hooks.example.com/plane/urgent",
  "scopes": ["workitem.created", "workitem.updated"],
  "pql_filters": {"stripped": "priority = \"urgent\""}
}'
Response201
json
{
  "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88",
  "name": "Urgent work items to on-call",
  "url": "https://hooks.example.com/plane/urgent",
  "is_active": true,
  "version": "v2",
  "content_type": "application/json",
  "scopes": ["workitem.created", "workitem.updated"],
  "last_used_filter": "pql_filters",
  "rich_filters": {},
  "pql_filters": {
    "stripped": "priority = \"urgent\""
  },
  "created_at": "2026-01-14T09:22:41.478363Z",
  "updated_at": "2026-01-14T09:22:41.478363Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "updated_by_id": null,
  "secret_key": "plane_wh_4f9c2e7a1b8d4c6e9a3f5b2d7e1c8a40"
}
Response400
json
{
  "type": "invalid_request",
  "code": "invalid_request",
  "detail": "One or more fields failed validation.",
  "errors": [
    {
      "field": "pql_filters",
      "code": "invalid",
      "message": "Function 'currentUser' is not supported in webhook filters"
    }
  ]
}