Users & Participants
Several different things could reasonably be called “a user” in Cortex, and they mean different things. Getting them straight prevents identity confusion and accidental data leakage.
The identities
| Identity | What it is | Identified by |
|---|---|---|
Dashboard user (users) | Someone who logs into the Cortex dashboard | userId (internal) + email |
Participant (participants) | A per-project identity for someone who appears in your rooms | externalUserId (your value) |
| ODIN peer | An ephemeral connection to a Voice room | peerId (assigned by Voice) + userId (your value) |
The two you’ll work with as an integrator are participants and the externalUserId.
externalUserId: the value that ties it together
When a client joins an ODIN Voice room, it passes a userId to the ODIN SDK. Cortex
persists that value as a participant’s externalUserId. It’s how Cortex maps an audio
stream to a stable identity:
A participant is unique per project by externalUserId. The same person who plays two of
your games is two participants (one per project), even with the same externalUserId
string — because identity is scoped to the project.
userId is a hint, not an identityODIN Voice does not validate the userId a client claims. Two clients can claim the same
userId. For trustworthy identity, authenticate your own users in your backend and pass a
value you control. Never treat the reported userId as proof of who someone is.
A participant is not a dashboard user
Most participants are not dashboard users — they’re just “someone with this
externalUserId who showed up in a room.” A participant only gains a link to a users row
if that same person also logs into the Cortex dashboard. So:
- Use participants to attribute transcripts, track gathering membership, apply sanctions and store per-speaker metadata.
- Use dashboard users only for “who created this resource” attribution in the dashboard.
One human, many rows
If Alice plays your game from two devices in the same session, Voice assigns two different
peerIds — but both carry the same externalUserId, so they resolve to the same
participant and her transcript is attributed correctly.
Erasing a participant
DELETE /api/projects/{projectId}/participants/{id} (or participant.delete() in the SDK)
erases the person, not the history. The participant is anonymised in place: the display
name, email, custom userData and the externalUserId are removed, and from then on the
participant reads as gone — it disappears from lists, GET returns 404, and a second
DELETE also returns 404. A participant.deleted event is emitted.
What stays is everything that isn’t only about this one person:
- Transcripts — messages are also the conversation context of everyone else in the session. They remain, attributed to a speaker that no longer has a name.
- Session attendance and gathering membership — shared history of sessions and gatherings.
- Sanctions — the moderation audit trail.
Two consequences to keep in mind:
- Erasing does not lift a ban. Sanctions are matched on the
externalUserIdrecorded with the sanction, so they stay in force. Revoke a sanction explicitly if that’s what you want. - The same person comes back as a new participant. Because the real
externalUserIdis released, the next time that user appears in the project a brand-new participant (with a newid) is created.
Anonymous participants and cross-session bans
In ODIN Rooms, users are often anonymous (a random id per device). You can still:
- Attribute transcripts to whatever
externalUserIdis present. - Ban across sessions by applying a sanction keyed on
externalUserIdrather than on a single participant row — your enforcement code can then look it up on every join, even if the participant record changes.