Logo Neyos

Webhook

Webhooks allow you to automatically redirect messages generated by your trackers to your own systems via HTTP POST. They transmit raw data (position, events, system status) in JSON format, ready to be used in your business applications.

Operating principles

Each Neyos OTRAC tracker transmits its data in real time to your server via HTTP POST requests in JSON format. Each payload contains either telemetry data (GPS points, system events) or a tracker state update.

Data flow

Serveur Neyos

Restauration du flux de données
Historique sous deux mois
Mise à disposition sécurisé

Webhook & API

Routage des données
Authentification sécurisée
Lecture et écriture des flux
Neyos

Plateforme Neyos

Prise en main rapide
Tableau de bord et configuration
Facturation simplifiée

Votre application

Des données pour vos métiers
En temps réel
Adapté à vos usages

Key points

  • Protocol: HTTPS POST only
  • Format: JSON (Content-Type: application/json)
  • Authentication: unique URL per customer
  • Signature: every call is signed with HMAC-SHA256 (see Notification signature)
  • Expected response: HTTP 200 (the response body is not read)
  • Recommended response time: < 5 seconds

Step-by-step configuration

Webhook configuration is done directly from the Neyos interface (Webhooks tab). Here is the typical procedure:

  1. Log in to the Neyos platform.
  2. Go to the Webhook tab
  3. Click on .
  4. Enter the target URL of your server, and click Enabled
  5. Your webhook is now running, data is redirected to the registered endpoint.

Your endpoint must

  1. Accept POST requests over HTTPS
  2. Parse the JSON body
  3. Respond HTTP 200
  4. Process the request in less than 5 seconds

Notification signature

Every outgoing webhook call is signed with HMAC-SHA256. This signature lets you verify that the payload really comes from Neyos and has not been tampered with in transit.

The signing secret (signingSecret) is returned to you only once, when the webhook is created.

Added headers

HeaderDescription
X-Neyos-TimestampUnix timestamp in seconds at the time of sending
X-Neyos-SignatureHMAC-SHA256 signature, formatted as sha256=<hex>
X-Neyos-Event-IdUnique event identifier (evt_…), stable across retries

Signature computation

payload   = "{timestamp}.{rawBody}"
signature = HMAC-SHA256(payload, signingSecret)
header    = "sha256=" + hex(signature)
  • timestamp — the value of the X-Neyos-Timestamp header, in seconds;
  • rawBody — the request body exactly as received, byte for byte;
  • signingSecret — the secret returned when the webhook was created.

Verification procedure

Read the raw body before any JSON parsing

Re-serializing an already parsed JSON object changes whitespace and key order, and invalidates the signature.

Check the timestamp

Reject the request if |now - timestamp| > 300 seconds. This window protects against the replay of an intercepted notification.

Recompute the HMAC

With your signingSecret and the {timestamp}.{rawBody} string.

Compare in constant time

Use a timingSafeEqual-style comparison rather than a plain string equality.

Deduplicate

Keep track of the X-Neyos-Event-Id values already processed to stay idempotent across retries.

import crypto from 'node:crypto'

function verifyNeyosSignature(rawBody: string, headers: Record<string, string>, secret: string) {
  const timestamp = Number(headers['x-neyos-timestamp'])
  const sigHeader = headers['x-neyos-signature']
  if (!timestamp || !sigHeader) return false

  // Replay protection: 5-minute window
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')
  const expectedHeader = `sha256=${expected}`

  return crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expectedHeader))
}
Reject any request with an invalid signature before processing its content, and never log your signingSecret.

Webhook types

Each payload contains a type field at the root that identifies the type of webhook.

TypeDescriptionFrequency
message.createdNew telemetry message (GPS points + events)Each time the tracker sends data
tracker.updatedA tracker's alias or notes are modifiedOn each modification
tracker.createdA tracker is attached to your organizationRare
tracker.deletedA tracker is deletedRare
tracker_action.createdA command is queued for a trackerEach time a command is created
tracker_action.updatedA command changes stateSeveral times per command
tracker_action.deletedA command is deletedRare
tracker.updated is not emitted on every new position or battery change: it is deliberately limited to alias and notes modifications, made from the platform or the API. To follow a tracker's field activity, listen to message.created.

message.created — Telemetry data

This is the main webhook. It contains the data collected by the tracker: GPS positions, system events, and the network/tracker context at the time of reception.

Root structure

