Skip to main content

Events Reference

Register listeners with protocol.on(eventType, listener). See Event Listeners for the full listener API.
Register listeners synchronously after construction, because the SDK subscribes to native events in its own constructor. Delivery is at-least-once, so handlers must be idempotent.
Field naming. Every event payload field is snake_case, matching the native event JSON. The only exceptions are type and the optional seenAt, a local timestamp in milliseconds recorded when the JS bridge observed the event. Types. ProtocolEvent is the discriminated union of every event; EventType is ProtocolEvent['type'].

Message Events

message_sent

message_received

encrypted is false only for plaintext accepted under the requireEncryption: false opt-out.

message_delivered

Terminal success: the recipient ACKed.

message_failed

Terminal failure. This is the only terminal failure signal for a message.

message_retrying

Non-terminal: a retry has been scheduled after a failed attempt.

message_deferred

The message was queued because no transport was available. Not a failure.
reason is a stable classification from a fixed local vocabulary, not a rendered error string.

message_undeliverable

A transport reported the recipient unreachable for an in-flight message.
This is not terminal and it repeats. The message stays in the outbox, and the event fires again for the same message_id while the recipient is offline, on an escalating probe (15s doubling to a 600s cap). A terminal message_failed arrives only at the absolute outbox cap, about 28 days on default settings. Do not settle a message in your UI on this event.

message_relayed

This node forwarded a message on behalf of another peer.

message_decryption_failed

Advisory, not terminal. It fires once per failed attempt, not once per message, because a receiver that cannot decrypt a frame withholds the delivery ACK so the sender’s resend can deliver. Settle on message_failed or file_receive_failed instead.PENDING_QUEUE_DROPPED is the exception worth handling: the message was dropped from the pending-decryption queue on overflow or TTL, and it was ACKed on receipt, so the sender will not retransmit.

Identity Events

identity_ready

Fires once per successful startup, before any message can be sent.
The address is derived from the identity key in this profile’s storage and is stable across restarts for the same profile. Also readable with localAddress(). See Identity & Addressing.

Connection Request Events

connection_request_received

sender_name and initial_message arrived in plaintext, because connection requests precede the MLS session.

connection_request_undeliverable

A status signal, not proof of permanent failure. The original request may still be delivered by the retry machinery, so a user-initiated resend can duplicate on the recipient’s side.

connection_accepted

connection_rejected

connection_request_cancelled

Network Events

neighbor_discovered

peer_id is the value that peer derived from its own identity key. Use it directly as recipient, regardless of which transport discovered the peer.

neighbor_lost

transport_switched

network_metrics

Internet Transport Events

internet_status_changed

authenticated: true is the positive gate for sendRawServerCommand. The connected: true, authenticated: false window is where the socket is up but the relay has not yet accepted the auth token. Emitted only on actual transitions, so query the current value with isInternetReady().

internet_session_superseded

The relay displaced this device’s connection: a newer registration for the same identity took the relay slot.
The SDK does not auto-reconnect from this state. Recovery is a deliberate enableTransport('internet', { serverAddress }).Treat it as state, not an edge: it can repeat, and it is one of the two one-shot events replayed to a late listener. Handlers must be idempotent. The pull-side counterpart is isInternetSuperseded().

internet_server_message

A raw relay frame your app needs outside or in addition to the SDK’s own processing.
Carries invite-link lifecycle responses (GroupInviteLinkCreated, GroupJoinedViaInvite, GroupInviteJoinPending), GroupRoleChanged, GroupDeleted, RateLimited, and any future relay message types. GroupError, GroupInfo, and UserGroups are dual-emitted here in addition to their typed events. Apply state from one channel, not both, as there is no cross-channel ordering guarantee.
Raw frames can contain profile data, invite tokens, and key packages. Do not log them indiscriminately.

mesh_stopped_by_user

Android only. The user stopped the mesh from the foreground-service notification’s Stop action rather than through stop().
Everything is already torn down, so this is a notification, not a request to act. It is the second one-shot event; a held copy is dropped by start().

Security Events

secure_session_established

secure_session_failed

As of v0.21.0 this can fire while the session with that peer stays live. An app that tears down session state on this event alone must stop doing so.

security_warning

Welcome delivery events

