Webhooks
Overview of Cariosan's event delivery system and when to use it.
Webhooks are Cariosan's extension point for anything the core server doesn't do itself — push notifications, audit logs, moderation, CRM sync, analytics. Instead of baking integrations into the server, Cariosan emits signed HTTP POSTs to URLs you register, and you do whatever you want on the other end.
The events
Each event fires exactly when the source-of-truth state changes — committed to Postgres — never on the optimistic write that hasn't reached durable storage yet. See the webhook guide for the full data shape of each.
| Event | Fires when |
|---|---|
message.created | A user posts a message (REST or WebSocket path) |
message.updated | A message is edited |
message.deleted | A message is unsent |
message.reaction.added / message.reaction.removed | A reaction changes on a message |
message.mentioned | A message @-mentions one or more members |
message.moderated | Automatic moderation flags/masks/rejects a message |
channel.created | A new channel is created |
channel.deleted | A channel is deleted |
member.joined | A user is added to a channel |
member.left | A user is removed from a channel |
user.created | A new user is inserted (upsert on an existing external_id does not fire this) |
Why so few?
High-frequency ephemeral events — typing.*, presence.*, and message.read — are deliberately not webhook-delivered. They'd overwhelm any consumer. Subscribe to the WebSocket gateway directly if you need them.
Registering a webhook
Webhook subscriptions are workspace-scoped. The signing secret is returned once at creation; store it securely.
Reliability model
- At-least-once delivery. Consumers must be idempotent, keyed by
event_idin the payload. - Retries on 5xx / timeout / network error with exponential backoff at 30s, 2m, 10m, 1h, 6h (five attempts total).
- 4xx is terminal. The server assumes a 4xx means your endpoint has a bug and won't retry.
- Auto-disable. After 24 consecutive failures (roughly a day of every-delivery-fails), the webhook is disabled and subsequent events skip it. Re-enable with
PATCH /v1/webhooks/:id.
Watch your tail latencies
Slow webhook responses block the worker pool. If your handler can't return in under 5 seconds, queue the work asynchronously and respond 200 immediately.
De-dup by event_id
At-least-once means you'll occasionally see the same event twice (during retries or partial failures). Store the last 1000 event_ids your endpoint has processed and short-circuit on hit.
Re-enabling a disabled webhook
Once auto-disabled, re-enable with:
Tip
Pair re-enable with a deploy-time hook so a hot-fixed bug doesn't leave subscribers stuck disabled. The admin UI lists every disabled webhook and the failure that caused it.
What's in the payload
Every delivery is a JSON POST with the same envelope: a stable event_id, the event type, an ISO-8601 timestamp, the workspace_id, and a data object whose shape depends on the event type. Two HTTP headers — X-Cariosan-Signature and X-Cariosan-Timestamp — let you verify authenticity with HMAC-SHA256.
Tip
Reject any request whose X-Cariosan-Timestamp is older than 5 minutes. This blocks replay attacks even if an attacker captures a valid signature.
See the webhook guide for the full payload shape per event type, HMAC verification code in Go / Node / Python, and a replay-protection checklist.
Was this page helpful?