feat(integration): outgoing webhooks / event subscriptions (LC-75) #171
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc-75-outgoing-webhooks"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Outgoing webhooks / event subscriptions (LC-75). An admin registers a delivery URL + event filter + scope; matching events POST a signed JSON body to the URL with retries, backoff, and auto-disable.
Design
0042):outgoing_webhooks(scope, events, url, signing_secret, failure counters,disabled_at) +outgoing_webhook_deliveries(per-attempt bookkeeping, pruned to 50/webhook).signing_secretis plaintext - the server computes the per-delivery HMAC and the receiver holds the same value to verify; shown once, rotatable, never logged.crate::outgoing::enqueue(best-effort, non-blocking) resolves the room's enclave, matches enabled webhooks by scope + event, inserts a delivery per match. Wired atmessage.posted(both finalizers),message.edited,message.deleted,reaction.added.spawn_outgoing_webhook_dispatcher, 10s tick, no AppState field - mirrors the reminders/scheduled dispatchers) POSTs withX-LetsChat-Event/X-LetsChat-Timestamp/X-LetsChat-Signature: sha256=<hmac>over the raw body. 2xx -> delivered + reset; failure -> backoff (1s,4s,16s,1m,5m,30m, 6 attempts) then mark failed + bumpconsecutive_failures, auto-disabling at 5.{"version":"1","event":"...","room_id":N,"data":{...}}./admin/outgoing-webhookslist/create (reveal secret once)/rotate/enable-disable/delete + per-webhook delivery history.docs/api.mddocuments it.Acceptance criteria
"version":"1").Tests
outgoing_webhooks.rs: event/scope matching, signed delivery against a local receiver (verifies HMAC), 5xx retry/backoff, auto-disable, disabled-not-matched, rotate; + asign()unit test. Migration0042appended to all hand-rolled chat lists.just check,just test-saasgreen;just testclean apart from the documentedroutes_uploadsconcurrency flake (passes isolated).🤖 Generated with Claude Code
An admin registers a delivery URL + event filter + scope (global / enclave / room). When a matching event fires, the server POSTs a signed JSON body to the URL with retries, exponential backoff, and auto-disable after repeated failures. Storage (migration 0042, chat.db): outgoing_webhooks (scope, events, url, signing_secret, failure counters, disabled_at) + outgoing_webhook_deliveries (per-attempt bookkeeping with bounded retention). signing_secret is stored plaintext because the server needs it to compute the per-delivery HMAC and the receiver holds the same value to verify; it is shown once, rotatable, and never logged. Event production: event handlers call crate::outgoing::enqueue (best-effort, never blocks the request) which resolves the room's enclave, finds enabled webhooks whose scope + event filter match, and inserts one delivery row each. Wired at message.posted (finalize_message_send + the incoming-webhook finalize), message.edited, message.deleted, and reaction.added. Delivery: a poll-based background loop (spawn_outgoing_webhook_dispatcher in main, 10s tick, no AppState field) claims due deliveries and POSTs the body with X-LetsChat-Event, X-LetsChat-Timestamp, and X-LetsChat-Signature: sha256=<hmac> over the raw body keyed by the webhook's signing_secret. 2xx -> delivered + reset failure counter; non-2xx / network error -> reschedule with backoff (1s,4s,16s,1m,5m,30m, 6 attempts) then mark failed and bump consecutive_failures, auto-disabling at 5. Old delivery rows are pruned to 50/webhook each tick. Payload schema is stable + versioned: {"version":"1","event":"...","room_id":N,"data":{...}}. Admin UI (standalone, org-admin): /admin/outgoing-webhooks lists + creates (reveals the signing secret once), rotate-secret, enable/disable (re-enable clears the failure counter), delete, and a per-webhook delivery-history page. Linked from the admin nav. docs/api.md documents the payload, headers, signature, and retry policy. Tests: outgoing_webhooks.rs covers event/scope matching, signed delivery against a local receiver (verifies the HMAC), 5xx retry/backoff, auto-disable at threshold, disabled-not-matched, and secret rotation; plus a sign() unit test. Migration 0042 appended to every hand-rolled chat migration list. just check, just test (one pre-existing routes_uploads concurrency flake, passes isolated), just test-saas all green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>