v1.0 · URL stable : https://dispozition.com/spec/webhook/v1
Spécification webhook Dispozition
Ce document décrit le format des notifications HTTP envoyées par Dispozition lorsqu’un billet de voyage évolue. L’objectif est simple : un même langage pour que les constructeurs et les sites de disposition branchent leurs outils (ERP, balance, portail, scripts) sans ambiguïté — et pour faire évoluer l’industrie vers des intégrations ouvertes.
Introduction
Dispozition envoie des requêtes POST vers une URL HTTPS que vous configurez sur votre portail de site de disposition.
Chaque message est un objet JSON avec une enveloppe commune (spec_version, event, event_id, etc.) et, pour les événements liés à un voyage, un objet site et un objet voyage détaillés.
Le champ spec_url dans le JSON pointe toujours vers cette page : vous pouvez ainsi découvrir la documentation à partir d’un payload réel.
La version courante est 1.0.
Authentification
Chaque livraison est signée avec HMAC-SHA256 en utilisant le secret partagé associé à votre webhook (affiché une seule fois à la configuration).
Le corps signé est exactement les octets UTF-8 du corps HTTP que vous recevez (tel quel), après ajout par Dispozition des champs delivered_at et delivery_attempt juste avant l’envoi.
Message signé
message = timestamp_utf8 + "." + raw_body_bytes
timestamp est la valeur du header X-Dispozition-Timestamp (secondes Unix, chaîne décimale).
La signature est sha256= suivie de l’hexadécimal du HMAC (minuscules).
Headers HTTP
| Header | Description |
|---|---|
Content-Type | application/json; charset=utf-8 |
User-Agent | Dispozition-Webhook/1.0 |
X-Dispozition-Spec-Version | Ex. 1.0 |
X-Dispozition-Event | Nom de l’événement (ex. voyage.created) |
X-Dispozition-Delivery | Même valeur que event_id dans le JSON |
X-Dispozition-Timestamp | Horodatage utilisé dans le HMAC |
X-Dispozition-Signature | sha256=<hex> |
Bonne pratique : refusez les requêtes trop anciennes (replay), par exemple si |maintenant − timestamp| > 300 secondes.
Exemple — Python
import hmac
import hashlib
import time
def verify_dispozition_webhook(secret: str, raw_body: bytes, headers: dict, max_skew_s: int = 300) -> bool:
ts = headers.get("X-Dispozition-Timestamp") or headers.get("X-Dispozition-Timestamp".lower())
sig = headers.get("X-Dispozition-Signature") or ""
if not ts or not sig.startswith("sha256="):
return False
if abs(int(time.time()) - int(ts)) > max_skew_s:
return False
msg = ts.encode("utf-8") + b"." + raw_body
digest = hmac.new(secret.encode("utf-8"), msg, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, "sha256=" + digest)
Exemple — Node.js
const crypto = require("crypto");
function verifyDispozitionWebhook(secret, rawBodyBuffer, headers, maxSkewS = 300) {
const ts = headers["x-dispozition-timestamp"];
let sig = headers["x-dispozition-signature"] || "";
if (!ts || !sig.startsWith("sha256=")) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(ts, 10)) > maxSkewS) return false;
const msg = Buffer.concat([Buffer.from(String(ts), "utf8"), Buffer.from("."), rawBodyBuffer]);
const digest = crypto.createHmac("sha256", secret).update(msg).digest("hex");
const expected = "sha256=" + digest;
try {
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
} catch {
return false;
}
}
Exemple — PHP
$maxSkewS) return false;
$msg = $ts . '.' . $rawBody;
$digest = hash_hmac('sha256', $msg, $secret);
return hash_equals($sig, 'sha256=' . $digest);
}
Événements
| Événement | Quand |
|---|---|
voyage.created | Un nouveau billet de voyage est créé côté constructeur et cible ce site de disposition. |
voyage.received | Le site accuse réception du voyage sur son portail (reçu). |
voyage.weight_updated | Le poids mesuré (ou sa suppression) est enregistré sur le portail. |
webhook.test | Événement de test depuis l’interface ; payload minimal sans site ni voyage. |
Vous choisissez quels événements activer sur votre webhook.
Payload voyage — structure et exemple
Ci-dessous un exemple réaliste mais fictif (noms et coordonnées d’exemple).
Les champs réservés pour extensions futures peuvent être null.
{
"spec_version": "1.0",
"spec_url": "https://dispozition.com/spec/webhook/v1",
"event": "voyage.created",
"event_id": "evt_abc123…",
"occurred_at": "2026-05-07T18:30:00.000000+00:00",
"delivered_at": "2026-05-07T18:30:00.123456+00:00",
"delivery_attempt": 1,
"platform": {
"name": "Dispozition",
"url": "https://dispozition.com"
},
"site": {
"id": "ds_42",
"nom": "Centre de tri Example",
"portal_code": "N3T6DVR8",
"site_type": "centre",
"site_subtype": null,
"address": {
"raw": "100 rue Example, Montréal QC",
"region": "Québec",
"region_code": 11,
"country": "CA",
"lat": 45.5,
"lng": -73.5,
"google_place_id": "ChIJ…"
}
},
"voyage": {
"id": "v_1001",
"voyage_code": "550e8400-e29b-41d4-a716-446655440000",
"external_reference": null,
"ticket_number": null,
"status": "en_attente",
"is_received": false,
"is_double": false,
"origin": {
"type": "construction_site",
"name": "Chantier tunnel Example",
"constructor": { "id": "ent_7", "nom": "Excavations Example inc." },
"address": {
"raw": "200 av. Chantier",
"region": null,
"region_code": null,
"country": "CA",
"lat": null,
"lng": null,
"google_place_id": null
}
},
"destination": {
"type": "disposal_site",
"site_id": "ds_42",
"address": {
"raw": "100 rue Example, Montréal QC",
"region": "Québec",
"region_code": 11,
"country": "CA",
"lat": 45.5,
"lng": -73.5,
"google_place_id": "ChIJ…"
}
},
"vehicle": {
"fleet_number": "T-12",
"license_plate": "ABC 123",
"license_plate_region": "QC",
"transporter": { "nom": "Transport Example" }
},
"material": {
"code": null,
"name": "Terre excavee propre",
"category": "clean_soil",
"is_contaminated": false
},
"measurement": {
"estimated_tonnes": 22.5,
"measured_tonnes": null,
"measured_at": null,
"source": null
},
"timeline": {
"created_at": "2026-05-07T17:00:00+00:00",
"received_at": null,
"completed_at": null
},
"compliance": {
"ca_reference": null,
"tracking_required": null,
"manifest_id": null
},
"links": {
"portal_view": "https://dispozition.com/destinataire/N3T6DVR8"
}
}
}
Référence des champs (enveloppe)
spec_version | Version du contrat (ex. 1.0). |
spec_url | Lien vers cette documentation. |
event | Nom de l’événement livré. |
event_id | Identifiant unique de la livraison ; idempotence côté client possible. |
occurred_at | Moment métier de l’événement (ISO 8601 avec fuseau). |
delivered_at | Ajouté au moment de l’envoi HTTP. |
delivery_attempt | Numéro de tentative (1 à 4 selon la politique de retry). |
platform | Infos sur Dispozition (name, url). |
site | Site de disposition cible (absent pour webhook.test). |
voyage | Détails du billet (absent pour webhook.test). |
Référence — voyage (extraits)
voyage.id | Identifiant interne préfixé v_. |
voyage.voyage_code | UUID public du billet (stable). |
voyage.status | Voir enum voyage.status ci-dessous. |
voyage.origin / destination | Provenance chantier et arrivée site ; adresses structurées. |
voyage.material.category | Catégorie normalisée (enum material.category). |
voyage.material.is_contaminated | true si la catégorie implique un suivi renforcé (ex. sol contaminé). |
voyage.measurement.source | Origine du poids mesuré si présent : portail_manuel, sync_balance, api, ou null. |
voyage.links.portal_view | Lien vers le portail du site (si disponible). |
Enums
material.category
Valeurs stables (anglais, snake_case). Les libellés métier restent dans material.name.
clean_soilcontaminated_soiltopsoilwet_soilclay_soilpeatconcreteconcrete_recycledasphaltasphalt_recycledgravelsandrockmixed_aggregatemixed_construction_debrismetal_ferrousmetal_non_ferrousbrick_masonryother
voyage.status
en_attente— en attente côté siteaccepte— acceptérefuse— refusécomplete— complété
Politique de version
- Non cassant : ajouter des champs optionnels, enrichir des enums avec de nouvelles valeurs, préciser la doc. Les intégrations doivent ignorer les clés inconnues.
- Cassant : retirer ou renommer un champ, changer le type ou la sémantique d’un champ existant, modifier l’algorithme de signature. Une version majeure (
spec_version2.0, nouvelle URL/spec/webhook/v2) sera publiée avec période de transition annoncée.
Le header X-Dispozition-Spec-Version et le champ JSON spec_version restent alignés.
Changelog
- v1.0 — 7 mai 2026
-
Première publication publique de la spécification : événements
voyage.created,voyage.received,voyage.weight_updated, signature HMAC-SHA256, modèlesite+voyage, enumsmaterial.categoryet statuts de voyage documentés.Samuel JacquesAuteur de cette version