Skip to main content
Outbound webhooks push page activity to external systems such as CI, data warehouses, Slack bridges, and custom apps, instead of making them poll.

Events

Personal-space activity never emits webhooks. A subscription can cover all shared spaces or be filtered to one space.

Delivery contract

Deliveries are POSTs with a JSON body:
Headers: X-Coco-Event, X-Coco-Delivery, X-Coco-Webhook, and X-Coco-Signature, an HMAC-SHA256 of the raw body with the webhook’s signing secret, formatted sha256=<hex>.

Verifying a signature

Non-2xx responses and network failures retry with backoff (3 retries by default). Every attempt’s outcome lands in the delivery log (GET /admin/webhooks/:id/deliveries, or the Deliveries panel in the admin UI).

Management (org admin)

  • GET/POST /admin/webhooks lists and creates subscriptions ({url, events, spaceSlug?, description?}; the signing secret is returned once on create)
  • PATCH /admin/webhooks/:id updates the URL, events, space filter, or enabled state
  • POST /admin/webhooks/:id/rotate-secret issues a new secret, returned once
  • POST /admin/webhooks/:id/test sends a webhook.test delivery and reports the outcome
  • DELETE /admin/webhooks/:id removes a subscription

Security posture

Webhook signing secrets are always stored encrypted. Webhooks require COCO_SETTINGS_ENCRYPTION_KEY on the API runtime (create/rotate return a clear 503 without it).
  • Endpoints must be https; plain http requires the deployment to opt in with COCO_WEBHOOKS_ALLOW_HTTP=1 (payloads then transit unencrypted).
  • SSRF guard: private, loopback, link-local, CGNAT, and reserved addresses (IPv4 and IPv6, including v4-mapped forms) are rejected: literals at create time, and again at delivery time after DNS resolution, so a hostname pointing at an internal address is blocked too (recorded in the delivery log, no retries). A small time-of-check/time-of-use window inherent to DNS remains; deployments with strict requirements should add egress filtering. COCO_WEBHOOKS_ALLOW_PRIVATE_URLS=1 disables the guard for intentionally internal deployments.