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:
| Key | Environment variable | Default | Purpose |
|---|---|---|---|
events_enabled | LUMIO__SEVENTV__EVENTS_ENABLED | true | Turn the listener off for operations; no 7TV events or event-driven cache refresh while off. |
ws_url | LUMIO__SEVENTV__WS_URL | wss://events.7tv.io/v3 | Outbound EventAPI URL. |
max_subscriptions_per_connection | LUMIO__SEVENTV__MAX_SUBSCRIPTIONS_PER_CONNECTION | 450 | Maximum subscriptions before opening another connection. |
roster_refresh_secs | LUMIO__SEVENTV__ROSTER_REFRESH_SECS | 600 | Interval 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
| File | Role |
|---|---|
apps/api/src/workers/seventv_events.rs | Lease, roster, connections, dispatch and cache invalidation |
apps/api/src/services/seventv_events.rs | Frame parser, subscriptions, event rows and metrics |
crates/lo-events/src/queries.rs | Cross-platform 7TV predicate for server-merged chat history |