{
  "type": "message.created",
  "data": {
    "id": "msg_XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "type": "datas",
    "network": "gsm",
    "mcc": 208,
    "mnc": 10,
    "rsrq": 61,
    "battery": 70,
    "temperature": 0,
    "receivedAt": "2026-02-25T14:32:50.358+00:00",
    "elements": [ ... ],
    "networkOperator": { ... },
    "tracker": { ... }
  }
}

data fields:

FieldTypeDescriptionExample
idstringUnique message identifiermsg_3AAKX57Y9c0KK5YuMxlAQhANjaS
typestringAlways "datas"datas
networkstringNetwork type usedgsm
mccintegerMobile Country Code208 (France)
mncintegerMobile Network Code (operator)10 (SFR)
rsrqintegerSignal quality (30 low → 70 excellent)61
batteryintegerBattery level (0-100 %)70
temperatureintegerInternal temperature in °C0
receivedAtstringPlatform reception date/time (ISO 8601)2026-02-25T14:32:50.358+00:00
elementsarrayArray of points and eventssee sections below
networkOperatorobjectNetwork operator detailssee dedicated section
trackerobjectFull tracker statesee dedicated section

Elements: GPS Point

A point element represents a timestamped GPS position captured by the tracker at a given moment.

{
  "id": "elm_3A6em1aqCXEqx7mmrIowhAVaQq2",
  "type": "point",
  "datas": {
    "type": "point",
    "timestamp": 1771917582000,
    "tag": 9216,
    "source": "GNSS",
    "precision": "MS",
    "latitude": 45.4570617,
    "longitude": 4.4021235,
    "accuracy": 18,
    "satellites": 11,
    "fix": 4,
    "speed": "40.8",
    "temperature": 19,
    "heading": 230,
    "altitude": 448,
    "tags": {
      "type": "POINT",
      "status": "NEW",
      "location": "RAM",
      "priority": "NORMAL",
      "gps": "DISPO",
      "fix": "3D",
      "buffer": "LIVE"
    }
  },
  "messageId": "msg_3A6em6HE5ts0Ls1Vzq863IuD62x",
  "trackerId": "tra_35pBms5aEyRZnLVAG7u7ZdmcCTC",
  "createdAt": "2026-02-24T07:20:02.554+00:00"
}

Root fields of the point element:

FieldTypeDescription
idstringUnique element identifier
typestringAlways "point"
messageIdstringParent message ID
trackerIdstringID of the sending tracker
createdAtstringPlatform-side creation date (ISO 8601)

datas fields of a point:

FieldTypeUnitDescription
typestring-Always "point"
timestampintegermsUnix timestamp in milliseconds
taginteger-Bitmask encoding of the metadata
sourcestring-Position source ("GNSS")
precisionstring-Time precision (see reference)
latitudenumberdegreesWGS84 latitude. 0 if no fix
longitudenumberdegreesWGS84 longitude. 0 if no fix
accuracyintegermEstimated horizontal accuracy. 0 if no fix
satellitesinteger-Number of satellites used
fixinteger-GPS fix type (see reference)
speedstringkm/hGround speed. "0.0" if stationary or no fix
temperatureinteger°CTemperature at the time of capture
headingintegerdegreesHeading (0-360°, 0 = North)
altitudeintegermAltitude above sea level
tagsobject-Human-readable version of the tag bitmask (see reference)

Elements: System event

An event element represents a system event that occurred on the tracker.

{
  "id": "elm_3AAKX0HlH7kOta90A7Ef5Mkwlq6",
  "type": "event",
  "datas": {
    "type": "event",
    "eventType": "power_off",
    "code": 2,
    "id": 9,
    "timestamp": 1772030960172,
    "precision": "MINUTE",
    "source": "GNSS"
  },
  "messageId": "msg_3AAKX57Y9c0KK5YuMxlAQhANjaS",
  "trackerId": "tra_35pBms5aEyRZnLVAG7u7ZdmcCTC",
  "createdAt": "2026-02-25T14:32:50.363+00:00"
}

Common fields (always present):

FieldTypeDescription
typestringAlways "event"
eventTypestringEvent type (see reference)
codeintegerUnique numeric event code
idintegerSequence number on the tracker
timestampintegerUnix timestamp in milliseconds
precisionstringTime precision (see reference)
sourcestringTime synchronization source

Optional fields (depending on event type):

FieldTypePresent forDescription
dataobjectevents with payload (shock_detection, battery_charging_*, sleep_scheduled, geofence_*, command_applied, config…)Event-specific structured payload (see dedicated section)
ptobjectevents with a GPS context (button_1/2/3, sos, user_ok)GPS position at the time of the event (see dedicated section)
latitude, longitude, accuracy, satellites, fixmixedsame events as ptLegacy duplicate of pt fields, flattened at the root of datas
For new integrations, prefer the pt object over the legacy flattened fields. They always carry the same values.

