SMS Conversations
The SMS Conversations API provides call center agents with a unified two-way messaging inbox. Conversations use a Persistent Thread model — exactly one thread exists per client phone number and system DID hotline in the workspace, ensuring complete message history across agent shifts and customer touchpoints.
Data Scoping & Team Assignment
Access to conversation threads is strictly scoped based on the agent's role and team assignments:
- Leader / Supervisor / Admin: Full access to view and manage all conversations across the workspace.
- Member (Agent): Can only access conversations where the system DID is assigned to one of their teams (
shared_teams_id), or threads assigned directly to them or their team. Regular members can only send new SMS from numbers assigned to their teams.
Endpoints​
| Method | Endpoint | Description |
|---|---|---|
GET | /call-center/conversations | List conversation threads (SMS Inbox) |
POST | /call-center/conversations | Start a new conversation thread (New Message) |
GET | /call-center/conversations/:id | Get details of a single conversation thread |
GET | /call-center/conversations/:id/messages | List message history for a conversation thread |
POST | /call-center/conversations/:id/messages | Send an outbound SMS reply in a thread |
PATCH | /call-center/conversations/:id/read | Mark conversation messages as read |
PATCH | /call-center/conversations/:id | Update conversation status or agent assignment |
Authentication​
All endpoints require a Bearer JWT with agent-api audience.
Authorization: Bearer YOUR_AGENT_JWT
The Conversation Object​
| Field | Type | Description |
|---|---|---|
id | string | Unique conversation identifier prefixed with conv_ (e.g. conv_a1b2c3d4e5f6) |
workspace_id | string | Workspace identifier |
phone_number_id | string | ID of the workspace DID number used for the thread (pn_...) |
system_number | string | E.164 phone number of the workspace DID (e.g. +13074295456) |
client_number | string | E.164 phone number of the client (e.g. +18647123123) |
client_name | string | null | Contact display name if matched in address books or manually set |
contact_id | string | null | Associated contact ID if resolved in contacts (con_...) |
assigned_agent_id | string | null | Agent ID currently assigned to handle this thread |
assigned_team_id | string | null | Team ID assigned to handle this thread |
status | string | Thread lifecycle status: open | closed |
unread_count | number | Number of unread inbound messages from client |
last_message | object | null | Snippet object of the latest message in the thread (see below) |
last_message_at | string | null | ISO 8601 timestamp of the latest message |
last_read_at | string | null | ISO 8601 timestamp when an agent last marked the thread as read |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last update timestamp |
The last_message Snippet Object​
Embedded inside conversation.last_message:
| Field | Type | Description |
|---|---|---|
id | string | Message identifier prefixed with msg_ |
body | string | Content text of the latest message |
direction | string | Message transmission direction: inbound | outbound |
sender_type | string | Originator classification: client | agent | voice_agent | system |
sender_id | string | null | Agent ID who sent the message (or null if sent by client, AI agent, or automated system) |
created_at | string | ISO 8601 creation timestamp |
The Message Object​
Represents an individual SMS or MMS message within a conversation thread.
| Field | Type | Description |
|---|---|---|
id | string | Unique message identifier prefixed with msg_ (e.g. msg_682d82eb0ba1ffeace018f) |
conversation_id | string | Associated conversation thread ID (conv_...) |
workspace_id | string | Workspace identifier |
phone_number_id | string | ID of the workspace DID phone number used (pn_...) |
from | string | Sender phone number in E.164 format (e.g. +13074295456) |
to | string | Recipient phone number in E.164 format (e.g. +18647123123) |
client_number | string | Client phone number in E.164 format |
body | string | Text content of the SMS message |
direction | string | Transmission direction: inbound | outbound |
status | string | Delivery / receipt status: queued | sending | sent | delivered | received | failed | undelivered |
sender_type | string | Originator entity: client | agent | voice_agent | system |
sender_id | string | null | ID of the agent who sent the message (null for client or system messages) |
media_urls | string[] | Optional array of public HTTPS URLs for MMS attachments |
cost | number | null | Cost incurred in workspace credits / USD for sending the SMS |
error_code | string | null | Telecom carrier or internal error code if transmission failed |
error_message | string | null | Explanatory error message if transmission failed |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last update timestamp |
Enumerations & Value References​
Comprehensive reference for enumerated fields used across conversations, messages, real-time events, and webhooks.
direction​
Specifies the communication direction relative to the workspace Call Center.
| Enum Value | Flow | Description |
|---|---|---|
inbound | Customer âž” Workspace DID | Incoming message: Sent by an external customer/client to a workspace DID hotline. Increments unread_count on the thread and triggers incoming alert notifications. |
outbound | Workspace DID âž” Customer | Outgoing message: Sent from a workspace DID to a customer's phone number by an agent, AI voice agent, or automated workflow. |
status​
The status attribute is used in two contexts: Conversation Status (thread resolution lifecycle) and Message Status (carrier delivery state).
Conversation Status​
Applies to conversation.status and query filters on /call-center/conversations.
| Enum Value | State | Description |
|---|---|---|
open | Active | The conversation thread is active, awaiting agent response, or undergoing live interaction. Newly created conversations or threads receiving a new customer inbound SMS are automatically set to open. |
closed | Resolved / Archived | The conversation thread has been marked as resolved or closed by an agent or supervisor. If the customer sends another inbound SMS to the same DID in the future, the thread will automatically reopen (open) and bump to the top of the inbox. |
Message Status​
Applies to message.status and real-time carrier status delivery updates.
| Enum Value | Direction | Category | Description |
|---|---|---|---|
queued | Outbound | Pending | Message has been accepted by the API and enqueued for carrier dispatch. |
sending | Outbound | In-flight | Message is actively being transmitted to the telecom carrier network. |
sent | Outbound | Dispatched | Telecom carrier accepted the message and dispatched it into the cellular network. |
delivered | Outbound | Success | Carrier received a confirmed Handset Delivery Receipt (DLR) indicating the SMS was successfully delivered to the customer's phone. |
received | Inbound | Success | Inbound SMS was successfully received from the carrier and appended to the conversation thread. |
failed | Outbound | Terminal Failure | Message delivery permanently failed before or during carrier dispatch (e.g. invalid destination number, blocked prefix, or unroutable route). Check error_code and error_message. |
undelivered | Outbound | Terminal Failure | Carrier dispatched the SMS but the destination mobile network reported delivery failure (e.g. handset powered off, out of coverage, or unreachable). |
stateDiagram-v2
direction LR
[*] --> queued: Send Message
queued --> sending: Carrier Dispatch
sending --> sent: Carrier Accepted
sent --> delivered: Carrier DLR Success
sent --> undelivered: Carrier DLR Failure
sending --> failed: Carrier Rejected
queued --> failed: Invalid Route/Number
[*] --> received: Inbound Message
sender_type​
Identifies the originator or authoring entity that created the message.
| Enum Value | Typical Direction | Description |
|---|---|---|
client | inbound | Customer / Client: The external mobile phone subscriber sending an SMS to the workspace DID number. |
agent | outbound | Call Center Agent: A human agent sending a reply from the Firetell Console inbox or Client SDK. sender_id will contain the agent's user ID. |
voice_agent | outbound | AI Voice Agent: An automated conversational Voice AI agent sending an SMS during or after a phone call (e.g. sending a scheduling link, OTP verification code, address, or summary). |
system | outbound | System / Workflow Automation: Automatically generated by backend platform workflows, API triggers, automated reminders, or webhooks. |
List Conversations (Inbox)​
GET /call-center/conversations
Retrieve a paginated list of conversation threads accessible to the authenticated agent, ordered by most recent message activity.
Query Parameters​
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Number of items per page (max: 100) |
status | string | — | Filter by status: open or closed |
phone_number_id | string | — | Filter by specific hotline phone number ID (pn_...) |
assigned_agent_id | string | — | Filter by assigned agent: 'me', 'unassigned', or a specific agent ID |
assigned_to | string | — | Alias for assigned_agent_id |
assigned_team_id | string | — | Filter by assigned team ID |
unread_only | boolean | false | When set to true, only returns conversation threads that contain unread incoming messages |
search | string | — | Search across client_number, client_name, or system_number |
Request​
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations?status=open&page=1&limit=20" \
-H "Authorization: Bearer YOUR_AGENT_JWT"
Response 200 OK​
{
"data": [
{
"id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "John Doe",
"contact_id": "con_650000000000000000000130",
"assigned_agent_id": "agent_001",
"assigned_team_id": "team_support_tier1",
"status": "open",
"unread_count": 2,
"last_message": {
"id": "msg_682d82eb0ba1ffeace018f",
"body": "Can I reschedule my appointment?",
"direction": "inbound",
"sender_type": "client",
"sender_id": null,
"created_at": "2026-09-27T10:00:00.000Z"
},
"last_message_at": "2026-09-27T10:00:00.000Z",
"last_read_at": "2026-09-27T09:30:00.000Z",
"created_at": "2026-09-25T08:00:00.000Z",
"updated_at": "2026-09-27T10:00:00.000Z"
}
],
"meta": {
"total": 1,
"page": 1,
"limit": 20,
"total_pages": 1
}
}
Start New Conversation (New Message)​
POST /call-center/conversations
Initiate a new conversation thread or send a message to a client directly from the agent UI (e.g., clicking "New Message"). If an active thread already exists between the selected DID and the client, the message is appended to the existing thread. The thread is automatically assigned to the initiating agent if currently unassigned.
DID Authorization Check
For agents with the member role, from must be a DID phone number assigned to at least one of their teams. If unauthorized, a 403 Forbidden error is returned.
Request Body​
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Workspace DID phone number to send from (E.164 format, e.g. +13074295456) |
client_number | string | Yes | Client recipient phone number (E.164 format, e.g. +18647123123) |
body | string | Yes | Text body of the SMS message (up to 1,600 characters) |
media_urls | string[] | No | Optional array of media URLs for MMS messages |
Request​
curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/conversations" \
-H "Authorization: Bearer YOUR_AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"from": "+13074295456",
"client_number": "+18647123123",
"body": "Hello John, thank you for reaching out to Firetell support."
}'
Response 201 Created​
{
"conversation": {
"id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "John Doe",
"contact_id": "con_650000000000000000000130",
"assigned_agent_id": "agent_001",
"assigned_team_id": null,
"status": "open",
"unread_count": 0,
"last_message": {
"id": "msg_682d82eb0ba1ffeace018f",
"body": "Hello John, thank you for reaching out to Firetell support.",
"direction": "outbound",
"sender_type": "agent",
"sender_id": "agent_001",
"created_at": "2026-09-27T10:05:00.000Z"
},
"last_message_at": "2026-09-27T10:05:00.000Z",
"last_read_at": "2026-09-27T10:05:00.000Z",
"created_at": "2026-09-27T10:05:00.000Z",
"updated_at": "2026-09-27T10:05:00.000Z"
},
"message": {
"id": "msg_682d82eb0ba1ffeace018f",
"conversation_id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"from": "+13074295456",
"to": "+18647123123",
"client_number": "+18647123123",
"body": "Hello John, thank you for reaching out to Firetell support.",
"direction": "outbound",
"status": "queued",
"sender_type": "agent",
"sender_id": "agent_001",
"media_urls": [],
"created_at": "2026-09-27T10:05:00.000Z",
"updated_at": "2026-09-27T10:05:00.000Z"
}
}
Get Conversation Details​
GET /call-center/conversations/:id
Retrieve full details of an accessible conversation thread by its ID.
Path Parameters​
| Parameter | Type | Description |
|---|---|---|
id | string | Conversation ID prefixed with conv_ |
Request​
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6" \
-H "Authorization: Bearer YOUR_AGENT_JWT"
Response 200 OK​
{
"id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "John Doe",
"contact_id": "con_650000000000000000000130",
"assigned_agent_id": "agent_001",
"assigned_team_id": "team_support_tier1",
"status": "open",
"unread_count": 2,
"last_message": {
"id": "msg_682d82eb0ba1ffeace018f",
"body": "Can I reschedule my appointment?",
"direction": "inbound",
"sender_type": "client",
"sender_id": null,
"created_at": "2026-09-27T10:00:00.000Z"
},
"last_message_at": "2026-09-27T10:00:00.000Z",
"last_read_at": "2026-09-27T09:45:00.000Z",
"created_at": "2026-09-25T08:00:00.000Z",
"updated_at": "2026-09-27T10:00:00.000Z"
}
List Messages in Conversation​
GET /call-center/conversations/:id/messages
Retrieve paginated messages for a conversation thread. Results are sorted in chronological order (oldest first to newest last) to facilitate rendering chat feeds in agent interfaces.
Query Parameters​
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 50 | Number of messages to return per page (max: 100) |
before | string | — | ISO 8601 timestamp cursor to paginate backwards in history |
after | string | — | ISO 8601 timestamp cursor to paginate forwards in history |
Request​
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/messages?page=1&limit=50" \
-H "Authorization: Bearer YOUR_AGENT_JWT"
Response 200 OK​
{
"data": [
{
"id": "msg_682d82eb0ba1ffeace018e",
"conversation_id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"from": "+18647123123",
"to": "+13074295456",
"client_number": "+18647123123",
"body": "Hi, I need assistance with my service.",
"direction": "inbound",
"status": "received",
"sender_type": "client",
"sender_id": null,
"media_urls": [],
"created_at": "2026-09-27T09:55:00.000Z",
"updated_at": "2026-09-27T09:55:01.000Z"
},
{
"id": "msg_682d82eb0ba1ffeace018f",
"conversation_id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"from": "+13074295456",
"to": "+18647123123",
"client_number": "+18647123123",
"body": "Can I reschedule my appointment?",
"direction": "inbound",
"status": "received",
"sender_type": "client",
"sender_id": null,
"media_urls": [],
"created_at": "2026-09-27T10:00:00.000Z",
"updated_at": "2026-09-27T10:00:01.000Z"
}
],
"meta": {
"total": 2,
"page": 1,
"limit": 50,
"total_pages": 1
}
}
Send Message (Reply)​
POST /call-center/conversations/:id/messages
Send an outbound SMS/MMS reply to the client in an existing conversation thread. The message automatically dispatches from the thread's configured system_number to the client's client_number.
Request Body​
| Field | Type | Required | Description |
|---|---|---|---|
body | string | Yes | Text body of the SMS reply (up to 1,600 characters) |
media_urls | string[] | No | Optional array of public HTTPS URLs for MMS attachments |
Request​
curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/messages" \
-H "Authorization: Bearer YOUR_AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"body": "Sure thing, John! We have slots available tomorrow at 2:00 PM or 4:00 PM."
}'
Response 201 Created​
{
"id": "msg_682d82eb0ba1ffeace0190",
"conversation_id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"from": "+13074295456",
"to": "+18647123123",
"client_number": "+18647123123",
"body": "Sure thing, John! We have slots available tomorrow at 2:00 PM or 4:00 PM.",
"direction": "outbound",
"status": "queued",
"sender_type": "agent",
"sender_id": "agent_001",
"media_urls": [],
"created_at": "2026-09-27T10:05:00.000Z",
"updated_at": "2026-09-27T10:05:00.000Z"
}
Mark Conversation as Read​
PATCH /call-center/conversations/:id/read
Mark all unread incoming messages in a conversation thread as read. This resets unread_count to 0 and updates last_read_at to the current timestamp.
Request​
curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/read" \
-H "Authorization: Bearer YOUR_AGENT_JWT"
Response 200 OK​
{
"ok": true,
"conversation_id": "conv_a1b2c3d4e5f6"
}
Update Conversation​
PATCH /call-center/conversations/:id
Update conversation metadata, such as assigning the thread to a specific agent or team, or closing/re-opening the thread status.
Request Body​
| Field | Type | Description |
|---|---|---|
status | string | Set thread status: open or closed |
assigned_agent_id | string | null | Assign thread to an agent ID (or null to unassign) |
assigned_team_id | string | null | Assign thread to a team ID (or null to unassign) |
client_name | string | Custom display name for the customer |
Request​
curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6" \
-H "Authorization: Bearer YOUR_AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"status": "closed",
"assigned_agent_id": "agent_002",
"assigned_team_id": "team_support_tier2"
}'
Response 200 OK​
{
"id": "conv_a1b2c3d4e5f6",
"workspace_id": "yourcompany",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "John Doe",
"contact_id": "con_650000000000000000000130",
"assigned_agent_id": "agent_002",
"assigned_team_id": "team_support_tier2",
"status": "closed",
"unread_count": 0,
"last_message": {
"id": "msg_682d82eb0ba1ffeace018f",
"body": "Can I reschedule my appointment?",
"direction": "inbound",
"sender_type": "client",
"sender_id": null,
"created_at": "2026-09-27T10:00:00.000Z"
},
"last_message_at": "2026-09-27T10:00:00.000Z",
"last_read_at": "2026-09-27T10:05:00.000Z",
"created_at": "2026-09-25T08:00:00.000Z",
"updated_at": "2026-09-27T10:10:00.000Z"
}
Realtime Notifications & Synchronization​
Firetell keeps agent devices and browser call center consoles synchronized across all inbound and outbound messages in real time.
1. Mobile Push Notifications (Inbound SMS)​
When a customer sends an SMS/MMS to a workspace number, Firetell delivers a high-priority alert notification to the assigned agents' mobile apps (iOS & Android) via APNs (Alert) and FCM.
Agent Targeting & Routing​
Push notifications are routed automatically based on the conversation assignment:
- Assigned Agent: If the conversation has an
assigned_agent_id, only that agent's devices receive the push. - Assigned Team: If the conversation has an
assigned_team_id, all active agents belonging to that team receive the push. - DID Shared Teams (Fallback): If the conversation has no assigned agent or team, all active agents in the teams mapped to the phone number (
shared_teams_id) receive the push notification.
Outbound SMS Policy Outbound messages sent by agents do not trigger mobile push notifications. This prevents notification spam and alert fatigue across teammates while ensuring agents only get mobile alerts when a customer needs attention.
Device Token Registration​
Mobile clients must register their regular notification token with the Agent API using POST /me/devices/notification-push-token:
- iOS: Register the standard APNs alert token (
UNUserNotificationCenter). - Android: Register the FCM registration token (
FirebaseMessaging).
Push Payload Specification​
iOS (APNs Alert Payload)​
Delivered with apns-push-type: alert and apns-priority: 10:
{
"aps": {
"alert": {
"title": "Alice Smith (+18647123123)",
"body": "Hello, I need assistance with my reservation."
},
"sound": "default",
"badge": 3,
"thread-id": "conv_a1b2c3d4e5f6"
},
"event": "message.received",
"workspace_id": "ws_123456789",
"message_id": "msg_be52701819fa34a72ae414",
"conversation_id": "conv_a1b2c3d4e5f6",
"from_number": "+18647123123",
"to_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "Alice Smith",
"body": "Hello, I need assistance with my reservation.",
"unread_count": 3,
"created_at": "2026-09-27T10:00:00.000Z"
}
Android/iOS (FCM Notification + Data Payload)​
Sent with priority high:
{
"notification": {
"title": "Alice Smith",
"body": "Hello, I need assistance with my reservation."
},
"android": {
"notification": {
"channel_id": "firetell_messages",
"tag": "conv_a1b2c3d4e5f6"
}
},
"apns": {
"payload": {
"aps": {
"thread-id": "conv_a1b2c3d4e5f6"
}
}
},
"data": {
"event": "message.received",
"workspace_id": "ws_123456789",
"message_id": "msg_be52701819fa34a72ae414",
"conversation_id": "conv_a1b2c3d4e5f6",
"from_number": "+18647123123",
"to_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "Alice Smith",
"body": "Hello, I need assistance with my reservation.",
"unread_count": "3",
"created_at": "2026-09-27T10:00:00.000Z"
}
}
Conversation Grouping (Threading)
- iOS: Uses
aps.thread-idset toconversation_id. iOS natively groups incoming notifications into an expandable conversation stack in the Notification Center. - Android: Uses
android.notification.tagset toconversation_id. If building custom notifications viaFirebaseMessagingService, passdata.conversation_idintoNotificationCompat.Builder.setGroup(conversationId)for native Android notification grouping.
2. Mobile Silent / Data-Only Push Notifications (conversation.updated)​
When a conversation is modified — such as an agent marking messages as read, reassigning to another agent/team, or updating thread status (open / closed) — Firetell dispatches a silent, data-only push notification (conversation.updated) to target mobile devices.
Purpose & Architecture​
Unlike incoming SMS alerts, silent pushes contain no alert banner, no sound, and no popup. They wake the mobile operating system in the background to execute code:
- Local Storage Synchronization: Update local databases (e.g. SQLite, WatermelonDB, Core Data, Realm) with the new
unread_count,status, andassigned_agent_id. - Badge Count Synchronization: Update or clear the application icon badge without requiring user interaction.
- Notification Dismissal: Dismiss existing notification center banners for this conversation when the thread has been read or resolved on another device or web portal.
Agent Targeting & Routing​
Delivered automatically to:
- Assigned Agent: If the conversation is assigned, the assigned agent's devices receive the silent push.
- Assigned Team: If assigned to a team, all active agents in that team receive the push.
- DID Shared Teams (Fallback): If unassigned, active agents in the teams assigned to the phone number receive the push.
Push Payload Specification​
iOS (APNs Background Silent Push)​
Delivered with apns-push-type: background and apns-priority: 5:
{
"aps": {
"content-available": 1
},
"event": "conversation.updated",
"workspace_id": "ws_123456789",
"id": "conv_a1b2c3d4e5f6",
"conversation_id": "conv_a1b2c3d4e5f6",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "Alice Smith",
"assigned_agent_id": "agent_001",
"assigned_team_id": null,
"status": "open",
"unread_count": 0,
"last_message": {
"id": "msg_05f18045cba1452204774f",
"body": "Hello Alice, I have updated your reservation for 7:00 PM.",
"direction": "outbound",
"sender_type": "agent",
"sender_id": "agent_001",
"created_at": "2026-09-28T03:39:00.000Z"
},
"last_message_at": "2026-09-28T03:39:00.000Z",
"last_read_at": "2026-09-28T03:40:00.000Z",
"created_at": "2026-09-27T10:00:00.000Z",
"updated_at": "2026-09-28T03:40:00.000Z",
"timestamp": "2026-09-28T03:40:00.000Z"
}
Android / iOS (FCM Data-Only Payload)​
Sent with priority: high and no notification block:
{
"android": {
"priority": "high",
"ttl": "60s"
},
"apns": {
"headers": {
"apns-priority": "5",
"apns-push-type": "background"
},
"payload": {
"aps": {
"content-available": 1
}
}
},
"data": {
"event": "conversation.updated",
"workspace_id": "ws_123456789",
"id": "conv_a1b2c3d4e5f6",
"conversation_id": "conv_a1b2c3d4e5f6",
"phone_number_id": "pn_99bf875464f1c096f46aff",
"system_number": "+13074295456",
"client_number": "+18647123123",
"client_name": "Alice Smith",
"assigned_agent_id": "agent_001",
"assigned_team_id": "",
"status": "open",
"unread_count": "0",
"last_message": "{\"id\":\"msg_05f18045cba1452204774f\",\"body\":\"Hello Alice, I have updated your reservation for 7:00 PM.\",\"direction\":\"outbound\",\"sender_type\":\"agent\",\"sender_id\":\"agent_001\",\"created_at\":\"2026-09-28T03:39:00.000Z\"}",
"last_message_at": "2026-09-28T03:39:00.000Z",
"last_read_at": "2026-09-28T03:40:00.000Z",
"created_at": "2026-09-27T10:00:00.000Z",
"updated_at": "2026-09-28T03:40:00.000Z",
"timestamp": "2026-09-28T03:40:00.000Z"
}
}
Handling Silent Pushes in Mobile Code
- iOS APNs (
AppDelegate/UNUserNotificationCenter): Handle viaapplication(_: didReceiveRemoteNotification:fetchCompletionHandler:). Inspectpayload["event"] == "conversation.updated", sync local database, updateUIApplication.shared.applicationIconBadgeNumber, and callcompletionHandler(.newData). - Android/iOS FCM (
FirebaseMessagingService): InonMessageReceived(remoteMessage: RemoteMessage), checkremoteMessage.data["event"] == "conversation.updated". Because there is nonotificationblock, the Android/iOS OS will not show any system notification. Update your local database (Room / SQLite / WatermelonDB) in the background.
3. Browser Realtime Synchronization (SSE)​
Browser-based Call Center Portals subscribe to the Realtime Events (SSE) stream:
GET /stream?token=YOUR_AGENT_JWT
Message events are published to the agent's scoped unicast channels
Supported Message Events​
| Event | Direction | Description |
|---|---|---|
message.received | Inbound | Emitted when a new message is received from a customer. Updates the chat thread and increases the unread count. |
message.sent | Outbound | Emitted when an agent sends an outbound message. Instantly updates the conversation for all teammates in the same team to prevent collision. |
message.updated | Status | Emitted when delivery status updates from the carrier (e.g., transitions from queued to sent, delivered, or failed). |
conversation.updated | State | Emitted when a conversation thread is updated (assigned/reassigned agent, status open/closed, or unread count reset upon reading). |
Example JavaScript Listener​
const eventSource = new EventSource(
`https://{workspace_id}.firetell.app/stream?token=${encodeURIComponent(agentJwt)}`,
);
// 1. Inbound customer message
eventSource.addEventListener("message.received", (e) => {
const { data } = JSON.parse(e.data);
console.log("New inbound SMS:", data.body, "from:", data.from_number);
// Prepend or append to conversation chat view, increment unread badge
updateConversationInbox(data.conversation_id, data);
});
// 2. Outbound message from colleague (Collision Avoidance)
eventSource.addEventListener("message.sent", (e) => {
const { data } = JSON.parse(e.data);
console.log("Teammate sent SMS:", data.body, "by agent:", data.sender_id);
// Reflect teammate's reply immediately without reloading
appendOutboundMessageToThread(data.conversation_id, data);
});
// 3. Delivery status update (Carrier acknowledgment)
eventSource.addEventListener("message.updated", (e) => {
const { data } = JSON.parse(e.data);
console.log("Message status updated:", data.id, "to:", data.status);
// Update tick icon to delivered / failed
updateMessageStatusIndicator(data.id, data.status, data.error_message);
});
// 4. Conversation state updated (Assignment / Status change / Read receipt)
eventSource.addEventListener("conversation.updated", (e) => {
const { data } = JSON.parse(e.data);
console.log(
"Conversation updated:",
data.id,
"assigned to:",
data.assigned_agent_id,
"status:",
data.status,
);
// Update conversation inbox thread item in real time
syncConversationState(data.id, data);
});