Skip to main content

Webhooks

A webhook tells Cotalker to POST an event to a URL you control whenever something happens — a task changes, a form is answered, a user logs out. It is how you connect a Cotalker process to a system that lives outside it.

cotctl manages webhooks as YAML, like every other resource, so the integrations a customer depends on live in your repository rather than in somebody's browser tab.

The shape of a webhook

kind: Webhook
code: notify_task_changes
name: "Notify task changes"
url: "https://example.com/cotalker/hook"
trigger: create-edit-delete-task

That is a complete, valid file. Everything else is optional.

FieldRequiredNotes
kindYesAlways Webhook
codeYesThe upsert key. Lowercase, digits and underscores (^[a-z]+([_a-z0-9]+)*$)
nameYesDisplay name
urlYesWhere the event is POSTed. cotctl checks it is a real URL before applying
triggerYesWhich event fires it — one of the six below
contextNoOptional scoping. Read the section below before using it
descriptionNoA free-text note
isActiveNoDefaults to true. Setting false stops it firing — there is no delete

trigger — the six events

Each value fires when the named entity is created, edited or deleted:

triggerFires on
create-edit-delete-taskA Task
create-edit-delete-surveyA Survey (form)
create-edit-delete-propertyA Property
create-edit-delete-userA User
create-edit-delete-noteA Note
logout-userA user logging out

This is a closed set as far as cotctl is concerned: a typo is caught before it is persisted. The backend is more permissive and would accept an arbitrary value, so the guard here is worth having.

The payload your endpoint receives is the audited event object. To see what it actually looks like, send yourself one and read it:

cotctl webhooks test notify_task_changes -c acme
cotctl webhooks logs notify_task_changes -c acme

context — scoping a webhook, and the trap in it

By default a webhook fires for every event of its trigger in the company. context narrows that to one survey, group or task group:

context:
survey: "6a000000000000000000aaaa"
group: "6a000000000000000000cccc"
taskGroup: "6a000000000000000000bbbb"

All three are ObjectIds, not codes — cotctl does not resolve them for you yet, so take the _id of the survey, group or task group you mean.

context only does something on create-edit-delete-task, and cotctl refuses it elsewhere. On any other trigger a populated context used to apply cleanly and then deliver nothing at all — matching events kept happening, and every delivery was silently dropped. The result was indistinguishable from an unreachable endpoint unless you happened to suspect context.

cotctl now rejects that combination before any request is sent. This is a cotctl-side guard, not a backend one: the same payload applied through the API or the admin UI still reproduces the original trap.

Clearing it: three intents, not two

context has the same three-way convention as requiredSurvey in workflows:

YAMLResult on apply
context omittedPreserved. Whatever is on the server stays
context: nullCleared — the recommended way
context: {}Cleared too — the older form, still supported
A populated contextReplaces whatever was there

context: null is new in 0.12.0. Before it, context: {} was the only way to empty the field, and it was blessed as such. Both work; null says what you mean.

Clearing is accepted on every trigger, including the ones that reject a populated context — removing a stale value is exactly what that rule wants you to do, so it is never blocked.

Why omitting does not clear. A webhook's context can be set outside cotctl, and most YAML files never mention the field. If omission meant "delete", every apply of a file that simply does not talk about context would silently drop the scoping.

A clear is always announced

An apply that would remove a populated context warns before it writes, on --dry-run and on the real run alike:

  ⚠ warn: notify_task_changes: context: { survey } → cleared — the webhook stops
being scoped and will fire for EVERY event of its trigger in the company.

Every path announces it, including -y and apply --dir, where there is no confirmation prompt, and including under -q. That flag mutes the per-webhook progress lines; it does not and cannot silence a destructive finding. So a scripted cotctl webhooks apply -y -q still reports the clear on stderr before it writes.

Preview it without writing anything:

cotctl webhooks apply -f webhook.yaml -c acme --dry-run

Working with webhooks

# List — active only by default
cotctl webhooks list -c acme
cotctl webhooks list -c acme --all # include deactivated ones

# Read one
cotctl webhooks get notify_task_changes -c acme
cotctl webhooks export notify_task_changes -c acme -o webhook.yaml

# Apply
cotctl webhooks apply -f webhook.yaml -c acme --dry-run
cotctl webhooks apply -f webhook.yaml -c acme

# Did it fire, and what did my endpoint answer?
cotctl webhooks logs notify_task_changes -c acme
cotctl webhooks test notify_task_changes -c acme

apply handles multi-document files and takes --dry-run, -y/--yes and -q/--quiet. test can also probe a destination that does not exist yet, with --url and --trigger instead of a code — useful when you are building the receiving end.

There is no delete. Set isActive: false to retire a webhook. It stops firing and stays listable with --all.

Apply order

Webhooks are applied last in a directory apply, after every resource their context might point at already exists. cotctl apply --dir enforces the order.

cotctl validate does not know the Webhook kind. It recognises seven of the twelve kinds apply handles, and this is one of the five it does not — so a Webhook file in a directory makes validate --dir fail with unrecognized kind, at the step before the one that would have applied it. Keep webhooks in their own directory, or let the validation step tolerate them. See validate.

See also

  • apply — the twelve kinds, and what --dry-run does and does not show
  • Workflows — where the tasks a create-edit-delete-task webhook reports on come from
  • CI/CD — applying webhooks unattended, and what -q does there