Webhooks
Plutôt que d’interroger l’API en boucle, laissez Stratt vous appeler. Un webhook
est une URL de votre côté que Stratt appelle en POST lorsqu’un événement
survient dans votre organisation.
Déclarer un point d’entrée
curl -s -X POST https://api.stratt.fr/api/v1/webhooks \
-H "Authorization: Bearer $STRATT_TOKEN" \
-H "X-Organization-Id: $STRATT_ORG_ID" \
-H "Content-Type: application/json" \
-d '{"url":"https://exemple.fr/hooks/stratt","events":["marche.created"]}'url et events sont requis, et events doit porter au moins une valeur. Un
événement inconnu fait échouer la création en 400 en le nommant — il n’y a pas
d’abonnement silencieux à un événement qui n’existe pas.
Vous pouvez fournir votre propre secret de signature ; si vous l’omettez,
Stratt en génère un. Dans les deux cas, la réponse de création l’expose une
seule fois, et il n’est jamais renvoyé ensuite : les listes indiquent
seulement, par has_secret, qu’un secret existe.
Les autres routes de gestion :
GET /api/v1/webhooks
GET /api/v1/webhooks/events
PUT /api/v1/webhooks/:id
DELETE /api/v1/webhooks/:id
GET /api/v1/webhooks/:id/deliveries
POST /api/v1/webhooks/:id/testPUT accepte url, events et is_active : basculer is_active à false
suspend un point d’entrée sans perdre sa configuration ni son secret.
Les événements souscriptibles
La liste de référence se lit sur GET /api/v1/webhooks/events. À ce jour :
| Événement | Ce qu’il annonce |
|---|---|
marche.created | un marché a été créé |
marche.updated | un marché a été modifié |
marche.deleted | un marché a été supprimé |
marche.seuil_depasse | un seuil réglementaire est franchi |
import.completed | un import s’est terminé |
alerte.echeance | une échéance approche |
alerte.delai_paiement | un délai de paiement est en jeu |
user.joined | un compte a rejoint l’organisation |
user.removed | un compte a été retiré de l’organisation |
La valeur spéciale "*" souscrit à tout, y compris aux événements ajoutés
plus tard. C’est le choix sûr si votre point d’entrée sait ignorer ce qu’il ne
connaît pas ; c’est le mauvais choix s’il tombe sur un événement inattendu.
L’enveloppe
Chaque livraison est un POST portant la même structure, quel que soit
l’événement :
{
"id": "9f1c…",
"event": "marche.created",
"organization_id": "e02b…",
"timestamp": "2026-10-02T09:41:12Z",
"data": { }
}Lisez organization_id au premier niveau, pas dans data. C’est le champ
supporté, et le seul sur lequel vous pouvez compter d’une version à l’autre.
timestamp est toujours exprimé en temps universel.
Le contenu de data dépend de l’événement et peut s’enrichir : traitez-le comme
ouvert, et ne cassez pas sur un champ supplémentaire.
Vérifier la signature
C’est l’étape qu’on saute et qu’on regrette : sans elle, n’importe qui connaissant votre URL peut vous envoyer de faux événements.
Lire les en-têtes
Content-Type: application/json
User-Agent: Stratt-Webhooks/1.0
X-Stratt-Signature: sha256=<hexadécimal>Recalculer le HMAC
Un HMAC SHA-256 du corps brut de la requête, avec le secret affiché à la création du webhook.
import { createHmac, timingSafeEqual } from "node:crypto";
export function signatureValide(corpsBrut, enTete, secret) {
const attendu = "sha256=" + createHmac("sha256", secret).update(corpsBrut).digest("hex");
const a = Buffer.from(attendu);
const b = Buffer.from(enTete ?? "");
// Longueurs différentes : timingSafeEqual lèverait au lieu de rendre false.
return a.length === b.length && timingSafeEqual(a, b);
}Deux pièges, et ils se tiennent la main. Signez le corps brut, pas un JSON
reparsé puis resérialisé : l’ordre des clés et les espaces changent, et la
signature ne correspond plus. Et comparez en temps constant — un ===
renseigne un attaquant sur le nombre de caractères justes.
Refuser ce qui n’est pas signé
Un point d’entrée déclaré sans secret reçoit des livraisons sans en-tête de signature. Si votre code accepte une requête dont la signature est absente, le secret ne protège rien. Traitez l’absence comme un échec.
Répondre vite
Tout code 2xx compte comme un succès, et rien d’autre. Faites le travail
après avoir répondu : Stratt abandonne la tentative au bout de dix
secondes, et une réponse lente compte comme un échec alors que vous avez bien
reçu l’événement.
En cas d’échec
Stratt tente la livraison quatre fois au total : immédiatement, puis après 1 minute, 5 minutes et 30 minutes. Au-delà, elle est abandonnée et son échec reste consultable.
Votre point d’entrée doit donc être idempotent : le champ id de
l’enveloppe est le même d’une tentative à l’autre pour une même livraison.
Servez-vous-en pour ignorer un événement déjà traité — c’est le seul moyen, car
rien ne distingue une première tentative d’une seconde dans la requête reçue.
Chaque tentative est consignée. GET /api/v1/webhooks/:id/deliveries les
renvoie, page par page, avec pour chacune :
| Champ | Contenu |
|---|---|
event | l’événement livré |
payload | le corps envoyé, tel quel |
status | pending, success ou failure |
status_code | le code HTTP de votre réponse |
error_msg | la raison de l’échec |
attempt_count | le nombre de tentatives faites |
next_retry_at | l’heure de la prochaine, s’il en reste une |
Le point d’entrée lui-même porte last_status et fail_count, qui comptent les
livraisons définitivement perdues : un fail_count qui grimpe est le signal à
surveiller.
Tester
POST /api/v1/webhooks/:id/test provoque une livraison d’essai portant le
message Ceci est un test de connexion Stratt. — de quoi vérifier l’URL, la
signature et le code de réponse avant d’attendre un vrai événement.
L’essai ne parvient qu’aux points d’entrée souscrits à "*". L’événement
de test ne figure pas dans la liste des événements souscriptibles, et ne peut
donc pas être souscrit nommément : un webhook abonné à marche.created verra
l’appel accepté, mais ne recevra rien. Pour tester, souscrivez temporairement à
"*".
Permissions requises
admin.manage sur l’organisation, pour toutes les routes de cette page —
création, modification, suppression, consultation des livraisons et essai. Un
webhook reçoit les événements de l’organisation entière, ce qui en fait un
réglage d’administration et non un réglage personnel.
Limites connues
- Seul l’événement de test est émis à ce jour. Les événements du tableau ci-dessus se souscrivent et sont validés à la création, mais la production de la plupart d’entre eux n’est pas encore branchée. Avant de bâtir une intégration dessus, vérifiez avec le support lesquels sont effectivement émis : un point d’entrée qui ne reçoit rien ressemble en tout point à un point d’entrée mal configuré, et c’est beaucoup de temps perdu à chercher.
- Pas de relivraison manuelle : aucune route ne permet de rejouer une livraison échouée. Une fois les quatre tentatives épuisées, l’événement est perdu pour votre système — seul son enregistrement subsiste.
- Une livraison en attente de nouvelle tentative n’est pas reprise si le service redémarre entre-temps.
- Pas de filtrage plus fin que l’événement : on souscrit à un type, pas à un service, un seuil ou un périmètre. Le tri se fait chez vous.
- Aucune plage d’adresses n’est publiée pour une liste d’autorisation réseau. La signature est le moyen prévu d’authentifier l’appelant ; si votre point d’entrée est derrière un filtrage par adresse, parlez-en au support.