Webhook
Principes de fonctionnement
Chaque balise Neyos OTRAC transmet ses données en temps réel vers votre serveur via des requêtes HTTP POST au format JSON. Chaque payload contient soit des données de télémétrie (points GPS, événements système), soit une mise à jour de l'état du tracker.
Flux de données
Serveur Neyos
Webhook & API
Plateforme Neyos
Votre application
Points clés
- Protocole : HTTPS POST uniquement
- Format : JSON (
Content-Type: application/json) - Authentification : URL unique par client
- Signature : chaque appel est signé en HMAC-SHA256 (voir Signature des notifications)
- Réponse attendue : HTTP 200 (le corps de la réponse n'est pas lu)
- Temps de réponse recommandé : < 5 secondes
Configuration pas à pas
La configuration des webhooks se fait directement depuis l'interface Neyos (onglet Webhooks). Voici la procédure type :
- Connectez-vous à la plateforme Neyos.
- Accédez à l'onglet Webhook
- Cliquez sur .
- Indiquez l'URL cible de votre serveur, et cliquez sur
Activé - Votre webhook est maintenant en marche, les données sont redirigées vers le endpoint enregistré.
Votre endpoint doit
- Accepter les requêtes POST en HTTPS
- Parser le body JSON
- Répondre HTTP 200
- Traiter la requête en moins de 5 secondes
Signature des notifications
Chaque appel webhook sortant est signé en HMAC-SHA256. Cette signature vous permet de vérifier que le payload provient bien de Neyos et qu'il n'a pas été altéré en chemin.
Le secret de signature (signingSecret) vous est retourné une seule fois, à la création du webhook.
En-têtes ajoutés
| En-tête | Description |
|---|---|
X-Neyos-Timestamp | Timestamp Unix en secondes au moment de l'envoi |
X-Neyos-Signature | Signature HMAC-SHA256, au format sha256=<hex> |
X-Neyos-Event-Id | Identifiant unique de l'événement (evt_…), stable entre les renvois |
Calcul de la signature
payload = "{timestamp}.{rawBody}"
signature = HMAC-SHA256(payload, signingSecret)
header = "sha256=" + hex(signature)
timestamp— la valeur de l'en-têteX-Neyos-Timestamp, en secondes ;rawBody— le corps de la requête tel que reçu, octet pour octet ;signingSecret— le secret retourné à la création du webhook.
Procédure de vérification
Lire le corps brut avant tout parsing JSON
Re-sérialiser un objet JSON déjà parsé modifie les espaces et l'ordre des clés, et invalide la signature.
Contrôler le timestamp
Rejetez la requête si |maintenant - timestamp| > 300 secondes. Cette fenêtre protège contre le rejeu d'une notification interceptée.
Recalculer le HMAC
Avec votre signingSecret et la chaîne {timestamp}.{rawBody}.
Comparer en temps constant
Utilisez une comparaison de type timingSafeEqual plutôt qu'une égalité de chaînes.
Dédupliquer
Conservez les X-Neyos-Event-Id déjà traités pour rester idempotent face aux renvois.
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
// Anti-rejeu : fenêtre de 5 minutes
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
# Anti-rejeu : fenêtre de 5 minutes
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;
}
// Anti-rejeu : fenêtre de 5 minutes
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
return hash_equals($sigHeader, 'sha256=' . $expected);
}
signingSecret.Types de webhooks
Chaque payload contient un champ type à la racine qui identifie le type de webhook.
| Type | Description | Fréquence |
|---|---|---|
message.created | Nouveau message de télémétrie (points GPS + événements) | À chaque envoi par la balise |
tracker.updated | Modification de l'alias ou des notes d'un tracker | À chaque modification |
tracker.created | Un tracker est rattaché à votre organisation | Rare |
tracker.deleted | Un tracker est supprimé | Rare |
tracker_action.created | Une commande est mise en file pour un tracker | À chaque commande créée |
tracker_action.updated | Une commande change d'état | Plusieurs fois par commande |
tracker_action.deleted | Une commande est supprimée | Rare |
tracker.updated n'est pas émis à chaque nouvelle position ou variation de batterie : il est volontairement limité aux modifications de l'alias et des notes, depuis la plateforme ou l'API. Pour suivre l'activité terrain d'une balise, écoutez message.created.message.created — Données de télémétrie
Ce webhook est le principal. Il contient les données collectées par la balise : positions GPS, événements système, et le contexte réseau/tracker au moment de la réception.
Structure racine
{
"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": { ... }
}
}
Champs data :
| Champ | Type | Description | Exemple |
|---|---|---|---|
id | string | Identifiant unique du message | msg_3AAKX57Y9c0KK5YuMxlAQhANjaS |
type | string | Toujours "datas" | datas |
network | string | Type de réseau utilisé | gsm |
mcc | integer | Mobile Country Code (pays) | 208 (France) |
mnc | integer | Mobile Network Code (opérateur) | 10 (SFR) |
rsrq | integer | Qualité du signal (30 faible → 70 excellent) | 61 |
battery | integer | Niveau de batterie (0-100 %) | 70 |
temperature | integer | Température interne en °C | 0 |
receivedAt | string | Date/heure de réception plateforme (ISO 8601) | 2026-02-25T14:32:50.358+00:00 |
elements | array | Tableau de points et événements | voir sections ci-dessous |
networkOperator | object | Détails opérateur réseau | voir section dédiée |
tracker | object | État complet du tracker | voir section dédiée |
Elements : Point GPS
Un élément point représente une position GPS horodatée capturée par la balise à un instant donné.
{
"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"
}
Champs racine de l'élément point :
| Champ | Type | Description |
|---|---|---|
id | string | Identifiant unique de l'élément |
type | string | Toujours "point" |
messageId | string | ID du message parent |
trackerId | string | ID du tracker émetteur |
createdAt | string | Date de création côté plateforme (ISO 8601) |
Champs datas d'un point :
| Champ | Type | Unité | Description |
|---|---|---|---|
type | string | - | Toujours "point" |
timestamp | integer | ms | Timestamp Unix en millisecondes |
tag | integer | - | Encodage bitmask des métadonnées |
source | string | - | Source de la position ("GNSS") |
precision | string | - | Précision temporelle (voir référence) |
latitude | number | degrés | Latitude WGS84. 0 si pas de fix |
longitude | number | degrés | Longitude WGS84. 0 si pas de fix |
accuracy | integer | m | Précision horizontale estimée. 0 si pas de fix |
satellites | integer | - | Nombre de satellites utilisés |
fix | integer | - | Type de fix GPS (voir référence) |
speed | string | km/h | Vitesse au sol. "0.0" si immobile ou pas de fix |
temperature | integer | °C | Température au moment de la capture |
heading | integer | degrés | Direction (0-360°, 0 = Nord) |
altitude | integer | m | Altitude au-dessus du niveau de la mer |
tags | object | - | Version lisible du bitmask tag (voir référence) |
Elements : Événement système
Un élément event représente un événement système survenu sur la balise.
{
"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"
}
Champs communs (toujours présents) :
| Champ | Type | Description |
|---|---|---|
type | string | Toujours "event" |
eventType | string | Type de l'événement (voir référence) |
code | integer | Code numérique unique de l'événement |
id | integer | Numéro de séquence sur la balise |
timestamp | integer | Timestamp Unix en millisecondes |
precision | string | Précision temporelle (voir référence) |
source | string | Source de synchronisation temporelle |
Champs optionnels (selon le type d'événement) :
| Champ | Type | Présent pour | Description |
|---|---|---|---|
data | object | événements avec payload (shock_detection, battery_charging_*, sleep_scheduled, geofence_*, command_applied, config…) | Payload structuré spécifique au type d'événement (voir section dédiée) |
pt | object | événements émis avec un contexte GPS (button_1/2/3, sos, user_ok) | Position GPS au moment de l'événement (voir section dédiée) |
latitude, longitude, accuracy, satellites, fix | mixte | mêmes événements que pt | Duplication legacy des champs pt, aplatie à la racine de datas |
pt plutôt que les champs legacy aplatis. Les deux portent toujours les mêmes valeurs.Payloads data par événement
L'objet data est uniquement présent pour les événements qui transportent un payload spécifique. Sa structure dépend du eventType.
Événement Choc
{
"data": {
"norm": 6869,
"x": -3997,
"y": 3995,
"z": 3905,
"axis": "X"
}
}
| Champ | Type | Unité | Description |
|---|---|---|---|
norm | integer | mg | Magnitude crête de l'accélération (sqrt(x²+y²+z²)) |
x | integer | mg | Crête sur l'axe X (signée) |
y | integer | mg | Crête sur l'axe Y (signée) |
z | integer | mg | Crête sur l'axe Z (signée) |
axis | string | - | Axe portant la plus grande crête ("X", "Y" ou "Z") |
Événement Début de charge · Fin de charge · Batterie chargée · Batterie faible · Erreur batterie
{
"data": {
"soc": 4.2,
"voltage": 4173,
"current": 777.8,
"temp": 29
}
}
| Champ | Type | Unité | Description |
|---|---|---|---|
soc | number | % | État de charge (0.0 - 100.0) |
voltage | integer | mV | Tension batterie |
current | number | mA | Courant de charge/décharge (positif = charge, négatif = décharge) |
temp | integer | °C | Température batterie |
Événement Veille programmée
{
"data": {
"wake_at": 1776947580
}
}
| Champ | Type | Unité | Description |
|---|---|---|---|
wake_at | integer | secondes | Timestamp Unix UTC cible auquel la balise est programmée pour se réveiller. La balise entre en veille profonde juste après cet événement. |
Événement Entrée de zone · Sortie de zone · Excès de vitesse en zone
{
"data": {
"slot": 2,
"name": "warehouse",
"speed": 78
}
}
| Champ | Type | Unité | Description |
|---|---|---|---|
slot | integer | - | Index du slot de zone (0..N) |
name | string | - | Nom de la zone configuré sur la plateforme |
speed | integer | km/h | (overspeed uniquement) Vitesse au moment de la détection |
Événement Commande appliquée
{
"data": {
"results": [
{ "cmd_id": 1, "status": 0 },
{ "cmd_id": 2, "status": 4 }
]
}
}
Chaque entrée contient cmd_id (index de commande FOTA) et status (code retour NSE 0-5 : 0 = échec, 1 = ok, 2 = paramètre inconnu, 3 = valeur invalide, 4 = hors plage, 5 = mauvais format).
Événement Configuration
{
"data": "gnss --rate=30000\nsleep schedule --wake_at=0\n..."
}
data est ici une chaîne texte (CBOR tstr) contenant la configuration NSE courante sous forme de commandes shell. Peut être réparti sur plusieurs événements config si la configuration est longue.
Objet pt (contexte position)
Pour les événements liés aux boutons (et tout événement configuré pour embarquer la position GPS courante), un objet pt est inclus en plus des champs legacy aplatis :
{
"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
}
| Champ | Type | Unité | Description |
|---|---|---|---|
lat | number | degrés | Latitude WGS84 (0 si pas de fix) |
lon | number | degrés | Longitude WGS84 (0 si pas de fix) |
acc | integer | m | Précision horizontale estimée (peut valoir 499 en valeur de repli sans fix) |
sats | integer | - | Nombre de satellites utilisés |
fix | integer | - | Type de fix GPS (voir référence) |
0 (et acc peut valoir 499 comme sentinelle). L'événement est tout de même livré : seuls timestamp et eventType sont alors fiables.Opérateur réseau
L'objet networkOperator fournit les informations sur l'opérateur réseau utilisé par la balise au moment de l'envoi.
{
"networkOperator": {
"mcc": 208,
"mnc": 10,
"region": "Europe",
"country": "France",
"iso": "FR",
"operator": "Altice",
"brand": "SFR"
}
}
| Champ | Type | Description |
|---|---|---|
mcc | integer | Mobile Country Code |
mnc | integer | Mobile Network Code |
region | string | Région géographique |
country | string | Nom du pays |
iso | string | Code pays ISO 3166-1 alpha-2 |
operator | string | Nom du groupe opérateur |
brand | string | Marque commerciale |
État du tracker
L'objet tracker contient l'état complet de la balise au moment de la réception du message.
{
"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
}
}
Identité
| Champ | Type | Description |
|---|---|---|
id | string | Identifiant unique du tracker (tra_*) |
serial | string | Numéro de série matériel |
alias | string | Nom / alias assigné |
createdAt | string | Date d'enregistrement |
updatedAt | string | Dernière mise à jour d'état |
customerId | string | ID client propriétaire (cus_*) |
configurationId | string|null | ID configuration appliquée (cfn_*) |
notes | string|null | Notes libres associées au tracker |
État réseau
| Champ | Type | Description |
|---|---|---|
network | string | Type de réseau |
mcc | integer | Mobile Country Code |
mnc | integer | Mobile Network Code |
rsrq | integer | Qualité du signal |
connected | boolean | Tracker actuellement connecté |
connectedAt | string|null | Date de dernière connexion |
Batterie et alimentation
| Champ | Type | Description |
|---|---|---|
battery | integer | Niveau batterie (0-100 %) |
temperature | integer | Température interne (°C) |
charging | boolean | Tracker en charge |
chargingAt | string|null | Dernier changement d'état charge |
Veille
| Champ | Type | Description |
|---|---|---|
sleep | boolean | Tracker en mode veille |
sleepAt | string|null | Date de passage en veille |
Dernière position connue
| Champ | Type | Description |
|---|---|---|
latitude | string|null | Dernière latitude (null si jamais fixée) |
longitude | string|null | Dernière longitude (null si jamais fixée) |
altitude | integer | Dernière altitude (m) |
speed | integer | Dernière vitesse (km/h) |
lastPointAt | string | Date du dernier point enregistré |
Communication
| Champ | Type | Description |
|---|---|---|
sentAt | string | Date de la dernière transmission |
configSentAt | string|null | Date du dernier push de configuration |
Suivi de zones
| Champ | Type | Description |
|---|---|---|
zoneTrack | boolean | true si la balise est actuellement à l'intérieur d'une zone géofence suivie |
latitude et longitude de l'objet tracker sont de type string (contrairement aux éléments point où ce sont des number). Ils peuvent aussi être null si le tracker n'a jamais obtenu de fix GPS.tracker.updated — Mise à jour d'état
Ce webhook est émis uniquement quand l'alias ou les notes d'un tracker sont modifiés, depuis la plateforme Neyos ou via l'API. Le payload contient l'état complet et à jour du tracker, pas seulement les champs modifiés.
{
"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
}
}
Les champs data sont identiques à ceux de l'objet tracker dans message.created (voir section ci-dessus).
tracker.updated pour répercuter dans vos systèmes les renommages de balises effectués depuis la plateforme. Pour maintenir un cache de l'état courant (batterie, position, connectivité…), appuyez-vous sur l'objet tracker inclus dans chaque message.created.tracker.created — Nouveau tracker
Émis quand un tracker est rattaché à votre organisation. Le champ data contient l'objet tracker complet, au même format que dans tracker.updated.
Tant que la balise n'a pas émis sa première trame, la plupart des champs de télémétrie sont à 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 supprimé
Émis quand un tracker est retiré de votre organisation. Le champ data contient le dernier état connu du tracker, au même format que ci-dessus.
serial. Pensez à archiver ou à désactiver la balise côté vos systèmes plutôt qu'à supprimer son historique.tracker_action — Cycle de vie des commandes
Ces trois événements suivent les commandes envoyées à vos balises, qu'elles soient créées depuis l'API publique ou depuis la plateforme Neyos.
tracker_action.*, les dates createdAt et updatedAt sont des timestamps Unix en millisecondes, et non des chaînes ISO 8601 comme sur les événements tracker.* et message.created.tracker_action.created
Émis dès qu'une commande est mise en file pour une balise. À ce stade elle n'a pas encore été transmise au device : status vaut waiting, ou waiting_config_loaded si la configuration de la balise doit d'abord être chargée.
{
"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
Émis à chaque changement d'état de la commande : c'est l'événement à écouter pour suivre son cycle de vie. Vous recevrez donc plusieurs notifications successives portant le même id.
Cycle typique : waiting → pending (transmise au device) → received (accusée) → success ou 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"
}
}
Le champ response détaille le résultat paramètre par paramètre : une commande peut échouer globalement alors qu'une partie des paramètres a bien été appliquée.
tracker_action.deleted
Émis quand une commande est supprimée. Le champ data contient le dernier état connu de la commande, au même format que ci-dessus.
Valeurs possibles
type — nature de la commande :
| Valeur | Description |
|---|---|
custom_commands | Écriture de paramètres de configuration |
load_config | Lecture de la configuration de la balise |
send_config | Envoi d'une configuration complète |
waiting_before_send_config | Attente technique avant envoi de configuration |
power_off | Extinction de la balise |
time_sync | Synchronisation de l'horloge |
status — état de la commande :
| Valeur | Description |
|---|---|
waiting_config_loaded | En attente du chargement de la configuration de la balise |
waiting | En file, pas encore transmise au device |
pending | Transmise au device, en attente d'accusé |
received | Accusé de réception reçu, traitement en cours |
success | Appliquée avec succès |
failed | Échec — voir response pour le détail |
canceled | Annulée avant exécution |
merged | Fusionnée avec une autre commande du même type |
response — objet { nom_du_paramètre: résultat }, null tant qu'aucun accusé n'a été reçu :
| Résultat | Signification |
|---|---|
success | Paramètre appliqué |
fail | Échec d'application |
unknown_param | Paramètre inconnu de la balise |
invalid_value | Valeur refusée |
out_of_range | Valeur hors des bornes autorisées |
bad_format | Format de valeur incorrect |
Référence des champs
Le champ precision indique la précision du timestamp associé.
| Valeur | Description | Précision |
|---|---|---|
"MS" | Milliseconde | Timestamp à la milliseconde. Horloge synchronisée via GNSS. |
"SECOND" | Seconde | Timestamp à la seconde. |
"MINUTE" | Minute | Timestamp à la minute. Horloge interne (pas de sync GNSS récente). |
precision: "MS" a un timestamp très fiable (synchronisé satellite). Un point precision: "MINUTE" a un timestamp approximatif basé sur l'horloge interne de la balise.Les événements sont regroupés par catégorie. Le champ code est l'identifiant numérique unique envoyé par la balise ; eventType est le label lisible attribué par la plateforme. Les colonnes data et Position indiquent si l'événement transporte un payload spécifique (voir section dédiée) et/ou un objet pt de position GPS.
Alimentation et cycle de vie
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | power_on | 1 | non | non | Mise sous tension (boot ou redémarrage automatique) |
| Événement | power_off | 2 | non | non | Extinction (volontaire ou automatique) |
Veille / réveil
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | sleep_enter | 7 | non | non | Passage en veille légère (DODO) |
| Événement | sleep_leave | 8 | non | non | Sortie de veille (reprise d'activité) |
| Événement | deep_sleep_enter | 22 | non | non | Passage en veille profonde (consommation minimale) |
| Événement | sleep_cancelled | 23 | non | non | Une mise en veille programmée a été annulée avant exécution |
| Événement | sleep_scheduled | 39 | oui | non | Réveil programmé (data : timestamp UTC cible) |
Batterie
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | battery_low | 9 | oui | non | Niveau de batterie sous le seuil |
| Événement | battery_charging_start | 24 | oui | non | Début de charge (data : SoC, voltage, courant, temp) |
| Événement | battery_charging_stop | 25 | oui | non | Fin de charge (data : SoC, voltage, courant, temp) |
| Événement | battery_full | 26 | oui | non | Batterie pleine (≥ 99 %) |
| Événement | battery_error | 37 | oui | non | Erreur matérielle batterie (échec I2C ou init) |
Mouvement / choc
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | shock_detection | 3 | oui | non | Choc détecté par l'accéléromètre (data : norm, x, y, z, axis) |
| Événement | motion_start | 13 | non | non | Début de mouvement détecté |
| Événement | motion_stop | 14 | non | non | Fin de mouvement détectée |
| Événement | tamper | 15 | oui | non | Tentative d'altération détectée (priorité) |
Boutons utilisateur
Les 3 boutons physiques (button_1, button_2, button_3) sont configurables côté plateforme. Selon la configuration client, un même appui physique peut être remonté sous différents eventType : button_1/2/3, sos, user_ok, etc. Le code reflète l'événement logique configuré, pas l'index physique du bouton.
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | sos | 6 | oui | oui (pt) | Alerte SOS (priorité) |
| Événement | user_ok | 36 | non | oui (pt) | Confirmation utilisateur ("tout va bien") |
| Événement | button_1 | 29 | non | oui (pt) | Appui bouton physique 1, événement brut |
| Événement | button_2 | 30 | non | oui (pt) | Appui bouton physique 2, événement brut |
| Événement | button_3 | 31 | non | oui (pt) | Appui bouton physique 3, événement brut |
pt à l'intérieur de datas, et dupliquée à la racine (latitude/longitude/accuracy/satellites/fix) pour la compatibilité legacy. Sans fix GPS au moment de l'appui, tous les champs valent 0 (et accuracy peut valoir 499 comme sentinelle).GPS
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | gps_fix | 20 | non | non | Fix GPS acquis |
| Événement | gps_lost | 21 | non | non | Fix GPS perdu |
| Événement | ephem_acquired | 34 | non | non | Éphémérides acquises (seuil suffisant atteint) |
| Événement | sat_table_acquired | 35 | non | non | Almanach satellite complet acquis |
Géofence
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | geofence_in | 11 | oui | non | Entrée dans une zone géofence |
| Événement | geofence_out | 12 | oui | non | Sortie d'une zone géofence |
| Événement | geofence_overspeed | 38 | oui | non | Dépassement de vitesse à l'intérieur d'une zone (data : slot, name, speed) |
Configuration / FOTA
| Badge | eventType | Code | data | Position | Description |
|---|---|---|---|---|---|
| Événement | command_received | 27 | non | non | Commande(s) FOTA reçue(s) |
| Événement | command_applied | 28 | oui | non | Commande(s) FOTA appliquée(s) (data : codes de retour par commande) |
| Événement | config | 32 | oui | non | Snapshot de la configuration courante (data : texte NSE en CBOR tstr) |
| Événement | request_time_sync | 33 | non | non | La balise demande une synchronisation horaire au serveur |
sleep_leave (réveil) → activité normale (envoi de points) → sleep_enter (veille légère) → deep_sleep_enter (veille profonde) → sleep_leave (réveil suivant). Un sleep_scheduled est émis juste avant l'entrée en veille profonde quand le réveil a été programmé via la commande shell sleep schedule. power_off survient lors d'une extinction complète.5 est réservé (legacy BUTTON_1). Les codes au-delà de 39 sont réservés pour usage futur (max 50).Le champ fix indique le type de fix obtenu par le récepteur GNSS.
| Valeur | Description | Fiabilité |
|---|---|---|
0 | Pas de fix | Position invalide (lat/lon = 0). Ne pas afficher sur carte. |
2 | Fix 2D | Position horizontale uniquement. Altitude peu fiable. |
3 | Fix 3D | Position complète (lat, lon, altitude). Bonne fiabilité. |
4 | Fix 3D + DGPS | Position 3D avec corrections différentielles. Meilleure précision. |
fix >= 2. Privilégiez fix >= 3 pour une bonne précision.Champ tag (integer)
Le champ tag est un entier qui encode plusieurs métadonnées du point sous forme de bitmask. Une valeur de 0 signifie « aucune métadonnée » (point sans fix ou point basique).
| Valeur | Signification |
|---|---|
0 | Aucune métadonnée (typiquement un point sans fix) |
9216 | Point avec métadonnées complètes (voir objet tags) |
Objet tags (optionnel)
Quand tag != 0, l'objet tags fournit une version décodée, lisible du bitmask.
{
"tags": {
"type": "POINT",
"status": "NEW",
"location": "RAM",
"priority": "NORMAL",
"gps": "DISPO",
"fix": "3D",
"buffer": "LIVE"
}
}
| Champ | Valeurs possibles | Description |
|---|---|---|
type | "POINT" | Type d'élément |
status | "NEW" | Statut du point |
location | "RAM", "FLASH" | Stockage avant envoi |
priority | "NORMAL", "HIGH" | Priorité du point |
gps | "DISPO", "NO_FIX" | Disponibilité GPS au moment de la capture |
fix | "2D", "3D" | Type de fix GPS |
buffer | "LIVE", "HISTORY" | Point temps réel ou du buffer historique |
buffer : "LIVE" = point capturé et envoyé en temps réel. "HISTORY" = point stocké en mémoire (hors couverture réseau) et envoyé plus tard.| Valeur | Description |
|---|---|
"GNSS" | Timestamp synchronisé via le système GNSS (GPS/Galileo/GLONASS) |
Réponse attendue
Votre serveur doit répondre à chaque webhook avec :
- Code HTTP :
200— c'est le seul critère de succès - Temps de réponse : idéalement < 1 seconde, maximum 5 secondes
HTTP/1.1 200 OK
Le corps de la réponse n'est pas lu : un 200 sans contenu suffit. Tout autre code, ainsi qu'un timeout ou un hôte injoignable, est consigné comme un échec.
data.id comme clé d'idempotence.Exemples complets
Point GPS valide (balise en mouvement)
{
"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
}
}
}
Interprétation : Tracker 0001 (serial 90372) roule à 40,8 km/h, cap sud-ouest (heading 230), à 448 m d'altitude, avec 11 satellites et 18 m de précision. Batterie à 71 %.
Événements système (veille + extinction)
{
"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
}
}
]
}
}
Interprétation : La balise est entrée en veille profonde (event 8), puis s'est éteinte (event 9). Le point GPS associé n'a pas de fix (lat/lon = 0, fix = 0) car le GPS n'était pas disponible au moment de l'extinction.
Réveil avec succession de points sans fix
Une balise qui sort de veille envoie souvent une succession de points le temps d'acquérir un fix GPS. Tous ces points auront fix: 0 et des coordonnées à 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 } }
]
}
Interprétation : La balise se réveille (sleep_leave). Les points envoyés n'ont pas encore de fix GPS (le récepteur GNSS est en cours d'acquisition). Ces points ne doivent pas être affichés sur une carte.
fix == 0 pour n'afficher que les positions valides.Détection de choc (avec 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"
}
}
}
Interprétation : Un choc de magnitude crête 6869 mg a été détecté, principalement sur l'axe X.
Appui bouton (avec 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
}
}
Interprétation : Le bouton 3 a été pressé. Aucun fix GPS n'était disponible au moment de l'appui (fix: 0, acc: 499 est la valeur sentinelle "no-fix").
eventType: "sos" (code 6), "user_ok" (code 36), etc. Les champs code et eventType reflètent toujours l'événement logique mappé sur le bouton.Début de charge (avec 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
}
}
}
Interprétation : Le câble de charge vient d'être branché. Batterie à 4,2 % SoC, tension 4173 mV, courant de charge +777,8 mA, température batterie 29 °C. Un événement battery_charging_stop (code 25) sera émis au débranchement, avec la même structure data.
Réveil programmé (avec timestamp cible)
{
"type": "event",
"datas": {
"type": "event",
"eventType": "sleep_scheduled",
"code": 39,
"id": 12,
"timestamp": 1776947400,
"precision": "SECOND",
"source": "GNSS",
"data": {
"wake_at": 1776947580
}
}
}
Interprétation : À 2026-04-23 12:30:00 UTC (timestamp de l'événement), un réveil programmé a été demandé via la commande shell sleep schedule. La balise se réveillera à 2026-04-23 12:33:00 UTC (wake_at). Juste après cet événement, elle entre en veille profonde (GNSS éteint, modem éteint) et attend l'heure cible pour émettre un sleep_leave au réveil.
sleep_scheduled est toujours suivi (après la période de veille profonde) d'un sleep_leave au moment du réveil effectif.Guide d'intégration
Algorithme de traitement recommandé
Réception POST webhook
│
├─> Vérifier Content-Type: application/json
│
├─> Parser le body JSON
│
├─> Lire le champ "type"
│ │
│ ├─> "message.created"
│ │ │
│ │ ├─> Pour chaque element dans "elements" :
│ │ │ │
│ │ │ ├─> Si type == "point" ET fix >= 2 :
│ │ │ │ Stocker la position GPS valide
│ │ │ │
│ │ │ ├─> Si type == "point" ET fix == 0 :
│ │ │ │ Ignorer (pas de position) ou stocker température seule
│ │ │ │
│ │ │ └─> Si type == "event" :
│ │ │ Traiter l'événement système
│ │ │
│ │ └─> Mettre à jour l'état du tracker (batterie, signal…)
│ │
│ └─> "tracker.updated"
│ │
│ └─> Mettre à jour l'alias / les notes en cache
│
└─> Répondre HTTP 200
Règles de filtrage des points
| Condition | Action |
|---|---|
fix == 0 | Ignorer — pas de position GPS valide |
fix == 2 | À utiliser avec précaution — position 2D seulement (altitude peu fiable) |
fix >= 3 | Afficher — position fiable |
accuracy > 100 | Marquer "faible précision" |
satellites < 4 | Position potentiellement imprécise |
Conversion des timestamps
Le champ timestamp est un timestamp Unix en millisecondes (pas en secondes).
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
Idempotence
Chaque message et élément possède un id unique. Utilisez ces identifiants pour éviter les doublons.
| Niveau | Champ | Format | Exemple |
|---|---|---|---|
| Message | data.id | msg_* | msg_3AAKX57Y9c0KK5YuMxlAQhANjaS |
| Élément | elements[].id | elm_* | elm_3A6em1aqCXEqx7mmrIowhAVaQq2 |
| Tracker | tracker.id | tra_* | tra_35pBms5aEyRZnLVAG7u7ZdmcCTC |
| Client | tracker.customerId | cus_* | cus_38wA1qzOeV4Y5CotZNCP15z4J5E |
| Configuration | tracker.configurationId | cfn_* | cfn_3AGOZqTDYE3nnfw0EeM5yGUVUlW |
Questions fréquentes
Comment savoir si un point GPS est valide ?
Vérifiez que fix >= 2 ET latitude != 0 ET longitude != 0. Un point avec fix: 0 n'a pas de position GPS et ne doit pas être affiché sur une carte.
Pourquoi je reçois des points avec latitude/longitude à 0 ?
La balise envoie des points même sans fix GPS (par exemple juste après le réveil, ou en intérieur). Ces points contiennent quand même la température et le timestamp, mais pas de position valide.
Quelle est la différence entre receivedAt et le timestamp d'un élément ?
receivedAt: le moment où la plateforme cloud a reçu le message du trackertimestamp(d'un élément) : le moment où le tracker a capturé la donnée (point GPS ou événement)
Il peut y avoir un décalage si la balise était hors couverture réseau et a bufferisé les données.
Pourquoi je ne reçois pas de tracker.updated à chaque message ?
C'est le comportement attendu : tracker.updated n'est émis que lorsque l'alias ou les notes d'une balise sont modifiés. L'état courant (batterie, position, connectivité…) est transmis par l'objet tracker présent dans chaque message.created — c'est lui qu'il faut utiliser pour maintenir votre cache.
Comment différencier un point "live" d'un point bufferisé ?
Si l'objet tags est présent, consultez le champ buffer :
"LIVE": point capturé et envoyé en temps réel"HISTORY": point stocké en mémoire et envoyé plus tard
Si tags est absent (tag = 0), le point est généralement un point sans fix (la distinction live/history n'est pas pertinente).
Que se passe-t-il si mon serveur ne répond pas à temps ?
Le webhook peut être rejoué. Assurez-vous que votre traitement est idempotent en utilisant les champs id du message et des éléments comme clés de déduplication.
Pourquoi le champ speed est une string dans les points mais un integer dans le tracker ?
C'est volontaire. Dans les éléments point, speed est une string avec décimales ("40.8"). Dans l'objet tracker, speed est un entier arrondi (34). Parsez en conséquence.
Comment interpréter le champ heading ?
Le heading est un cap en degrés (0-360) :
- 0 = Nord
- 90 = Est
- 180 = Sud
- 270 = Ouest
Un heading de 230 signifie que la balise se déplace vers le sud-ouest.
Un même message peut-il contenir à la fois des points et des événements ?
Oui. Le tableau elements peut contenir un mélange de point et event dans n'importe quel ordre. Un message typique après un réveil peut contenir un événement sleep_leave suivi de plusieurs points GPS.
Quel système de coordonnées est utilisé pour les positions GPS ?
Toutes les coordonnées utilisent le datum WGS84 (le standard mondial des systèmes GPS). Latitude et longitude sont exprimées en degrés décimaux.
Pourquoi un même bouton physique peut-il remonter des eventType différents selon les clients ?
Chaque bouton physique (button_1, button_2, button_3) est mappé sur un événement logique côté plateforme. Un client peut configurer le bouton 1 comme sos, le bouton 2 comme user_ok, le bouton 3 comme button_3 brut, etc. Les champs code et eventType reflètent toujours l'événement logique, pas l'index physique. Pour savoir quel bouton physique a été pressé indépendamment du mapping, consultez la configuration plateforme du tracker concerné.
Quelle différence entre l'objet pt et les champs legacy latitude/longitude dans un événement ?
Pour les événements émis avec un contexte GPS (appuis bouton, SOS), la position est envoyée deux fois pour la rétrocompatibilité : une fois sous forme d'objet structuré pt (pt.lat, pt.lon, pt.acc, pt.sats, pt.fix) et une fois sous forme de champs aplatis à la racine de datas (latitude, longitude, accuracy, satellites, fix). Les deux portent les mêmes valeurs. Privilégiez l'objet pt pour les nouvelles intégrations.
Pourquoi un événement contient-il parfois un objet data et parfois non ?
Les événements se répartissent en 3 catégories :
- Événements de cycle de vie (
power_on,power_off,sleep_enter,sleep_leave,deep_sleep_enter,gps_fix,gps_lost…) : pas de payload, l'eventTypeseul porte l'information. - Événements télémétriques (
shock_detection,battery_charging_start,geofence_in,sleep_scheduled…) : contiennent un objetdataavec des champs spécifiques au type d'événement. - Événements de type bouton (
button_1/2/3,sos,user_ok) : contiennent un objetptavec la position GPS au moment de l'appui. Peuvent aussi contenir undataselon leeventType.
La présence de data et pt est documentée par type d'événement dans le tableau "Types d'événements" (colonnes data / Position).
Comment savoir quand une balise sortira d'une veille profonde programmée ?
Quand un événement sleep_scheduled (code 39) arrive, parsez data.wake_at : c'est le timestamp Unix en secondes (UTC) auquel la balise est programmée pour se réveiller. La balise est injoignable entre sleep_scheduled et le prochain sleep_leave (qui se déclenche au réveil effectif, généralement à ±30 secondes de wake_at selon la granularité de polling).
Que signifie zoneTrack dans l'état du tracker ?
zoneTrack: true indique que la balise est actuellement à l'intérieur d'une des zones géofence qu'elle suit. C'est un signal booléen rapide pour les dashboards. Pour le détail (quelle zone, quand l'entrée a eu lieu, etc.), appuyez-vous sur les événements geofence_in / geofence_out.
Suivi et diagnostic
L'onglet Webhook de la plateforme vous permet de suivre en temps réel l'état de vos intégrations (statut, codes HTTP, latence, logs). Chaque webhook affiche :
- Statut d'activation (Enabled / Disabled)
- Type d'événements écoutés (
message.created,tracker.updated) - Statut HTTP renvoyé par votre serveur (200, 400, 500…)
- Temps de réponse moyen côté serveur
- Date et type du dernier envoi
- Succès ou échec des envois récents
Bonnes pratiques
Pour garantir une intégration robuste, nous recommandons :
- Sécurité : HTTPS obligatoire, filtrage IP recommandé
- Capacité : assurez-vous que votre infrastructure peut absorber les envois en cas de fort trafic
- Traitement asynchrone : répondez rapidement HTTP 200 puis traitez les messages en tâche de fond (queue, worker)
- Idempotence : utilisez
data.idcomme clé unique pour éviter les doublons - Logs : archivez les payloads reçus pour faciliter le support et le diagnostic