Event data payloads

The data object is only present for events that carry a specific payload. Its structure depends on eventType.


Event Shock

{
  "data": {
    "norm": 6869,
    "x": -3997,
    "y": 3995,
    "z": 3905,
    "axis": "X"
  }
}
FieldTypeUnitDescription
normintegermgPeak acceleration magnitude (sqrt(x²+y²+z²))
xintegermgPeak on X axis (signed)
yintegermgPeak on Y axis (signed)
zintegermgPeak on Z axis (signed)
axisstring-Axis with the largest peak ("X", "Y" or "Z")

Event Charging started · Charging stopped · Battery full · Battery low · Battery error

{
  "data": {
    "soc": 4.2,
    "voltage": 4173,
    "current": 777.8,
    "temp": 29
  }
}
FieldTypeUnitDescription
socnumber%State of charge (0.0 - 100.0)
voltageintegermVBattery voltage
currentnumbermACharge/discharge current (positive = charging, negative = discharging)
tempinteger°CBattery temperature

Event Scheduled sleep

{
  "data": {
    "wake_at": 1776947580
  }
}
FieldTypeUnitDescription
wake_atintegersecondsTarget Unix timestamp (UTC) when the tracker is programmed to wake up. The tracker enters deep sleep right after this event.

Event Zone entry · Zone exit · Zone overspeed

{
  "data": {
    "slot": 2,
    "name": "warehouse",
    "speed": 78
  }
}
FieldTypeUnitDescription
slotinteger-Zone slot index (0..N)
namestring-Zone name configured on the platform
speedintegerkm/h(overspeed only) Speed at the time of detection

Event Command applied

{
  "data": {
    "results": [
      { "cmd_id": 1, "status": 0 },
      { "cmd_id": 2, "status": 4 }
    ]
  }
}

Each entry contains cmd_id (FOTA command index) and status (NSE return code 0-5: 0 = fail, 1 = ok, 2 = unknown param, 3 = invalid value, 4 = out of range, 5 = bad format).


Event Configuration

{
  "data": "gnss --rate=30000\nsleep schedule --wake_at=0\n..."
}

data is a plain text string (CBOR tstr) containing the current NSE configuration as a series of shell-style commands. It may be split into multiple config events when long.

pt object (position context)

For button-related events (and any event configured to embed the current GPS position), a pt object is included alongside the legacy flattened fields:

{
  "pt": {
    "lat": 49.0088218,
    "lon": 2.2842179,
    "acc": 6,
    "sats": 20,
    "fix": 4
  },
  "latitude": 49.0088218,
  "longitude": 2.2842179,
  "accuracy": 6,
  "satellites": 20,
  "fix": 4
}
FieldTypeUnitDescription
latnumberdegreesWGS84 latitude (0 if no fix)
lonnumberdegreesWGS84 longitude (0 if no fix)
accintegermEstimated horizontal accuracy (may be 499 as a fallback when no fix)
satsinteger-Satellites used
fixinteger-GPS fix type (see reference)
No fix at press time — when the user presses a button without a GPS fix, all position fields are 0 (and acc may be 499 as a sentinel). The event is still delivered: only timestamp and eventType are reliable in this case.

Network operator

The networkOperator object provides information about the network operator used by the tracker at the time of sending.

{
  "networkOperator": {
    "mcc": 208,
    "mnc": 10,
    "region": "Europe",
    "country": "France",
    "iso": "FR",
    "operator": "Altice",
    "brand": "SFR"
  }
}
FieldTypeDescription
mccintegerMobile Country Code
mncintegerMobile Network Code
regionstringGeographic region
countrystringCountry name
isostringISO 3166-1 alpha-2 country code
operatorstringOperator group name
brandstringCommercial brand

Tracker state

The tracker object contains the full state of the tracker at the time the message was received.

{
  "tracker": {
    "id": "tra_35pBms5aEyRZnLVAG7u7ZdmcCTC",
    "serial": "90372",
    "alias": "0001",
    "createdAt": "2025-11-22T07:41:34.642+00:00",
    "updatedAt": "2026-02-24T07:19:41.923+00:00",
    "network": "gsm",
    "battery": 71,
    "temperature": 0,
    "mcc": 208,
    "mnc": 10,
    "rsrq": 61,
    "connected": true,
    "connectedAt": "2026-02-24T07:02:46.902+00:00",
    "sleep": false,
    "sleepAt": null,
    "charging": false,
    "chargingAt": null,
    "sentAt": "2026-02-24T07:19:41.908+00:00",
    "lastPointAt": "2026-02-24T07:19:22.000+00:00",
    "latitude": "45.4579552",
    "longitude": "4.4046063",
    "altitude": 443,
    "speed": 34,
    "configSentAt": null,
    "customerId": "cus_38wA1qzOeV4Y5CotZNCP15z4J5E",
    "configurationId": null,
    "notes": null,
    "zoneTrack": false
  }
}

Identity

FieldTypeDescription
idstringUnique tracker identifier (tra_*)
serialstringHardware serial number
aliasstringAssigned name / alias
createdAtstringRegistration date
updatedAtstringLast state update
customerIdstringOwning customer ID (cus_*)
configurationIdstring|nullApplied configuration ID (cfn_*)
notesstring|nullFree-form notes associated with the tracker

Network state

FieldTypeDescription
networkstringNetwork type
mccintegerMobile Country Code
mncintegerMobile Network Code
rsrqintegerSignal quality
connectedbooleanTracker currently connected
connectedAtstring|nullLast connection date

Battery and power

FieldTypeDescription
batteryintegerBattery level (0-100 %)
temperatureintegerInternal temperature (°C)
chargingbooleanTracker charging
chargingAtstring|nullLast charging state change

Sleep

FieldTypeDescription
sleepbooleanTracker in sleep mode
sleepAtstring|nullDate entered sleep mode

Last known position

FieldTypeDescription
latitudestring|nullLast latitude (null if never fixed)
longitudestring|nullLast longitude (null if never fixed)
altitudeintegerLast altitude (m)
speedintegerLast speed (km/h)
lastPointAtstringDate of the last recorded point

Communication

FieldTypeDescription
sentAtstringDate of the last transmission
configSentAtstring|nullDate of the last configuration push

Geofence tracking

FieldTypeDescription
zoneTrackbooleantrue if the tracker is currently inside one of the geofence zones it tracks
The latitude and longitude fields of the tracker object are of type string (unlike point elements where they are number). They can also be null if the tracker has never obtained a GPS fix.

tracker.updated — State update

This webhook is emitted only when a tracker's alias or notes are modified, from the Neyos platform or through the API. The payload contains the complete, up-to-date state of the tracker, not just the modified fields.

{
  "type": "tracker.updated",
  "data": {
    "id": "tra_3A2Mu74SLWWg8cnRtNewiBIPKs9",
    "serial": "90456",
    "alias": "198",
    "createdAt": "2026-02-22T18:53:51.615+00:00",
    "updatedAt": "2026-02-27T19:13:08.987+00:00",
    "network": "gsm",
    "battery": 64,
    "temperature": 0,
    "mcc": 208,
    "mnc": 10,
    "rsrq": 56,
    "connected": true,
    "connectedAt": "2026-02-27T14:46:19.174+00:00",
    "sleep": false,
    "sleepAt": null,
    "charging": false,
    "chargingAt": null,
    "sentAt": "2026-02-27T19:13:08.973+00:00",
    "lastPointAt": "2026-02-27T19:09:16.359+00:00",
    "latitude": null,
    "longitude": null,
    "altitude": 0,
    "speed": 0,
    "configSentAt": null,
    "customerId": "cus_2tBdSTgP8giQ5s7bNNhN2ZvFOzv",
    "configurationId": "cfn_3AGOZqTDYE3nnfw0EeM5yGUVUlW",
    "notes": null
  }
}

The data fields are identical to those of the tracker object in message.created (see section above).

Typical use: use tracker.updated to propagate tracker renamings made from the platform into your systems. To maintain a cache of the current state (battery, position, connectivity…), rely on the tracker object included in every message.created.

tracker.created — New tracker

Emitted when a tracker is attached to your organization. The data field contains the full tracker object, in the same format as in tracker.updated.

Until the tracker sends its first frame, most telemetry fields are null.

{
  "type": "tracker.created",
  "data": {
    "serial": "90456",
    "alias": null,
    "createdAt": "2026-02-22T18:53:51.615+00:00",
    "updatedAt": "2026-02-22T18:53:51.615+00:00",
    "network": "gsm",
    "battery": null,
    "temperature": null,
    "mcc": null,
    "mnc": null,
    "rsrq": null,
    "connected": false,
    "connectedAt": null,
    "sleep": false,
    "sleepAt": null,
    "charging": false,
    "chargingAt": null,
    "sentAt": null,
    "lastPointAt": null,
    "latitude": null,
    "longitude": null,
    "altitude": null,
    "speed": null,
    "configSentAt": null,
    "notes": null,
    "zoneTrack": false
  }
}

