Webhook
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
Webhook & API
Plateforme Neyos
Votre application
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:
- Log in to the Neyos platform.
- Go to the Webhook tab
- Click on .
- Enter the target URL of your server, and click
Enabled - Your webhook is now running, data is redirected to the registered endpoint.
Your endpoint must
- Accept POST requests over HTTPS
- Parse the JSON body
- Respond HTTP 200
- 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
| Header | Description |
|---|---|
X-Neyos-Timestamp | Unix timestamp in seconds at the time of sending |
X-Neyos-Signature | HMAC-SHA256 signature, formatted as sha256=<hex> |
X-Neyos-Event-Id | Unique event identifier (evt_…), stable across retries |
Signature computation
payload = "{timestamp}.{rawBody}"
signature = HMAC-SHA256(payload, signingSecret)
header = "sha256=" + hex(signature)
timestamp— the value of theX-Neyos-Timestampheader, 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))
}
import hashlib
import hmac
import time
def verify_neyos_signature(raw_body: bytes, headers: dict, secret: str) -> bool:
timestamp = headers.get("x-neyos-timestamp")
sig_header = headers.get("x-neyos-signature")
if not timestamp or not sig_header:
return False
# Replay protection: 5-minute window
if abs(time.time() - int(timestamp)) > 300:
return False
signed = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig_header, f"sha256={expected}")
<?php
function verifyNeyosSignature(string $rawBody, array $headers, string $secret): bool
{
$timestamp = $headers['x-neyos-timestamp'] ?? null;
$sigHeader = $headers['x-neyos-signature'] ?? null;
if (!$timestamp || !$sigHeader) {
return false;
}
// Replay protection: 5-minute window
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
return hash_equals($sigHeader, 'sha256=' . $expected);
}
signingSecret.Webhook types
Each payload contains a type field at the root that identifies the type of webhook.
| Type | Description | Frequency |
|---|---|---|
message.created | New telemetry message (GPS points + events) | Each time the tracker sends data |
tracker.updated | A tracker's alias or notes are modified | On each modification |
tracker.created | A tracker is attached to your organization | Rare |
tracker.deleted | A tracker is deleted | Rare |
tracker_action.created | A command is queued for a tracker | Each time a command is created |
tracker_action.updated | A command changes state | Several times per command |
tracker_action.deleted | A command is deleted | Rare |
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:
| Field | Type | Description | Example |
|---|---|---|---|
id | string | Unique message identifier | msg_3AAKX57Y9c0KK5YuMxlAQhANjaS |
type | string | Always "datas" | datas |
network | string | Network type used | gsm |
mcc | integer | Mobile Country Code | 208 (France) |
mnc | integer | Mobile Network Code (operator) | 10 (SFR) |
rsrq | integer | Signal quality (30 low → 70 excellent) | 61 |
battery | integer | Battery level (0-100 %) | 70 |
temperature | integer | Internal temperature in °C | 0 |
receivedAt | string | Platform reception date/time (ISO 8601) | 2026-02-25T14:32:50.358+00:00 |
elements | array | Array of points and events | see sections below |
networkOperator | object | Network operator details | see dedicated section |
tracker | object | Full tracker state | see 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:
| Field | Type | Description |
|---|---|---|
id | string | Unique element identifier |
type | string | Always "point" |
messageId | string | Parent message ID |
trackerId | string | ID of the sending tracker |
createdAt | string | Platform-side creation date (ISO 8601) |
datas fields of a point:
| Field | Type | Unit | Description |
|---|---|---|---|
type | string | - | Always "point" |
timestamp | integer | ms | Unix timestamp in milliseconds |
tag | integer | - | Bitmask encoding of the metadata |
source | string | - | Position source ("GNSS") |
precision | string | - | Time precision (see reference) |
latitude | number | degrees | WGS84 latitude. 0 if no fix |
longitude | number | degrees | WGS84 longitude. 0 if no fix |
accuracy | integer | m | Estimated horizontal accuracy. 0 if no fix |
satellites | integer | - | Number of satellites used |
fix | integer | - | GPS fix type (see reference) |
speed | string | km/h | Ground speed. "0.0" if stationary or no fix |
temperature | integer | °C | Temperature at the time of capture |
heading | integer | degrees | Heading (0-360°, 0 = North) |
altitude | integer | m | Altitude above sea level |
tags | object | - | 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):
| Field | Type | Description |
|---|---|---|
type | string | Always "event" |
eventType | string | Event type (see reference) |
code | integer | Unique numeric event code |
id | integer | Sequence number on the tracker |
timestamp | integer | Unix timestamp in milliseconds |
precision | string | Time precision (see reference) |
source | string | Time synchronization source |
Optional fields (depending on event type):
| Field | Type | Present for | Description |
|---|---|---|---|
data | object | events with payload (shock_detection, battery_charging_*, sleep_scheduled, geofence_*, command_applied, config…) | Event-specific structured payload (see dedicated section) |
pt | object | events 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, fix | mixed | same events as pt | Legacy duplicate of pt fields, flattened at the root of datas |
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"
}
}
| Field | Type | Unit | Description |
|---|---|---|---|
norm | integer | mg | Peak acceleration magnitude (sqrt(x²+y²+z²)) |
x | integer | mg | Peak on X axis (signed) |
y | integer | mg | Peak on Y axis (signed) |
z | integer | mg | Peak on Z axis (signed) |
axis | string | - | 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
}
}
| Field | Type | Unit | Description |
|---|---|---|---|
soc | number | % | State of charge (0.0 - 100.0) |
voltage | integer | mV | Battery voltage |
current | number | mA | Charge/discharge current (positive = charging, negative = discharging) |
temp | integer | °C | Battery temperature |
Event Scheduled sleep
{
"data": {
"wake_at": 1776947580
}
}
| Field | Type | Unit | Description |
|---|---|---|---|
wake_at | integer | seconds | Target 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
}
}
| Field | Type | Unit | Description |
|---|---|---|---|
slot | integer | - | Zone slot index (0..N) |
name | string | - | Zone name configured on the platform |
speed | integer | km/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
}
| Field | Type | Unit | Description |
|---|---|---|---|
lat | number | degrees | WGS84 latitude (0 if no fix) |
lon | number | degrees | WGS84 longitude (0 if no fix) |
acc | integer | m | Estimated horizontal accuracy (may be 499 as a fallback when no fix) |
sats | integer | - | Satellites used |
fix | integer | - | GPS fix type (see reference) |
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"
}
}
| Field | Type | Description |
|---|---|---|
mcc | integer | Mobile Country Code |
mnc | integer | Mobile Network Code |
region | string | Geographic region |
country | string | Country name |
iso | string | ISO 3166-1 alpha-2 country code |
operator | string | Operator group name |
brand | string | Commercial 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
| Field | Type | Description |
|---|---|---|
id | string | Unique tracker identifier (tra_*) |
serial | string | Hardware serial number |
alias | string | Assigned name / alias |
createdAt | string | Registration date |
updatedAt | string | Last state update |
customerId | string | Owning customer ID (cus_*) |
configurationId | string|null | Applied configuration ID (cfn_*) |
notes | string|null | Free-form notes associated with the tracker |
Network state
| Field | Type | Description |
|---|---|---|
network | string | Network type |
mcc | integer | Mobile Country Code |
mnc | integer | Mobile Network Code |
rsrq | integer | Signal quality |
connected | boolean | Tracker currently connected |
connectedAt | string|null | Last connection date |
Battery and power
| Field | Type | Description |
|---|---|---|
battery | integer | Battery level (0-100 %) |
temperature | integer | Internal temperature (°C) |
charging | boolean | Tracker charging |
chargingAt | string|null | Last charging state change |
Sleep
| Field | Type | Description |
|---|---|---|
sleep | boolean | Tracker in sleep mode |
sleepAt | string|null | Date entered sleep mode |
Last known position
| Field | Type | Description |
|---|---|---|
latitude | string|null | Last latitude (null if never fixed) |
longitude | string|null | Last longitude (null if never fixed) |
altitude | integer | Last altitude (m) |
speed | integer | Last speed (km/h) |
lastPointAt | string | Date of the last recorded point |
Communication
| Field | Type | Description |
|---|---|---|
sentAt | string | Date of the last transmission |
configSentAt | string|null | Date of the last configuration push |
Geofence tracking
| Field | Type | Description |
|---|---|---|
zoneTrack | boolean | true if the tracker is currently inside one of the geofence zones it tracks |
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).
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.
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.
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"
}
}
{
"type": "tracker_action.updated",
"data": {
"id": "tac_2fH3kLmN9pQrStUvWxYz01",
"type": "custom_commands",
"payload": {
"commands": {
"GPS_PERIOD": 300,
"UNKNOWN_PARAM": 12
}
},
"priority": 0,
"status": "failed",
"response": {
"GPS_PERIOD": "success",
"UNKNOWN_PARAM": "unknown_param"
},
"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:
| Value | Description |
|---|---|
custom_commands | Writing configuration parameters |
load_config | Reading the tracker configuration |
send_config | Sending a complete configuration |
waiting_before_send_config | Technical wait before sending a configuration |
power_off | Powering the tracker off |
time_sync | Clock synchronization |
status — state of the command:
| Value | Description |
|---|---|
waiting_config_loaded | Waiting for the tracker configuration to be loaded |
waiting | Queued, not transmitted to the device yet |
pending | Transmitted to the device, awaiting acknowledgement |
received | Acknowledgement received, processing in progress |
success | Applied successfully |
failed | Failed — see response for details |
canceled | Canceled before execution |
merged | Merged with another command of the same type |
response — an object { parameter_name: result }, null until an acknowledgement is received:
| Result | Meaning |
|---|---|
success | Parameter applied |
fail | Failed to apply |
unknown_param | Parameter unknown to the tracker |
invalid_value | Value rejected |
out_of_range | Value outside the allowed bounds |
bad_format | Incorrect value format |
Field reference
The precision field indicates the precision of the associated timestamp.
| Value | Description | Precision |
|---|---|---|
"MS" | Millisecond | Millisecond-level timestamp. Clock synchronized via GNSS. |
"SECOND" | Second | Second-level timestamp. |
"MINUTE" | Minute | Minute-level timestamp. Internal clock (no recent GNSS sync). |
precision: "MS" has a very reliable timestamp (satellite-synchronized). A precision: "MINUTE" point has an approximate timestamp based on the tracker's internal clock.Events are grouped by category. The code field is the unique numeric identifier sent by the tracker; eventType is the human-readable label assigned by the platform. The data and Position columns indicate whether the event carries a specific payload (see dedicated section) and/or a pt GPS position object.
Power and lifecycle
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | power_on | 1 | no | no | Power-on (boot or auto-restart) |
| Event | power_off | 2 | no | no | Power-off (voluntary or automatic) |
Sleep / wake
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | sleep_enter | 7 | no | no | Entering light sleep (DODO) |
| Event | sleep_leave | 8 | no | no | Exiting sleep (activity resumed) |
| Event | deep_sleep_enter | 22 | no | no | Entering deep sleep (minimum power consumption) |
| Event | sleep_cancelled | 23 | no | no | A scheduled sleep was cancelled before entering |
| Event | sleep_scheduled | 39 | yes | no | Scheduled wake-up programmed (data: target UTC timestamp) |
Battery
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | battery_low | 9 | yes | no | Battery level below threshold |
| Event | battery_charging_start | 24 | yes | no | Charging started (data: SoC, voltage, current, temp) |
| Event | battery_charging_stop | 25 | yes | no | Charging stopped (data: SoC, voltage, current, temp) |
| Event | battery_full | 26 | yes | no | Battery fully charged (≥ 99 %) |
| Event | battery_error | 37 | yes | no | Battery hardware error (I2C fail or init failure) |
Motion / shock
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | shock_detection | 3 | yes | no | Shock detected by accelerometer (data: norm, x, y, z, axis) |
| Event | motion_start | 13 | no | no | Motion start detected |
| Event | motion_stop | 14 | no | no | Motion end detected |
| Event | tamper | 15 | yes | no | Tampering attempt detected (priority) |
User buttons
The 3 physical buttons (button_1, button_2, button_3) are configurable on the platform side. Depending on customer configuration, the same physical press can be reported under different eventType values: button_1/2/3, sos, user_ok, etc. The code reflects the logical event configured for that button, not the physical button index.
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | sos | 6 | yes | yes (pt) | SOS alert (priority) |
| Event | user_ok | 36 | no | yes (pt) | User check-in confirmation ("everything is fine") |
| Event | button_1 | 29 | no | yes (pt) | Physical button 1 press, raw event |
| Event | button_2 | 30 | no | yes (pt) | Physical button 2 press, raw event |
| Event | button_3 | 31 | no | yes (pt) | Physical button 3 press, raw event |
pt object inside datas, and duplicated at the root (latitude/longitude/accuracy/satellites/fix) for legacy compatibility. Without a GPS fix at press time, all position fields are 0 (and accuracy may carry a fallback value, e.g. 499).GPS
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | gps_fix | 20 | no | no | GPS fix acquired |
| Event | gps_lost | 21 | no | no | GPS fix lost |
| Event | ephem_acquired | 34 | no | no | Ephemerides acquired (sufficient threshold reached) |
| Event | sat_table_acquired | 35 | no | no | Full satellite almanac acquired |
Geofence
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | geofence_in | 11 | yes | no | Tracker entered a geofence zone |
| Event | geofence_out | 12 | yes | no | Tracker exited a geofence zone |
| Event | geofence_overspeed | 38 | yes | no | Speed limit exceeded inside a zone (data: slot, name, speed) |
Configuration / FOTA
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Event | command_received | 27 | no | no | FOTA command(s) received |
| Event | command_applied | 28 | yes | no | FOTA command(s) applied (data: per-command status codes) |
| Event | config | 32 | yes | no | Current device configuration snapshot (data: NSE config text in CBOR tstr) |
| Event | request_time_sync | 33 | no | no | Tracker requests an authoritative time sync from the server |
sleep_leave (wake up) → normal activity (sending points) → sleep_enter (light sleep) → deep_sleep_enter (deep sleep) → sleep_leave (next wake up). A sleep_scheduled is emitted right before deep sleep when the wake-up time was programmed via the sleep schedule shell command. power_off occurs on a complete shutdown.5 is reserved (legacy BUTTON_1). Codes above 39 are reserved for future use (max 50).The fix field indicates the type of fix obtained by the GNSS receiver.
| Value | Description | Reliability |
|---|---|---|
0 | No fix | Invalid position (lat/lon = 0). Do not display on map. |
2 | 2D Fix | Horizontal position only. Unreliable altitude. |
3 | 3D Fix | Full position (lat, lon, altitude). Good reliability. |
4 | 3D Fix + DGPS | 3D position with differential corrections. Best precision. |
fix >= 2. Prefer fix >= 3 for good precision.tag field (integer)
The tag field is an integer that encodes several point metadata as a bitmask. A value of 0 means "no metadata" (point without fix or basic point).
| Value | Meaning |
|---|---|
0 | No metadata (typically a point without fix) |
9216 | Point with full metadata (see tags object) |
tags object (optional)
When tag != 0, the tags object provides a decoded, human-readable version of the bitmask.
{
"tags": {
"type": "POINT",
"status": "NEW",
"location": "RAM",
"priority": "NORMAL",
"gps": "DISPO",
"fix": "3D",
"buffer": "LIVE"
}
}
| Field | Possible values | Description |
|---|---|---|
type | "POINT" | Element type |
status | "NEW" | Point status |
location | "RAM", "FLASH" | Storage before sending |
priority | "NORMAL", "HIGH" | Point priority |
gps | "DISPO", "NO_FIX" | GPS availability at the time of capture |
fix | "2D", "3D" | GPS fix type |
buffer | "LIVE", "HISTORY" | Real-time point or from historical buffer |
buffer: "LIVE" = point captured and sent in real time. "HISTORY" = point stored in memory (out of network coverage) and sent later.| Value | Description |
|---|---|
"GNSS" | Timestamp synchronized via the GNSS system (GPS/Galileo/GLONASS) |
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.
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.
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).
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.
sleep_scheduled event is always followed (after the deep sleep period) by a sleep_leave event when the device wakes up.Integration guide
Recommended processing algorithm
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
| Condition | Action |
|---|---|
fix == 0 | Ignore — no valid GPS position |
fix == 2 | Use with caution — 2D position only (unreliable altitude) |
fix >= 3 | Display — reliable position |
accuracy > 100 | Flag as "low accuracy" |
satellites < 4 | Potentially 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
from datetime import datetime, timezone
ts_ms = 1771917582000
dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
# -> 2026-02-24T17:19:42+00:00
$ts_ms = 1771917582000;
$date = (new DateTime())->setTimestamp($ts_ms / 1000);
// -> 2026-02-24 17:19:42
long ts_ms = 1771917582000;
var date = DateTimeOffset.FromUnixTimeMilliseconds(ts_ms);
// -> 2026-02-24T17:19:42.000+00:00
long tsMs = 1771917582000L;
Instant instant = Instant.ofEpochMilli(tsMs);
// -> 2026-02-24T17:19:42Z
Idempotency
Each message and element has a unique id. Use these identifiers to avoid duplicates.
| Level | Field | Format | Example |
|---|---|---|---|
| Message | data.id | msg_* | msg_3AAKX57Y9c0KK5YuMxlAQhANjaS |
| Element | elements[].id | elm_* | elm_3A6em1aqCXEqx7mmrIowhAVaQq2 |
| Tracker | tracker.id | tra_* | tra_35pBms5aEyRZnLVAG7u7ZdmcCTC |
| Customer | tracker.customerId | cus_* | cus_38wA1qzOeV4Y5CotZNCP15z4J5E |
| Configuration | tracker.configurationId | cfn_* | 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 trackertimestamp(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:
- Lifecycle events (
power_on,power_off,sleep_enter,sleep_leave,deep_sleep_enter,gps_fix,gps_lost…): no payload, theeventTypealone is meaningful. - Telemetry events (
shock_detection,battery_charging_start,geofence_in,sleep_scheduled…): include adataobject with event-specific fields. - Button-like events (
button_1/2/3,sos,user_ok): include aptobject with the GPS position at press time. May or may not also include adataobject depending oneventType.
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.idas a unique key to avoid duplicates - Logs: archive received payloads to facilitate support and diagnostics