Skip to content

Support room-level webhooks - #1

Open
Darshak03 wants to merge 1 commit into
masterfrom
room-webhooks
Open

Support room-level webhooks#1
Darshak03 wants to merge 1 commit into
masterfrom
room-webhooks

Conversation

@Darshak03

Copy link
Copy Markdown
Owner

Summary

Webhooks are currently global — every room's events go to the URLs in the webhook config, 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.mod carries a replace pointing at that branch until it lands.

Following the egress pattern

Egress already supports per-resource webhooks:

  1. repeated WebhookConfig webhooks on the request proto.
  2. The request lives in the resource state (EgressInfo.Request), so it is available at every event.
  3. egress.GetEgressNotifyOptions turns that into []webhook.NotifyOption.
  4. NotifyEgressEvent spreads opts... into QueueNotify.

Rooms now do the same, with RoomInternal as the state carrier and webhook.GetRoomNotifyOptions as the derivation.

Configuration

Either per room on the API:

// CreateRoom
{
  "name": "support-call",
  "webhooks": [
    { "url": "https://example.com/hook", "signing_key": "APIxxx" }
  ]
}

or attached to a named preset in config.yaml and selected with room_preset:

room:
  room_configurations:
    support-call:
      empty_timeout: 300
      webhooks:
        - url: https://example.com/hook
          filter_params:
            include_events: [participant_joined, participant_left]

Resolved in roomallocator.go next to the existing Egress and Agents handling, then persisted on RoomInternal.

An empty signing_key means "sign with the key from the global webhook config". Per-webhook filter_params is honoured by the existing ResourceURLNotifier.

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, not Room. Room is returned by ListRooms and webhook entries hold signing keys. RoomInternal is the direct analogue of EgressInfo.Request: server-side state that persists with the resource.

Threading the list, not a registry. A map[RoomID][]*WebhookConfig inside telemetryService would have been a much smaller diff, but RoomIDChanged re-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.

BytesSignalStats passes nil. It calls ParticipantLeft with shouldSendEvent false 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 Agents line 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 on RoomService; it was already a provider in the wire injector, so wire_gen.go regenerated 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 receives room_started, participant_joined, track_published, participant_left, room_finished while the global endpoint still receives everything.
  • Upstream webhook/room_notify_options_test.go covers the event allowlist exhaustively.

Verified the end-to-end test actually catches a regression: neutering GetRoomNotifyOptions makes it fail with room webhook did not receive RoomStarted.

Notes on the local test run

  • TestRoomWebhooks and the pre-existing TestWebhooks flake at the same rate on my machine and fail on the same runs — a shared media/ICE issue (STUN lookups to stun.l.google.com failing), not specific to this change. The full ./test/ suite fails ~20 tests on an unmodified checkout here for the same reason.
  • pkg/service TestEgressStore fails against a persistent local Redis whose leftover rows accumulate across runs. It fails identically with this change stashed out.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant