Skip to Content
APIWebhooks

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/test

PUT 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énementCe qu’il annonce
marche.createdun marché a été créé
marche.updatedun marché a été modifié
marche.deletedun marché a été supprimé
marche.seuil_depasseun seuil réglementaire est franchi
import.completedun import s’est terminé
alerte.echeanceune échéance approche
alerte.delai_paiementun délai de paiement est en jeu
user.joinedun compte a rejoint l’organisation
user.removedun 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 :

ChampContenu
eventl’événement livré
payloadle corps envoyé, tel quel
statuspending, success ou failure
status_codele code HTTP de votre réponse
error_msgla raison de l’échec
attempt_countle nombre de tentatives faites
next_retry_atl’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.