tracker.deleted — Tracker deleted

Emitted when a tracker is removed from your organization. The data field contains the last known state of the tracker, in the same format as above.

After this event, no further data will come in for that serial. Archive or deactivate the tracker in your systems rather than deleting its history.

tracker_action — Command life cycle

These three events track the commands sent to your trackers, whether they were created through the public API or from the Neyos platform.

On tracker_action.* events, the createdAt and updatedAt dates are Unix timestamps in milliseconds, not ISO 8601 strings as on tracker.* and message.created events.

tracker_action.created

Emitted as soon as a command is queued for a tracker. At this stage it has not been transmitted to the device yet: status is waiting, or waiting_config_loaded if the tracker configuration must be loaded first.

{
  "type": "tracker_action.created",
  "data": {
    "id": "tac_2fH3kLmN9pQrStUvWxYz01",
    "type": "custom_commands",
    "payload": {
      "commands": {
        "GPS_PERIOD": 300,
        "ACCEL_SENSITIVITY": 4
      }
    },
    "priority": 0,
    "status": "waiting",
    "response": null,
    "createdAt": 1772534400000,
    "updatedAt": 1772534400000,
    "serial": "90456"
  }
}

tracker_action.updated

Emitted on every state change of the command: this is the event to listen to in order to follow its life cycle. You will therefore receive several successive notifications carrying the same id.

Typical cycle: waiting → pending (transmitted to the device) → received (acknowledged) → success or failed.

{
  "type": "tracker_action.updated",
  "data": {
    "id": "tac_2fH3kLmN9pQrStUvWxYz01",
    "type": "custom_commands",
    "payload": {
      "commands": {
        "GPS_PERIOD": 300,
        "ACCEL_SENSITIVITY": 4
      }
    },
    "priority": 0,
    "status": "success",
    "response": {
      "GPS_PERIOD": "success",
      "ACCEL_SENSITIVITY": "success"
    },
    "createdAt": 1772534400000,
    "updatedAt": 1772534612000,
    "serial": "90456"
  }
}

The response field details the outcome parameter by parameter: a command can fail overall while part of its parameters were applied successfully.

tracker_action.deleted

Emitted when a command is deleted. The data field contains the last known state of the command, in the same format as above.

Possible values

type — nature of the command:

ValueDescription
custom_commandsWriting configuration parameters
load_configReading the tracker configuration
send_configSending a complete configuration
waiting_before_send_configTechnical wait before sending a configuration
power_offPowering the tracker off
time_syncClock synchronization

status — state of the command:

ValueDescription
waiting_config_loadedWaiting for the tracker configuration to be loaded
waitingQueued, not transmitted to the device yet
pendingTransmitted to the device, awaiting acknowledgement
receivedAcknowledgement received, processing in progress
successApplied successfully
failedFailed — see response for details
canceledCanceled before execution
mergedMerged with another command of the same type

response — an object { parameter_name: result }, null until an acknowledgement is received:

ResultMeaning
successParameter applied
failFailed to apply
unknown_paramParameter unknown to the tracker
invalid_valueValue rejected
out_of_rangeValue outside the allowed bounds
bad_formatIncorrect value format

Field reference

Expected response

Your server must respond to each webhook with:

  • HTTP code: 200 — this is the only success criterion
  • Response time: ideally < 1 second, maximum 5 seconds
HTTP/1.1 200 OK

The response body is not read: an empty 200 is enough. Any other code, as well as a timeout or an unreachable host, is logged as a failure.

In case of no response or error — the webhook may be replayed. Make sure your processing is idempotent (processing the same message twice must not create duplicates). Use the data.id field as the idempotency key.

Complete examples

Valid GPS point (tracker in motion)

{
  "type": "message.created",
  "data": {
    "id": "msg_3A6em6HE5ts0Ls1Vzq863IuD62x",
    "type": "datas",
    "network": "gsm",
    "mcc": 208,
    "mnc": 10,
    "rsrq": 61,
    "battery": 71,
    "temperature": 0,
    "receivedAt": "2026-02-24T07:20:02.545+00:00",
    "elements": [
      {
        "id": "elm_3A6em1aqCXEqx7mmrIowhAVaQq2",
        "type": "point",
        "datas": {
          "type": "point",
          "timestamp": 1771917582000,
          "tag": 9216,
          "source": "GNSS",
          "precision": "MS",
          "latitude": 45.4570617,
          "longitude": 4.4021235,
          "accuracy": 18,
          "satellites": 11,
          "fix": 4,
          "speed": "40.8",
          "temperature": 19,
          "heading": 230,
          "altitude": 448,
          "tags": {
            "type": "POINT",
            "status": "NEW",
            "location": "RAM",
            "priority": "NORMAL",
            "gps": "DISPO",
            "fix": "3D",
            "buffer": "LIVE"
          }
        },
        "messageId": "msg_3A6em6HE5ts0Ls1Vzq863IuD62x",
        "trackerId": "tra_35pBms5aEyRZnLVAG7u7ZdmcCTC",
        "createdAt": "2026-02-24T07:20:02.554+00:00"
      }
    ],
    "networkOperator": {
      "mcc": 208,
      "mnc": 10,
      "region": "Europe",
      "country": "France",
      "iso": "FR",
      "operator": "Altice",
      "brand": "SFR"
    },
    "tracker": {
      "id": "tra_35pBms5aEyRZnLVAG7u7ZdmcCTC",
      "serial": "90372",
      "alias": "0001",
      "battery": 71,
      "connected": true,
      "sleep": false,
      "latitude": "45.4579552",
      "longitude": "4.4046063",
      "altitude": 443,
      "speed": 34
    }
  }
}

Interpretation: Tracker 0001 (serial 90372) is traveling at 40.8 km/h, heading south-west (heading 230), at 448 m altitude, with 11 satellites and 18 m accuracy. Battery at 71 %.

System events (sleep + power off)

{
  "type": "message.created",
  "data": {
    "id": "msg_3AAKX57Y9c0KK5YuMxlAQhANjaS",
    "type": "datas",
    "network": "gsm",
    "battery": 70,
    "receivedAt": "2026-02-25T14:32:50.358+00:00",
    "elements": [
      {
        "type": "event",
        "datas": {
          "type": "event",
          "eventType": "deep_sleep_enter",
          "code": 22,
          "id": 8,
          "timestamp": 1772030676673,
          "precision": "MINUTE",
          "source": "GNSS"
        }
      },
      {
        "type": "event",
        "datas": {
          "type": "event",
          "eventType": "power_off",
          "code": 2,
          "id": 9,
          "timestamp": 1772030960172,
          "precision": "MINUTE",
          "source": "GNSS"
        }
      },
      {
        "type": "point",
        "datas": {
          "type": "point",
          "timestamp": 1772030966384,
          "tag": 0,
          "latitude": 0,
          "longitude": 0,
          "fix": 0,
          "satellites": 0,
          "speed": "0.0",
          "temperature": 25
        }
      }
    ]
  }
}

Interpretation: The tracker entered deep sleep (event 8), then powered off (event 9). The associated GPS point has no fix (lat/lon = 0, fix = 0) because GPS was not available at the time of shutdown.

Wake-up with a series of fix-less points

A tracker waking up from sleep often sends a series of points while it acquires a GPS fix. All these points will have fix: 0 and coordinates of 0.

{
  "elements": [
    {
      "type": "event",
      "datas": {
        "eventType": "sleep_leave",
        "code": 8,
        "id": 11,
        "timestamp": 1772219306752,
        "precision": "SECOND"
      }
    },
    { "type": "point", "datas": { "timestamp": 1772218605007, "fix": 0, "latitude": 0, "longitude": 0, "temperature": 27 } },
    { "type": "point", "datas": { "timestamp": 1772219307243, "fix": 0, "latitude": 0, "longitude": 0, "temperature": 24 } },
    { "type": "point", "datas": { "timestamp": 1772219308223, "fix": 0, "latitude": 0, "longitude": 0, "temperature": 24 } }
  ]
}

Interpretation: The tracker wakes up (sleep_leave). The points sent do not yet have a GPS fix (the GNSS receiver is still acquiring). These points should not be displayed on a map.

Filter out points with fix == 0 to display only valid positions.

Shock detection (with payload)

{
  "type": "event",
  "datas": {
    "type": "event",
    "eventType": "shock_detection",
    "code": 3,
    "id": 7,
    "timestamp": 1778140491769,
    "precision": "SECOND",
    "source": "GNSS",
    "data": {
      "norm": 6869,
      "x": -3997,
      "y": 3995,
      "z": 3905,
      "axis": "X"
    }
  }
}

Interpretation: A shock with a peak magnitude of 6869 mg was detected, primarily on the X axis.

Button press (with position)

