Support room-level webhooks - #1
Open
Darshak03 wants to merge 1 commit into
Open
Conversation
Webhooks are currently global: every room's events go to the URLs in the `webhook` config, and there is no way to send one room's events to its own endpoint. This adds per-room webhooks, following the shape egress already uses for per-resource webhooks. Egress attaches `repeated WebhookConfig webhooks` to the request that created the resource, keeps that request in the resource state (EgressInfo.Request), derives notify options from it with egress.GetEgressNotifyOptions, and spreads them into QueueNotify at the emit site. Rooms now do the same, with RoomInternal as the state carrier and webhook.GetRoomNotifyOptions as the derivation. Configuration comes from either CreateRoomRequest.webhooks or a named room preset (RoomConfiguration.webhooks), resolved in the allocator next to the existing Egress and Agents handling and persisted on RoomInternal. The preset copy is guarded on a non-empty list rather than mirroring the Agents line verbatim: mirroring it would materialise an empty non-nil slice that wipes previously configured webhooks when an existing room is re-created. The six webhook-emitting telemetry methods take the room's webhook list and derive the options internally, the way NotifyEgressEvent does. A registry keyed by room inside telemetryService would have been a smaller diff, but RoomIDChanged re-keys a room mid-session and a keyed lookup would then silently stop matching. BytesSignalStats passes nil: it calls ParticipantLeft with shouldSendEvent false and emits no webhook, so the signal node never needs the room's config. Room webhooks receive room, participant and track events only. Egress and ingress events are excluded by an allowlist in the protocol helper, since those resources carry their own webhook config. CreateRoom now rejects webhook URLs that are not absolute http(s) and signing keys that no configured API key matches. Without the check a typo means webhooks silently never fire, leaving only a warning log per event. This needed the key provider on RoomService; it was already a provider in the wire injector. Includes a note in config-sample.yaml, unit tests for the allocator resolution and the request validation, and an end-to-end test asserting a room's own endpoint receives its events while the global endpoint still receives everything.
Darshak03
force-pushed
the
room-webhooks
branch
from
September 7, 2026 19:22
e5e1796 to
c988022
Compare
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.
Summary
Webhooks are currently global — every room's events go to the URLs in the
webhookconfig, and there is no way to route one room's events to its own endpoint. This adds per-room webhooks for room, participant and track events.Depends on Darshak03/protocol#1, which adds the proto fields and the derivation helper.
go.modcarries areplacepointing at that branch until it lands.Following the egress pattern
Egress already supports per-resource webhooks:
repeated WebhookConfig webhookson the request proto.EgressInfo.Request), so it is available at every event.egress.GetEgressNotifyOptionsturns that into[]webhook.NotifyOption.NotifyEgressEventspreadsopts...intoQueueNotify.Rooms now do the same, with
RoomInternalas the state carrier andwebhook.GetRoomNotifyOptionsas the derivation.Configuration
Either per room on the API:
or attached to a named preset in
config.yamland selected withroom_preset:Resolved in
roomallocator.gonext to the existingEgressandAgentshandling, then persisted onRoomInternal.An empty
signing_keymeans "sign with the key from the globalwebhookconfig". Per-webhookfilter_paramsis honoured by the existingResourceURLNotifier.Event scope
Room webhooks receive
room_started,room_finished,participant_joined,participant_left,participant_connection_aborted,track_published,track_unpublished.Egress and ingress events are excluded by an allowlist in the protocol helper — those resources carry their own webhook config on the request that created them.
Notable decisions
RoomInternal, notRoom.Roomis returned byListRoomsand webhook entries hold signing keys.RoomInternalis the direct analogue ofEgressInfo.Request: server-side state that persists with the resource.Threading the list, not a registry. A
map[RoomID][]*WebhookConfiginsidetelemetryServicewould have been a much smaller diff, butRoomIDChangedre-keys a room mid-session and the keyed lookup would then silently stop matching. Threading it through the six webhook-emitting methods is explicit and cannot drift.BytesSignalStatspassesnil. It callsParticipantLeftwithshouldSendEventfalse and emits no webhook, so the signal node never needs the room's config — no plumbing across the signal path.Preset copy guarded on a non-empty list. Mirroring the
Agentsline verbatim would materialise an empty non-nil slice, which then wipes previously configured webhooks when an existing room is re-created.Validation at
CreateRoom. URLs must be absolute http(s) and signing keys must match a configured API key. Without this a typo means webhooks silently never fire, leaving only a warning log per event. This needed the key provider onRoomService; it was already a provider in the wire injector, sowire_gen.goregenerated cleanly.Tests
pkg/service/roomwebhook_test.go— request →RoomInternal, preset fallback, request wins over preset, no-webhook case, and the five validation rejections.test/webhook_test.go:TestRoomWebhooks— real server with a second endpoint on:7891; asserts the room's endpoint receivesroom_started,participant_joined,track_published,participant_left,room_finishedwhile the global endpoint still receives everything.webhook/room_notify_options_test.gocovers the event allowlist exhaustively.Verified the end-to-end test actually catches a regression: neutering
GetRoomNotifyOptionsmakes it fail withroom webhook did not receive RoomStarted.Notes on the local test run
TestRoomWebhooksand the pre-existingTestWebhooksflake at the same rate on my machine and fail on the same runs — a shared media/ICE issue (STUN lookups tostun.l.google.comfailing), not specific to this change. The full./test/suite fails ~20 tests on an unmodified checkout here for the same reason.pkg/serviceTestEgressStorefails against a persistent local Redis whose leftover rows accumulate across runs. It fails identically with this change stashed out.