Webhooks
Plutôt que d'interroger l'API en boucle, enregistrez une URL HTTPS et nous vous livrons les événements au fil de l'eau. Chaque livraison est signée, ce qui vous permet de prouver qu'elle vient bien de nous.
Enregistrer une URL
POST /api/v1/client/webhooks crée un abonnement et renvoie un secret.
curl -X POST "https://<hote-api-external>/api/v1/client/webhooks" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://votre-app.example.com/hooks/staffngo", … }'
Le secret est renvoyé une seule fois, à l'enregistrement. Il est stocké chiffré de notre côté et ne peut pas être relu. Récupérez-le immédiatement ; le perdre impose de réenregistrer.
Enregistrer deux fois la même URL ne crée pas de doublon — le même abonnement est renvoyé plutôt qu'un second. Votre endpoint doit être joignable publiquement en HTTPS : les adresses privées, de loopback ou non résolvables sont refusées d'emblée.
L'
idrenvoyé est une chaîne opaque : stockez-la et renvoyez-la telle quelle. Ne la lisez pas comme un nombre, n'en déduisez pas un autre, n'en fabriquez pas : les valeurs ne sont volontairement pas séquentielles, et une valeur que nous ne vous avons pas remise ne résoudra pas.
À quoi ressemble une livraison
| En-tête | Contenu |
|---|---|
X-Webhook-Id |
Identifiant unique de l'événement — servez-vous-en pour dédoublonner. |
X-Webhook-Event |
Type d'événement. |
X-Webhook-Timestamp |
Temps Unix (secondes) au moment de la signature. |
X-Webhook-Signature |
sha256=<HMAC en hexadécimal minuscule> |
Vérifier la signature
La signature est un HMAC-SHA256, clé = votre secret, calculé sur la chaîne canonique
{timestamp}.{corps brut} — le corps exactement tel que reçu, avant toute analyse JSON ou re-sérialisation.
import hmac, hashlib, time
def verify(secret: str, timestamp: str, raw_body: bytes, header: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
f"{timestamp}.".encode("utf-8") + raw_body,
hashlib.sha256,
).hexdigest()
# Comparaison à temps constant — un == simple fuit la signature octet par octet.
if not hmac.compare_digest(f"sha256={expected}", header):
return False
# Rejeter les horodatages trop anciens, sinon une livraison capturée reste rejouable.
return abs(time.time() - int(timestamp)) < 300
Deux règles qui comptent plus qu'il n'y paraît :
- Comparez à temps constant. Une comparaison de chaînes naïve est un oracle temporel.
- Contrôlez l'horodatage. La signature seule n'expire pas ; sans fenêtre de fraîcheur, quiconque a capturé une livraison peut la rejouer indéfiniment.
Répondre
Répondez 2xx rapidement. Faites le travail réel de façon asynchrone — un handler lent grignote le délai de
livraison et transforme un événement réussi en réessai.
| Votre réponse | Ce que nous faisons |
|---|---|
2xx |
Livré. Terminé. |
408, 429, 5xx |
Traité comme transitoire — réessayé avec backoff. |
Tout autre 4xx |
Traité comme permanent — non réessayé, et des échecs répétés désactivent l'abonnement. |
| Timeout, DNS, TLS ou échec de connexion | Transitoire — réessayé avec backoff. |
Qui dit réessais dit handler idempotent : dédoublonnez sur X-Webhook-Id. Une livraison déjà traitée
peut légitimement se représenter après un incident réseau de notre côté.
Tester avant la mise en service
POST /api/v1/client/webhooks/test envoie un ping à votre URL enregistrée : vous validez la joignabilité et
votre vérification de signature sans attendre un événement réel. À utiliser à chaque modification du côté
récepteur.