{
  "type": "event",
  "datas": {
    "type": "event",
    "eventType": "button_3",
    "code": 31,
    "id": 6,
    "timestamp": 1778140445246,
    "precision": "SECOND",
    "source": "GNSS",
    "pt": {
      "lat": 0,
      "lon": 0,
      "acc": 499,
      "sats": 0,
      "fix": 0
    },
    "latitude": 0,
    "longitude": 0,
    "accuracy": 499,
    "satellites": 0,
    "fix": 0
  }
}

Interpretation: Button 3 was pressed. No GPS fix was available at press time (fix: 0, acc: 499 is the no-fix sentinel value).

Depending on platform configuration, the same physical press could appear as eventType: "sos" (code 6), "user_ok" (code 36), etc. The code and eventType fields always reflect the logical event mapped to the button.

Charging start (with payload)

{
  "type": "event",
  "datas": {
    "type": "event",
    "eventType": "battery_charging_start",
    "code": 24,
    "id": 2,
    "timestamp": 1778140272055,
    "precision": "SECOND",
    "source": "GNSS",
    "data": {
      "soc": 4.2,
      "voltage": 4173,
      "current": 777.8,
      "temp": 29
    }
  }
}

Interpretation: The charging cable was just plugged in. Battery at 4.2 % SoC, voltage 4173 mV, charging current +777.8 mA, battery temperature 29 °C. A battery_charging_stop event (code 25) is emitted when the cable is unplugged, with the same data structure.

Scheduled wake-up (with target timestamp)

{
  "type": "event",
  "datas": {
    "type": "event",
    "eventType": "sleep_scheduled",
    "code": 39,
    "id": 12,
    "timestamp": 1776947400,
    "precision": "SECOND",
    "source": "GNSS",
    "data": {
      "wake_at": 1776947580
    }
  }
}

Interpretation: At 2026-04-23 12:30:00 UTC (event timestamp) a scheduled wake-up was requested via the sleep schedule shell command. The tracker will wake up at 2026-04-23 12:33:00 UTC (wake_at). Right after this event, it enters deep sleep (GNSS off, modem off) and waits for the target time to fire a normal sleep_leave event upon wake-up.

Pairing — a sleep_scheduled event is always followed (after the deep sleep period) by a sleep_leave event when the device wakes up.

Integration guide

Receive POST webhook
  │
  ├─> Check Content-Type: application/json
  │
  ├─> Parse the JSON body
  │
  ├─> Read the "type" field
  │     │
  │     ├─> "message.created"
  │     │     │
  │     │     ├─> For each element in "elements":
  │     │     │     │
  │     │     │     ├─> If type == "point" AND fix >= 2:
  │     │     │     │     Store the valid GPS position
  │     │     │     │
  │     │     │     ├─> If type == "point" AND fix == 0:
  │     │     │     │     Ignore (no position) or store temperature only
  │     │     │     │
  │     │     │     └─> If type == "event":
  │     │     │           Process the system event
  │     │     │
  │     │     └─> Update the tracker state (battery, signal…)
  │     │
  │     └─> "tracker.updated"
  │           │
  │           └─> Update the cached alias / notes
  │
  └─> Respond HTTP 200

Point filtering rules

ConditionAction
fix == 0Ignore — no valid GPS position
fix == 2Use with caution — 2D position only (unreliable altitude)
fix >= 3Display — reliable position
accuracy > 100Flag as "low accuracy"
satellites < 4Potentially imprecise position

Timestamp conversion

The timestamp field is a Unix timestamp in milliseconds (not seconds).

const ts_ms = 1771917582000;
const date = new Date(ts_ms);
// -> 2026-02-24T17:19:42.000Z

Idempotency

Each message and element has a unique id. Use these identifiers to avoid duplicates.

LevelFieldFormatExample
Messagedata.idmsg_*msg_3AAKX57Y9c0KK5YuMxlAQhANjaS
Elementelements[].idelm_*elm_3A6em1aqCXEqx7mmrIowhAVaQq2
Trackertracker.idtra_*tra_35pBms5aEyRZnLVAG7u7ZdmcCTC
Customertracker.customerIdcus_*cus_38wA1qzOeV4Y5CotZNCP15z4J5E
Configurationtracker.configurationIdcfn_*cfn_3AGOZqTDYE3nnfw0EeM5yGUVUlW

Frequently asked questions

How do I know if a GPS point is valid?

Check that fix >= 2 AND latitude != 0 AND longitude != 0. A point with fix: 0 has no GPS position and should not be displayed on a map.

Why am I receiving points with latitude/longitude at 0?

The tracker sends points even without a GPS fix (for example right after waking up, or indoors). These points still contain the temperature and timestamp, but no valid position.

