Moderating voice chat
This guide wires up a complete moderation loop: a plugin flags toxic messages, Cortex escalates repeat offenders from a warning to mutes and bans, the bot enforces mutes inside the room, and your token server keeps banned players out.
You’ll need a backend API key with the plugins, settings and sanctions scopes for
setup, and a separate key with only sanctions.read for your token server (see
Scopes).
1. Activate the moderation plugins
The Profanity Filter checks every transcribed message and annotates it (profanity, with
flagged: true when it crosses your threshold). The Auto-Sanction plugin turns those flags
into sanctions along an escalation ladder. (All catalog plugins and their settings are listed under
Plugins.)
Flags are counted per player, and only those since the player’s last auto-sanction — so every
step of the ladder needs three fresh violations. Each auto-sanction ends after its step’s duration;
nothing is permanent unless a step says so ("perm_ban:permanent").
Skip the Auto-Sanction plugin and decide in a serverless function instead — see Custom rules in a function below.
2. Turn on enforcement in the room
With sanctionPush on, the Cortex bot tells the room about sanctions and enforces voice mutes
on the receiving side: every client stops playing the muted player, and the bot stops transcribing
them.
The bot reads this setting when it joins a room, so it applies from the next session.
Only enable sanctionPush once your game clients handle the frames (step 3). With it on, the bot
stops transcribing muted players — a client that ignores the frames keeps playing the muted voice,
and then nobody moderates it.
3. Handle sanction frames in the game client
The bot sends cortex.sanction and cortex.mutes frames as ODIN room messages. Your client keeps
a set of muted peers and applies it:
cortex.mutes(sent to every peer that joins) replaces the set.cortex.sanctionwithmute: trueadds itspeerIds;mute: false(revoked or expired) removes them.- Every muted peer gets channel mask
0(ODIN 2.x); everyone else is restored. - A ban notice (
temp_ban/perm_ban, sent only to the banned player) means: leave the room.
In a web or Node client, use the decoder from @4players/odin-cortex (1.10.0 or newer):
How to recognise the bot peer and the full frame format are covered in Room Push.
4. Keep banned players out
ODIN checks a token only when a peer joins, so ban checks belong in the place that hands out room
tokens — your token server. Give it a key with sanctions.read only: it can check sanctions,
but never issue or lift one.
getActive() answers the common case — no sanctions — from a cache, and reflects a revoke at
once, so it is cheap to call on every join.
externalUserIdSanctions created by the plugins carry the player’s externalUserId — the user id your game uses
in ODIN. Key your own sanctions on it too: a returning player is still muted or banned on their
next join, in any session of the project. See
Users & Participants.
A ban issued while the player is in the room reaches their client as a notice (step 3), and the client leaves. A server-side kick isn’t available yet — it will follow once ODIN Voice offers a server-side room API.
5. Appeals & audit
Moderators review and lift sanctions in the dashboard, or from your backend. Lifting a sanction keeps the full history — nothing is deleted — and the bot unmutes the player in the room right away:
createdBy, createdAt, revokedBy and revokedAt are all retained for accountability. A revoked
auto-sanction no longer counts towards the player’s escalation level.
Custom rules in a function
If the ladder doesn’t fit, react to the annotations yourself in a
serverless function. ctx.cortex is pre-authorized, so
the function needs no API key: