MQTT Integration
The Eixam platform uses MQTT for real-time SOS ingestion, telemetry ingestion, and SOS lifecycle events. This guide covers the public SDK contract for direct MQTT clients and custom integrations.
Connection parameters
| Parameter | Value |
|---|---|
| Broker host | mqtt.eixam.io |
| TLS port (recommended) | 8883 |
| WebSocket TLS port | 443 (path /mqtt) |
| Protocol | MQTT 3.1.1 / 5 |
| Keep-alive | 30 s |
| Clean session | true |
Use QoS 1 for app traffic. Publish SOS and telemetry messages with retain disabled.
Auth
SDK clients authenticate with MQTT CONNECT credentials derived from the active SDK identity:
| Field | Value |
|---|---|
username | sdk:<app_id>:<external_user_id> |
password | HMAC user hash (Authorization: Bearer <user_hash> on REST) |
clientId | Stable unique client id, usually eixam_<app_id>_<external_user_id> |
The broker validates credentials by calling the Eixam API (POST /v1/sdk/mqtt/auth/connect). On success, the API caches a short-lived session in Redis. Each publish and subscribe is then ACL-checked with POST /v1/sdk/mqtt/auth/acl.
CONNECT still uses the integrator external_user_id, but MQTT topics use the Eixam SDK user UUID. Call GET /v1/sdk/me first and read user.id before building topics.
Topic reference
| Direction | Topic | Purpose |
|---|---|---|
| Publish | sos/alerts/<sdk_user_id> | Trigger or update an SOS incident |
| Publish | tel/<sdk_user_id>/data | Send telemetry |
| Subscribe | sos/events/<sdk_user_id> | Receive SOS lifecycle events for this user |
<sdk_user_id> is GET /v1/sdk/me -> user.id (sdk_users.id). It is not external_user_id.
Example topics for SDK user 11111111-1111-1111-1111-111111111111:
sos/alerts/11111111-1111-1111-1111-111111111111
tel/11111111-1111-1111-1111-111111111111/data
sos/events/11111111-1111-1111-1111-111111111111
ACL per SDK user
Each authenticated SDK session is scoped to its own SDK user id:
| Access | Topic |
|---|---|
| Publish | sos/alerts/<own sdk_user_id> |
| Publish | tel/<own sdk_user_id>/data |
| Subscribe | sos/events/<own sdk_user_id> |
Legacy staging clients can still publish sos/alerts with userId in JSON and use tel/<external_user_id>/data / sos/events/<external_user_id>. New clients should use SDK-user-id topics and omit userId.
Payload schemas
SOS ingest payload
Publish SDKSOSIngestPayload to sos/alerts/<sdk_user_id>:
{
"timestamp": "2026-04-18T12:00:00Z",
"latitude": 41.3851,
"longitude": 2.1734,
"altitude": 12,
"deviceId": "beacon-hw-9",
"deviceBattery": {
"rawValue": 80,
"range": "ok"
},
"mobileBattery": 72,
"mobileCoverage": {
"networkType": "wifi",
"isConnected": true
}
}
When the device has no GPS fix, omit latitude, longitude, and altitude:
{
"timestamp": "2026-04-18T12:00:00Z",
"mobileBattery": 72
}
Telemetry ingest payload
Publish SDKMQTTIngestPayload to tel/<sdk_user_id>/data:
{
"occurredAt": "2026-04-18T12:00:01Z",
"latitude": 41.3852,
"longitude": 2.1735,
"altitude": 11,
"mobileBattery": 80
}
Publish field reference
| Field | Type | Required | Notes |
|---|---|---|---|
timestamp | string (RFC3339 / RFC3339Nano) | Yes, unless occurredAt is present | Primary event time |
occurredAt | string (RFC3339 / RFC3339Nano) | Yes, unless timestamp is present | Used when timestamp is omitted |
latitude | float | No | Must be provided together with longitude, or both omitted |
longitude | float | No | Must be provided together with latitude, or both omitted |
altitude | float | No | Meters. Optional when latitude/longitude are present |
deviceId | string | No | Paired device hardware_id. May reference another user's device in the same app for LoRa relay |
deviceBattery | object | No | Hardware tracker battery |
deviceCoverage | object | No | Hardware tracker connectivity |
mobileBattery | integer (0-100) | No | Mobile device battery percentage |
mobileCoverage | object | No | Mobile device connectivity |
Do not send userId, originalDeviceId, or reportingDeviceId from new clients. Identity comes from the MQTT topic and authenticated session; device ownership and relay attribution are resolved by the backend.
For LoRa relay, the gateway authenticates as its own SDK user and publishes on its own sos/alerts/<gateway-sdk-user-id> or tel/<gateway-sdk-user-id>/data topic. The payload only includes the remote device's deviceId; the backend resolves that device's owner within the same app.
SOS event payload
Subscribe to sos/events/<sdk_user_id> to receive an incident event whenever the incident state changes. Two event types carry the actuator snapshot:
processed— emitted once after the incident is accepted; carries the actuator plan (snapshotVersion: 1, every actuatorscheduled).sos.actuator_update— emitted on every actuator transition; carries the live snapshot.
{
"type": "processed",
"appId": "550e8400-e29b-41d4-a716-446655440001",
"userId": "11111111-1111-1111-1111-111111111111",
"incidentId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "active",
"occurredAt": "2026-04-18T12:00:00.000Z",
"openedAt": "2026-04-18T12:00:00.500Z",
"updatedAt": "2026-04-18T12:00:01.000Z",
"acknowledgedAt": null,
"cancelledAt": null,
"resolvedAt": null,
"snapshotVersion": 1,
"actuators": {
"snapshotVersion": 1,
"items": [
{
"id": "emergency_contacts", "type": "emergency_contacts", "status": "scheduled", "outcome": "pending",
"contacts": [
{ "contactId": "a1b2c3d4-0000-4000-8000-000000000001", "name": "Marie Dubois", "priority": 1, "status": "scheduled",
"channels": [ { "channel": "sms", "status": "scheduled" }, { "channel": "voice", "status": "scheduled" } ] }
]
},
{ "id": "slack", "type": "slack", "status": "scheduled", "outcome": "pending",
"deliveries": [ { "attempt": 1, "status": "scheduled", "provider": "slack" } ] }
]
}
}
| Field | Type | Notes |
|---|---|---|
type | string | Event trigger: processed, sos.actuator_update, acknowledged, cancelled, or resolved |
userId | UUID string | Eixam SDK user id, matching the topic segment |
status | string | active, acknowledged, cancelled, or resolved |
incidentId | UUID string | Stable across all events for the same incident |
occurredAt | ISO 8601 | Timestamp from the original alert payload |
openedAt | ISO 8601 | When the incident was created in the backend |
updatedAt | ISO 8601 | Most recent state change |
acknowledgedAt | ISO 8601 or null | Set when a contact acknowledges via the public link |
cancelledAt | ISO 8601 or null | Set on user-initiated cancel |
resolvedAt | ISO 8601 or null | Set on operator/system resolution |
snapshotVersion | integer | Monotonic per incident. Present on processed and sos.actuator_update. Discard any event not newer than the last seen. |
actuators | object | Per-actuator execution snapshot. See the SOS Actuators guide for the full lifecycle, outcome verdict, and a live worked example. |
The actuators object is identical over REST (incident.actuators) and MQTT. Read SOS Actuators for the status/outcome state model and how to apply snapshotVersion ordering.
Flutter SDK usage
The Flutter SDK manages the MQTT lifecycle automatically. Direct broker access is only needed for custom integrations.
- Trigger SOS
- Listen to SOS events
- Send telemetry
// The SDK publishes to sos/alerts/<sdk_user_id> with QoS 1 automatically.
final incident = await sosRepository.triggerSos(
triggerSource: 'manual_button',
positionSnapshot: currentPosition,
deviceId: pairedDeviceHardwareId,
);
// The SDK subscribes to sos/events/<sdk_user_id> and exposes a typed stream.
sosRepository.watchSosState().listen((state) {
switch (state) {
case SosState.acknowledged:
// A contact has acknowledged the emergency.
case SosState.resolved:
// Incident closed.
default:
break;
}
});
// The SDK publishes to tel/<sdk_user_id>/data with QoS 1.
await telemetryRepository.sendTelemetry(
SdkTelemetryPayload(
timestamp: DateTime.now().toUtc(),
latitude: position?.latitude,
longitude: position?.longitude,
altitude: position?.altitude,
mobileBattery: mobileBatteryPercent,
),
);
Custom / raw MQTT client
If you are building a custom client without the Flutter SDK:
- Resolve the session with
GET /v1/sdk/me. - Use
username = "sdk:<app_id>:<external_user_id>"andpassword = <user_hash>forCONNECT. - Store
user.idfrom the response as<sdk_user_id>. - Subscribe to
sos/events/<sdk_user_id>after connect. - Publish SOS to
sos/alerts/<sdk_user_id>. - Publish telemetry to
tel/<sdk_user_id>/data. - Use QoS 1 and
clean_session = true.
If a connection is rejected, refresh the SDK identity first. If an ACL check is rejected, verify that the topic segment is the SDK user UUID from GET /v1/sdk/me, not the external user id used in the CONNECT username.