Repository navigation
feat(notifications): per-namespace notifications to Matterbridge and webhooks - #16
Merged
Merged
Conversation
Owners pick targets (Matterbridge API, signed Standard Webhooks JSON, Slack-style and Discord webhooks) and events (runs finished/failed/cancelled, batch runs finished, things added). A notifier in every background process reads what changed, claims each event once, queues it per target and retries with backoff. Targets reach public addresses unless an admin allows a private network. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqBe9ycLaqFLZvCqL5wprK
The first look marks a floor; rows in that same second could predate it (CI saw the test's seeded recordings sent), so the floor is exclusive. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqBe9ycLaqFLZvCqL5wprK
…ap a send - Each source is read from a (time, id) cursor a page at a time; only a caught-up look reads the last minute again, so more than a page of events no longer freezes the notifier. - Claiming an event and queueing its deliveries is one transaction; only an existing claim counts as someone else's, and the cache learns after the fact. - A send gets 30 s in all (a trickling answer is cut off), well inside the 5-minute reclaim; a round stops sending after a minute to look for news. - A batch run's counts are per namespace. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqBe9ycLaqFLZvCqL5wprK
Regenerated openapi.json and the API client from the merged backend. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNbRrpHTCqLewRKdkmd45X
adeelahmad
marked this pull request as ready for review
October 2, 2026 13:27
2 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Description
Before: Lens told nobody when a run finished or failed; you had to look at Activity.
After: a namespace's owners add notification targets on the namespace's page (Notifications). Each target picks its events (a run finished, failed or was cancelled; a batch run finished; something was added), can be sent a test, and lists what it was sent.
Targets:
POST /api/messagewith gateway, username and optional bearer token, reaching Slack, Discord, Matrix, Telegram, IRC and the rest.webhook-id,webhook-timestamp,webhook-signature), with a secret shown once that can be rotated.{"text"}) and Discord ({"content"}) incoming webhooks.How: nothing in the existing code calls into notifications. A notifier thread (
app/domain/notify.py) runs wherever background work runs (inline workers in the API process,lens worker). It reads runs that ended (job.finished_at) and recordings added (recording.created_at) since its last look, with a 60 s lookback. Each event is claimed once across processes through aCREATE notify_event:<hash>that only one process can win. Events are then queued per target innotify_delivery, claimed per send, and retried with backoff: 30 s, then ×4 each time, capped at 6 h, up tonotifications.max_attempts. A 4xx response other than 408, 425 or 429 is not retried. A batch run's jobs go out as onebatch.finishedmessage, and more than 5 additions in one look go out as one message.Security: target URLs, secrets and tokens are sealed with the server key, and the API returns only scheme://host. Targets can reach public addresses only, unless an admin lists private networks in
notifications.networks. The notifier resolves the host itself, checks every address, connects to the address it checked (with SNI and certificate checks against the hostname), and doesn't follow redirects.Admin settings (Settings → Notifications):
enabled,networks,app_url(for links; defaults toFRONTEND_URL),poll_seconds,max_attempts.API:
GET/POST /api/v1/namespaces/{name}/notifications,PATCH/DELETE …/{tid},POST …/{tid}/test,POST …/{tid}/secret,GET …/{tid}/deliveries. These are for owners and are audited.Motivation and Context
Requested in the project thread: Matterbridge and webhook support for notifications.
Screenshots (if appropriate):
Checked in a local run of the app: the namespace's panel listing targets and test results, the New target dialog, the Sent log and the admin settings section all render. A test to a fake Matterbridge answered 200.
Steps to reproduce (if appropriate):
172.16.0.0/12for Docker).http://matterbridge:4242and gatewaylens, then click Test.Types of changes
Checklist:
docs/notifications.md,docs/api.md,docs/configuration.md, CHANGELOG).Tests:
tests/api/test_notifications.pyhas 6 tests, all run against local HTTP servers: signing checked against the spec, the private-address refusal, sending once across two "processes", retry and refusal, and grouping. ruff, mypy,tsc, eslint and prettier are clean. In the full backend suite, 1 test fails:test_conversion.py::test_office_and_text_files_become_documents. LibreOffice can't read the .docx in this container, and the test doesn't touch this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01PqBe9ycLaqFLZvCqL5wprK
Generated by Claude Code