SDK & Widget · Mobile
Real-time (Socket.IO)
Receive replies, typing indicators and conversation events the moment they happen, and send messages over the same connection.
Inboxi uses Socket.IO v4 on the same host as the REST API (socketUrl from init). The socket is how a visitor receives replies live; sending can go either over the socket or over REST.
Connect
io(socketUrl)
widget_join
organizationId + visitor
widget_chat_history
Previous messages
widget_message
Live in both directions
Connect and join
After connecting, emit widget_join. Use the visitorId returned by init as userDetails.id, so the socket and the REST session refer to the same visitor. A join without a name is ignored.
import { io } from 'socket.io-client';
const socket = io(session.socketUrl, {
query: { appId: String(session.organizationId) },
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000,
});
socket.on('connect', () => {
socket.emit('widget_join', {
organizationId: session.organizationId,
context: { source: 'mobile_sdk' }, // add widgetId here if you run several widgets
userDetails: {
id: session.visitorId,
name: user.name,
email: user.email ?? null,
phone: user.phone ?? null,
},
});
});
socket.on('widget_chat_history', ({ chatId, messages }) => renderHistory(chatId, messages));
socket.on('widget_message', (evt) => {
if (evt.type === 'agent_message') renderReply(evt.message);
if (evt.type === 'system_message') renderNotice(evt.message.content);
});
socket.on('widget_system_event', ({ type, data }) => handleEvent(type, data));
socket.on('widget_typing', ({ isTyping }) => showTyping(isTyping));
socket.on('widget_error', ({ code, message }) => console.warn(code, message));
// Send
socket.emit('widget_message', {
organizationId: session.organizationId,
visitorId: session.visitorId,
text: 'Hello!',
});Client → server events
| Event | Payload | Description |
|---|---|---|
widget_join | { organizationId, userDetails: { id, name, email?, phone? }, context?: { widgetId?, source? } } | Joins the visitor's conversation. Required before sending. Re-emit after every reconnect. |
widget_message | { organizationId, visitorId, text, mediaUrl?, mediaType? } | Sends a visitor message. Triggers the AI agent if enabled. |
widget_set_email | { visitorId, email } | Saves an email on the conversation so your team can follow up. |
widget_rating | { visitorId, rating } | Rates the conversation, an integer from 1 to 5. |
Server → client events
| Event | Payload | When |
|---|---|---|
widget_chat_history | { chatId, messages: [{ id, content, sender, senderName, mediaUrl, mediaType, timestamp }] } | Right after a join, when the conversation already has messages. |
widget_message | { type: 'agent_message', message: { id, content, senderName, mediaUrl, mediaType, timestamp } } | A reply from your team or the AI agent. |
widget_message | { type: 'system_message', message: { id, content } } | An automatic notice, such as a queue message. |
widget_system_event | { type, data, timestamp } | Conversation state changes: agent_joined (data.agentName), chat_resolved, chat_reopened, email_captured. |
widget_typing | { isTyping } | A teammate or the AI is typing. |
widget_error | { code, message } | Something went wrong: JOIN_FAILED, SESSION_NOT_FOUND, INVALID_EMAIL. See Errors. |
An
agent_message may carry a card object instead of text (for example product or choice cards in store widgets). Clients that do not render cards can skip messages with empty content.Reconnection and reliability
- Enable Socket.IO's built-in reconnection and emit
widget_joinagain in everyconnecthandler. Replies are delivered to the conversation room, which a socket enters only by joining. - If a
widget_messagearrives before the join is processed, Inboxi waits briefly; if no session appears you getSESSION_NOT_FOUND. Re-join and resend. - De-duplicate by message
id: history after a reconnect can include messages you already rendered. - When the socket is down (app in the background, flaky network), send with
POST /sdk/messagesand rely on push for replies. Re-sync with the history endpoint when the app returns to the foreground. - Disconnect when the chat screen closes. A visitor with no open socket is treated as offline, which is what triggers push notifications.