Skip to main content

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

ParameterValue
Broker hostmqtt.eixam.io
TLS port (recommended)8883
WebSocket TLS port443 (path /mqtt)
ProtocolMQTT 3.1.1 / 5
Keep-alive30 s
Clean sessiontrue

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:

FieldValue
usernamesdk:<app_id>:<external_user_id>
passwordHMAC user hash (Authorization: Bearer <user_hash> on REST)
clientIdStable 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.

warning

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

DirectionTopicPurpose
Publishsos/alerts/<sdk_user_id>Trigger or update an SOS incident
Publishtel/<sdk_user_id>/dataSend telemetry
Subscribesos/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:

AccessTopic
Publishsos/alerts/<own sdk_user_id>
Publishtel/<own sdk_user_id>/data
Subscribesos/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

FieldTypeRequiredNotes
timestampstring (RFC3339 / RFC3339Nano)Yes, unless occurredAt is presentPrimary event time
occurredAtstring (RFC3339 / RFC3339Nano)Yes, unless timestamp is presentUsed when timestamp is omitted
latitudefloatNoMust be provided together with longitude, or both omitted
longitudefloatNoMust be provided together with latitude, or both omitted
altitudefloatNoMeters. Optional when latitude/longitude are present
deviceIdstringNoPaired device hardware_id. May reference another user's device in the same app for LoRa relay
deviceBatteryobjectNoHardware tracker battery
deviceCoverageobjectNoHardware tracker connectivity
mobileBatteryinteger (0-100)NoMobile device battery percentage
mobileCoverageobjectNoMobile 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.

tip

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 actuator scheduled).
  • 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" } ] }
]
}
}
FieldTypeNotes
typestringEvent trigger: processed, sos.actuator_update, acknowledged, cancelled, or resolved
userIdUUID stringEixam SDK user id, matching the topic segment
statusstringactive, acknowledged, cancelled, or resolved
incidentIdUUID stringStable across all events for the same incident
occurredAtISO 8601Timestamp from the original alert payload
openedAtISO 8601When the incident was created in the backend
updatedAtISO 8601Most recent state change
acknowledgedAtISO 8601 or nullSet when a contact acknowledges via the public link
cancelledAtISO 8601 or nullSet on user-initiated cancel
resolvedAtISO 8601 or nullSet on operator/system resolution
snapshotVersionintegerMonotonic per incident. Present on processed and sos.actuator_update. Discard any event not newer than the last seen.
actuatorsobjectPer-actuator execution snapshot. See the SOS Actuators guide for the full lifecycle, outcome verdict, and a live worked example.
tip

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.

// 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,
);

Custom / raw MQTT client

If you are building a custom client without the Flutter SDK:

  1. Resolve the session with GET /v1/sdk/me.
  2. Use username = "sdk:<app_id>:<external_user_id>" and password = <user_hash> for CONNECT.
  3. Store user.id from the response as <sdk_user_id>.
  4. Subscribe to sos/events/<sdk_user_id> after connect.
  5. Publish SOS to sos/alerts/<sdk_user_id>.
  6. Publish telemetry to tel/<sdk_user_id>/data.
  7. Use QoS 1 and clean_session = true.
tip

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.