User channel

topic = "user:<user_id>"

<user_id> is the poolse id of the signed-in user (from GET /v1/me), not your external_id. A user can only join their own topic. Any other id is refused with forbidden.

Keep this channel joined for as long as the user is signed in. Unlike a conversation channel, it does not depend on which screen is open. poolse.realtime.user(id) returns it, and useConversations() joins it for you.

Client → server

Nothing. Every event a client sends here gets an error reply:

{ "status": "error", "response": { "reason": "unknown_event" } }

Server → client

EventWhen
conversation:createdThe user created a conversation or was added to one.
conversation:updatedA message was sent or deleted, or the latest message edited, in one of the user's conversations.
mention:newA message mentioned the user.
call:ring_endedThis user's ring for a call is over.
call:finishedA call this user was rung for, or was in, has ended.

conversation:created

The user is in a new conversation: they created it, or someone added them. Add it to the inbox.

{
  "id": "6f1c…",
  "tenant_id": "6578…",
  "type": "group",
  "name": "Engineering",
  "avatar_url": null,
  "created_by_user_id": "2b7e…",
  "member_limit": null,
  "custom_data": {},
  "settings": {},
  "last_message_at": null,
  "last_message_preview": null,
  "last_sequence": 0,
  "unread_count": null,
  "inserted_at": "2026-10-27T09:00:00.000000Z",
  "updated_at": "2026-10-27T09:00:00.000000Z",
  "member_external_ids": ["acme-alice", "acme-bob"]
}

The conversation, as GET /v1/conversations/:id returns it. unread_count is null here; treat it as 0.

When a conversation is created (POST /v1/conversations), every member, the creator included, gets this event once the conversation has been saved. Each copy lists every member in member_external_ids. If creating fails, nobody gets it. A member added later (POST /v1/conversations/:id/members) gets it at that point, listing the members at that moment.

conversation:updated

Sent to every member when, in a conversation they belong to, a message is sent, a message is deleted, or the latest message is edited (its last_message_preview changes). Use it to update the inbox row and its unread badge.

{
  "conversation": {
    "id": "6f1c…",
    "type": "group",
    "name": "Engineering",
    "last_message_at": "2026-10-27T09:14:03.112233Z",
    "last_message_preview": "ship it",
    "last_sequence": 185,
    "unread_count": 3,
    "member_external_ids": ["acme-alice", "acme-bob", "acme-carol"],
    "…": "every other conversation field"
  },
  "by_user_id": "2b7e…"
}
FieldTypeMeaning
conversationobjectThe full conversation, with unread_count computed for this recipient.
conversation.unread_countintegerMessages in the conversation that are not deleted and come after this user's read cursor. The sender's own message never counts: sending moves the sender's cursor.
conversation.last_message_previewstring | nullUp to 80 characters of the newest message, Attachment / N attachments for one without text, or the call-record JSON for a call-log message.
by_user_iduuid | nullWho caused the update: the sender, the person who deleted a message, or the editor. null for a call-log message.

Not sent when the conversation is renamed or its custom_data changes. Not sent when someone marks the conversation read either. That comes on the conversation channel as member:read, and the reader's next GET /v1/conversations has the new count.

mention:new

A message mentioned this user.

{ "message_id": "8d0e…", "conversation_id": "6f1c…", "sender_id": "2b7e…" }

Poolse ids only. Fetch the message from the conversation if you need its text. No hook or component in the SDK uses this event. Listen with poolse.realtime.user(id).onMention(cb).

call:ring_ended

This user's phone should stop ringing for a call, because it was answered, declined, cancelled, timed out or finished.

{
  "cv": 1,
  "call_id": "4a69…",
  "conversation_id": "6f1c…",
  "reason": "answered_elsewhere",
  "device_id": "d3a0…"
}
FieldTypeMeaning
reasonstringanswered_elsewhere (this user picked up on one device), declined (this user declined on one device), cancelled (the caller hung up first), timeout (it rang out), or finished (the call ended while it rang).
device_iduuidOnly present with answered_elsewhere, and only when the call was answered from a ring push: the registered device that answered. That device should ignore the event.

If you use @poolse/native, pass this on to the native core: onRingEnded(call_id, reason). See Ringing phones end to end.

call:finished

A call this user was rung for or took part in has ended. That includes the caller, everyone rung (also people added later) and everyone who joined.

{
  "cv": 1,
  "call_id": "4a69…",
  "conversation_id": "6f1c…",
  "ended_at": "2026-10-27T09:34:25.000Z",
  "duration_ms": 265000,
  "joined_user_ids": ["2b7e…", "9c41…"],
  "reason": "finished"
}

The same fields as call:finished on the conversation channel, plus conversation_id. It is sent here as well because a phone often leaves the conversation's channel when the user navigates away during a call. With @poolse/native, pass it on: onCallFinished(call_id, reason).

Not on this channel

  • Presence: poolse has no app-wide online status. Presence is per conversation (presence_state / presence_diff on the conversation channel).
  • Being removed from a conversation: there is no event here. The conversation's own channel closes instead (phx_close).
  • An incoming call: rings go out by push. A foreground app sees the call through call:started on the conversation channel.
User channel · Realtime · poolse docs