Events and listeners
Every listener, callback, event and stream the in-app SDKs offer — the main listener on iOS, Android and Flutter, React Native's events and client events, Kotlin Multiplatform's event flow, and the calls listeners — with each payload, threading and a JSON example.
Listeners are optional: implement only what you use — every method has a default that does nothing. Where each one runs:
| Platform | Delivered on | Kept |
|---|---|---|
| iOS | The main thread | Weakly — keep your own reference; a view controller that forgets to remove itself is not leaked |
| Android | The main thread | Until you remove it |
| Flutter | The UI isolate | Until you remove it |
| React Native | The JavaScript thread | Hooks until unmount; client.on until you call the function it returned |
| Kotlin Multiplatform | Your coroutine scope — collect the flows where you draw | While you collect |
The main listener
What each one means
| Event | When | Payload |
|---|---|---|
| Session ready | The agent's settings loaded | Session settings |
| Call state | The bar's call connected, or is connecting | connected, connecting |
| Caption | A line being said, again as it fills in | role, text, live |
| Component / pending | The agent drew a component, or one is on its way | The component / { kind } |
| Ended | The bar's call is over, however it ended. Once | — |
| Microphone denied | The microphone was refused | NotAllowedError or NotFoundError |
| Quality, network, reconnecting | The line changed | the quality word; online; active |
| Connect progress (Android) | Where a call being placed is | stage, slow, failed |
| Transport | What carries the audio, and why not the one asked for | not_configured, unreachable, screen_channel, media |
| Connectivity | The engine page loading, ready, retrying or failed | state, attempt, reason |
| Action requested / completed | The agent called an action; your answer | name; the result |
| Friction | The customer looks stuck; the agent has been told | name, screen, detail |
| Theme (iOS) | The design's tokens loaded or changed | the tokens |
| Chat message | A chat turn arrived or changed | the turn (Android: role, text, live) |
| Chat state (Android) | The chat connected or is connecting | connected, connecting |
| Agent volunteered (Flutter, React Native) | The agent spoke without being asked | the text |
| Error | Something your team must fix — loud for you, invisible to the customer | a message |
React Native events
useGraineEvents delivers:
type | Payload |
|---|---|
conversation_started | — |
conversation_ended | reason — the customer hanging up, or the connection dying |
agent_speaking | speaking |
agent_volunteered | text |
action_requested | name |
action_completed | name, status, message |
muted | muted |
mic_denied | reason |
launcher_shown | screen |
friction_detected | name, screen, detail |
connection_quality | quality, rtt, jitter, loss |
network | online |
reconnecting | attempt, delayMs — the chat dropped; the next try is due |
error | message |
Each fires once, at the moment, in order — use them for what you do once (log, count, show a toast), and the provider's state for drawing.
client.on(name, fn) adds the client's own events. It returns a function that
removes the listener.
| Event | Payload |
|---|---|
open / close | { transport } / { code, reason, byClient? } — byClient when your app closed it |
session | The agent's settings, once loaded |
turn_start, chunk, turn_end | An agent turn beginning; its words, as a string; its end |
agent_speaking | true or false |
clear | The customer talked over the agent: drop whatever is still to be shown or played |
text_sent | { id, text } — a waiting message has gone |
transcript | { text, final } — the customer's speech, as recognised |
widget, widget_pending | A component; { kind } while one is on its way |
widget_result | { widget_id, data, summary } — the customer answered a component |
action | { action_id, name, kind?, arguments } — the agent called one of your actions |
action_result | { name, status, message? } — what your handler answered |
diagnostic | { type: "actions", usable, undeclared, unhandled } — see checkActions |
highlight | { name, ttlMs } — the agent points at an element; name null to stop |
friction | { name, screen, detail } — before the agent is told |
app_event | { name, detail?, cooldown_seconds?, max_per_session?, quiet?, data? } — an event reported to the agent |
screen | { screen, actions } — the screen as it was sent, and the actions offered |
variables | The merged prompt variables, after a change |
muted | true or false |
quality | { quality, rtt?, jitter?, loss? } — on change only |
network | { online } |
reconnecting | { attempt, delayMs } |
identity | The IdentityChannel changed |
memories_changed | { forgotten: true, deleted } |
scheduled_calls_changed | { booked: id } or { cancelled: id } |
surface | { surface } — the call or the chat |
native_call | { live } — a native call started or ended |
Other names the client emits are its own plumbing and may change between versions; do not listen for them.
Calls listeners
Do not depend on the order between a state change and the outcome it leads to.
A call can ring, be answered and end before your app's code is running: React
Native and Flutter hold the last 20 outcomes and rings and hand them to the first
listener, oldest first. Send each outcome to your server with its callId.
| Method | Platforms | When |
|---|---|---|
| Incoming call | all but the website | A call started ringing on this device — the SDK shows the ring; this is for your own UI or analytics |
| Call state | iOS, Android, Kotlin Multiplatform, Flutter, React Native (snapshot) | The call moved: idle, ringing, connecting, active, ended |
| Call outcome | all but the website | What became of a call — answered, declined, missed, ended (local, remote, failed) or failed |
| Call caption (Android) | Android | The agent's words as they are said, then the finished line; the customer's likewise |
| Agent speaking (Android) | Android | The agent's voice started or stopped |
| Call controls (Android) | Android | Muted or speaker changed — from your app or from the call screen |
Payload fields: GraineCallOutcome, GraineIncomingCall.

