Skip to main content

7TV Event Listener

The API listens to 7TV's public EventAPI at wss://events.7tv.io/v3 without an API key. It watches the active 7TV emote sets of connected Twitch, Kick, and YouTube channels. Additions, removals, renames, and active-set switches become ordinary stream events. This is an outbound listener; it does not add an incoming endpoint or permission.

Roster and subscriptions​

apps/api/src/workers/seventv_events.rs elects one listening replica through the Redis lease lumio:seventv:events_listener:leader. The leader reads channel_connections and resolves each channel's 7TV user and active set with the same 7TV v4 lookup used by the emote fetcher. The result is cached under lumio:seventv:link:{platform}:{channel}: one hour for a linked user, six hours when there is no 7TV user. A failed lookup is retried after ten minutes. A roster refresh runs every roster_refresh_secs; connection changes on the leader's replica also wake it. Each refresh caps lookups at 200, with eight in flight at once.

The listener subscribes to emote_set.update for each active set and user.update for each channel's 7TV user. Every subscription condition contains only object_id. A shared set needs one subscription, even when several channels use it. The listener does not subscribe with ctx: "channel": that condition would also deliver viewers' personal set changes. A dispatch whose set or user is absent from the roster produces no event. The kind: 3 field on an emote_set.update dispatch means an emote-set object, not a personal set; the roster is the privacy boundary.

Each connection holds at most max_subscriptions_per_connection subscriptions; the listener opens another connection when needed. A silent connection is considered dead after three server heartbeat intervals. Reconnects use exponential backoff with jitter and try RESUME; if resume fails, the listener subscribes again. An end-of-stream frame starts a fresh session.

Each connection hands dispatches to the leader's session through a bounded queue so that a slow database write never stalls the socket. When that queue is full, the dispatch is lost: it is logged at warn and counted as seventv_events_dispatch_total{outcome="dropped"}, apart from the normal ignored skips.

Storage, relay, and freshness​

The listener writes one platform_event_logs row per affected account and broadcasts the stored row through lumio:events:{account} to the existing events:{account} WebSocket channel. external_id uses the dispatch timestamp rounded to whole seconds, so replayed frames and a lease handover do not duplicate a change. A dispatch without a timestamp uses the second it was received instead, so a later re-add or re-remove of the same emote is never mistaken for a duplicate. The account's existing event retention sweep applies. 7TV does not provide a changes-since query, so a full outage leaves a gap in history.

After a change, the listener deletes the affected channels' versioned lumio:emotes:v4:{platform}:{channel} hot-cache keys. For additions and renames it also upserts the emote into chat_emotes and invalidates the catalog's Redis map. Removal clears the hot cache, but the catalog is upsert-only: a removed shortcode can remain in the chat-ingest map until that map expires. Browser chats receiving a matching live 7TV event reload their emote picker and autocomplete, debounced by two seconds. The normal six-hour hot-cache TTL remains a fallback when event delivery is unavailable.

Configuration​

The [seventv] block in apps/api/config/default.toml has these settings:

KeyEnvironment variableDefaultPurpose
events_enabledLUMIO__SEVENTV__EVENTS_ENABLEDtrueTurn the listener off for operations; no 7TV events or event-driven cache refresh while off.
ws_urlLUMIO__SEVENTV__WS_URLwss://events.7tv.io/v3Outbound EventAPI URL.
max_subscriptions_per_connectionLUMIO__SEVENTV__MAX_SUBSCRIPTIONS_PER_CONNECTION450Maximum subscriptions before opening another connection.
roster_refresh_secsLUMIO__SEVENTV__ROSTER_REFRESH_SECS600Interval for re-reading connected channels and their 7TV identities.

See Installation for the environment override pattern and Operational Metrics for the listener's four seventv_events_* series.

Code​

FileRole
apps/api/src/workers/seventv_events.rsLease, roster, connections, dispatch and cache invalidation
apps/api/src/services/seventv_events.rsFrame parser, subscriptions, event rows and metrics
crates/lo-events/src/queries.rsCross-platform 7TV predicate for server-merged chat history