What is the difference between receivedAt and an element's timestamp?
  • receivedAt: the moment the cloud platform received the message from the tracker
  • timestamp (of an element): the moment the tracker captured the data (GPS point or event)

There can be a delay if the tracker was out of network coverage and buffered the data.

Why don't I receive a tracker.updated for every message?

That is the expected behaviour: tracker.updated is only emitted when a tracker's alias or notes are modified. The current state (battery, position, connectivity…) is carried by the tracker object present in every message.created — that is what you should use to maintain your cache.

How do I distinguish a "live" point from a buffered one?

If the tags object is present, check the buffer field:

  • "LIVE": point captured and sent in real time
  • "HISTORY": point stored in memory and sent later

If tags is absent (tag = 0), the point is generally a fix-less point (the live/history distinction is not relevant).

What happens if my server does not respond in time?

The webhook may be replayed. Make sure your processing is idempotent by using the id fields of the message and elements as deduplication keys.

Why is the speed field a string in points but an integer in the tracker?

This is intentional. In point elements, speed is a string with decimals ("40.8"). In the tracker object, speed is a rounded integer (34). Parse accordingly.

How do I interpret the heading field?

The heading is a bearing in degrees (0-360):

  • 0 = North
  • 90 = East
  • 180 = South
  • 270 = West

A heading of 230 means the tracker is moving south-west.

Can a single message contain both points and events?

Yes. The elements array can contain a mix of point and event in any order. A typical message after a wake-up can contain a sleep_leave event followed by several GPS points.

Which coordinate system is used for GPS positions?

All coordinates use the WGS84 datum (the worldwide standard for GPS systems). Latitude and longitude are expressed in decimal degrees.

Why does the same physical button report different eventType values across customers?

Each physical button (button_1, button_2, button_3) is mapped to a logical event on the platform side. A customer can configure button 1 as sos, button 2 as user_ok, button 3 as raw button_3, etc. The code and eventType fields always reflect the logical event, not the physical button index. To know which physical button was pressed regardless of mapping, look at the platform configuration of that tracker.

What is the difference between the pt object and the legacy latitude/longitude fields in an event?

For events emitted with a GPS context (button presses, SOS), the position is sent twice for backwards compatibility: once as a structured pt object (pt.lat, pt.lon, pt.acc, pt.sats, pt.fix) and once as flattened root fields of datas (latitude, longitude, accuracy, satellites, fix). Both carry the same values. Prefer the pt object for new integrations.

Why does an event sometimes contain a data object and sometimes not?

Events fall into 3 categories:

  1. Lifecycle events (power_on, power_off, sleep_enter, sleep_leave, deep_sleep_enter, gps_fix, gps_lost…): no payload, the eventType alone is meaningful.
  2. Telemetry events (shock_detection, battery_charging_start, geofence_in, sleep_scheduled…): include a data object with event-specific fields.
  3. Button-like events (button_1/2/3, sos, user_ok): include a pt object with the GPS position at press time. May or may not also include a data object depending on eventType.

The presence of data and pt is documented per event type in the "Event types" reference table (columns data / Position).

How do I know when a tracker will wake up from a scheduled deep sleep?

When a sleep_scheduled event (code 39) arrives, parse data.wake_at — it is the Unix timestamp in seconds (UTC) when the tracker is programmed to wake up. The tracker will be unreachable between the sleep_scheduled event and the next sleep_leave event (which fires at the actual wake-up, typically within ±30 seconds of wake_at due to the polling granularity).

What does zoneTrack mean in the tracker state?

zoneTrack: true indicates that the tracker is currently inside one of the geofence zones it tracks. Use it as a quick boolean signal in dashboards. For full details (which zone, when entered, etc.), rely on the geofence_in / geofence_out events.

Monitoring and diagnostics

The Webhook tab in the platform allows you to track the status of your integrations in real time (status, HTTP codes, latency, logs). Each webhook displays:

  • Activation status (Enabled / Disabled)
  • Event types listened to (message.created, tracker.updated)
  • HTTP status returned by your server (200, 400, 500…)
  • Average server response time
  • Date and type of the last send
  • Success or failure of recent sends

Best practices

To ensure a robust integration, we recommend:

  • Security: HTTPS mandatory, IP filtering recommended
  • Capacity: make sure your infrastructure can absorb the sends in case of heavy traffic
  • Asynchronous processing: respond quickly with HTTP 200 then process messages in the background (queue, worker)
  • Idempotency: use data.id as a unique key to avoid duplicates
  • Logs: archive received payloads to facilitate support and diagnostics