welcome_send_succeeded followed by welcome_send_failed for the same Welcome is a legal sequence over the internet transport: the bridge confirms on socket write, but the relay stores nothing for offline recipients, so its later delivery error corrects the earlier success. Treat these as state, not a terminal verdict. A PEER_UNREACHABLE failure always carries next_retry_at.

convergence_diag

A receiver-side breadcrumb for the Welcome receive/adopt/confirm path. Carries no protocol effect.

Group Events

group_created

group_message_received

group_message_sent

group_message_partial_failure

group_message_delivery_report

The relay’s settled per-recipient report for a relay-broadcast group message, emitted after the SDK has already acted on it.
This is observability, not a failure signal. missed_reissued members were already re-sent. It fires once per broadcast whose report arrived, seconds after group_message_sent, so correlate by message_id, never by order.

group_member_added / group_member_removed

An absent authorized means “not evaluated”, not “authorized.” It is omitted for your own join from a Welcome, for relay reconciliation frames, and by older cores.authorized: false means the change did happen (MLS accepted the commit) but the committer was not a known admin. The judgment is made against this device’s local, best-effort-replicated role state, which can lag and which members can disagree about. Do not act on it automatically.

group_unauthorized_membership_change

Read enforced first.
  • enforced: false (default config): the change has been applied, and roster events accompany it. An admin can undo it with meshRemoveFromGroup / meshInviteToGroup.
  • enforced: true (with group.enforceAdminCommits on): the commit was refused before merging. Nothing changed locally, but this device is now an epoch behind and must be re-invited. This is a partition alarm.
It can false-positive when the local role replica lags. Known limitation: the member removed by an unauthorized Remove does not receive this event.

group_info

Dual-emitted as internet_server_message. Use the raw event for app-owned fields such as descriptions, avatars, and pending join requests, but do not apply state from both.

user_groups

group_role_changed

group_renamed

Emitted for renames performed or received via meshRenameGroup. Renames observed only as relay-native frames surface through internet_server_message instead.

group_relay_sync_changed

synced: true fires only on the relay’s positive registration ack. Await it with ensureGroupRegistered().

group_rich_extras_dropped

Rich media metadata was dropped from an outbound group message because the group is not fully rich-capable. The text was still sent; members receive it without the attachment. unknown_members is empty when the local richPayloadEnabled kill switch caused the drop instead. Use meshGroupRichReadiness() to warn before sending.

group_epoch_fork_detected / group_epoch_fork_resolved

Concurrent MLS commits caused members to diverge. The deterministic leader attempts automatic resolution.

group_error

reason is a fixed code minted locally, not the relay’s wording. Do not parse it as prose or show it to users as-is. Apps needing the exact wording (invite-link flows correlating by request ID) should read the raw GroupError frame from internet_server_message.

File & Media Events

file_progress

file_received

file_receive_failed

Terminal, at most once per transfer. No file_received will follow for this file_id; the sender must re-send under a fresh one.

media_sent

All chunks were ACK-delivered.

media_send_failed

The outbound transfer aborted before all chunks were delivered. No media_sent will follow.

media_resend_required

An outbound media transfer was in flight when the previous process died.
The SDK persists only the transfer descriptor, never chunk bytes, so your app must re-supply the file bytes via sendMedia with this file_id. They are checksum-validated against the original transfer.

Presence & Typing Events

presence_updated

One unified stream for both sources. internet is relay-observed presence: an authoritative CheckPresence answer or relay-derived reachability. peer is a peer-sent self-report. Apps rendering relay-style presence UI should filter on internet.
Emission is 1:1 with the underlying signal. The SDK never dedupes unchanged statuses, so every relay answer re-emits this event even when nothing changed.
last_seen_ms is present only for relay-sourced presence, and only when the relay knows it.

typing_indicator_received

read_receipt_received

Relay Role Events

relay_promoted

relay_demoted

relay_demoted_battery

Blocking Events

Emitted for local UI notification only.

Service Discovery Events

service_discovered

service_request_received

service_response_received

DORS Events

phase: 'TRIGGERED' is a recommendation; 'APPLIED' means the fallback succeeded.

Resource Pressure Events

ack_evicted

fragment_assembly_evicted

Diagnostic Events

diagnostic

Next: Types & Internals

Type definitions, DORS scoring, mesh architecture, and troubleshooting.