Documentation Webhook
Lancez vos tests QA depuis n'importe quel outil externe (CI/CD, n8n, Zapier, cron...).
1. Démarrage rapide
Trois étapes pour lancer vos tests via webhook :
# Lancer tous les tests du client curl -X POST https://prod-watch.com/api/webhook/run/{slug} \ -H "Authorization: Bearer {votre_token}"
2. Authentification
Toutes les requêtes webhook nécessitent un Bearer token dans le header Authorization.
Le token a le format wh_ suivi de 48 caractères hexadécimaux (ex: wh_a1b2c3d4...).
Gestion du token
- Génération : depuis le dashboard client (Connecteurs > Triggers entrants > Webhook CI) ou via l'admin
- Révocation : le token peut être révoqué et régénéré à tout moment
- Sécurité : le token est vérifié avec un algorithme constant-time pour éviter les attaques timing
# Header requis pour chaque appel Authorization: Bearer wh_a1b2c3d4e5f6a7b8c9d0a1b2c3d4e5f6a7b8c9d0a1b2c3d4
3. Lancer les tests
Protection anti-doublon : si un run est déjà en cours pour le même client, l'API retourne 409 Conflict avec le jobId du run en cours.
Lance l'ensemble des scénarios de test pour le client identifié par son slug.
Paramètres
| Paramètre | Type | Description |
|---|---|---|
slug |
URL path | requis Identifiant unique du client |
scenario |
Query string | optionnel Nom d'un scénario, ou liste CSV (scenario=login,checkout). Si omis, tous les scénarios actifs sont lancés. Les scénarios désactivés sont automatiquement filtrés. |
group |
Query string | optionnel Nom ou ID d'un groupe de scénarios défini pour ce client. Mutuellement exclusif avec scenario (sinon 400). |
callback_url |
Body JSON | optionnel URL où envoyer les résultats en POST une fois le run terminé (voir section 4). |
ci |
Body JSON | optionnel Contexte CI (commit, branche, run URL...) pour corréler un échec à un déploiement. Détails ci-dessous. |
Exemples
# Lancer tous les tests actifs curl -X POST https://prod-watch.com/api/webhook/run/mon-client \ -H "Authorization: Bearer wh_..." # Lancer un seul scénario curl -X POST "https://prod-watch.com/api/webhook/run/mon-client?scenario=login" \ -H "Authorization: Bearer wh_..." # Lancer plusieurs scénarios (CSV) curl -X POST "https://prod-watch.com/api/webhook/run/mon-client?scenario=login,checkout,search" \ -H "Authorization: Bearer wh_..." # Lancer un groupe défini dans le dashboard curl -X POST "https://prod-watch.com/api/webhook/run/mon-client?group=smoke" \ -H "Authorization: Bearer wh_..."
Réponse (200)
{
"success": true,
"jobId": 42,
"message": "Tests triggered for mon-client"
}
Le jobId retourné permet de suivre l'exécution (voir section 5).
Contexte CI (optionnel)
Envoyez un objet ci dans le body pour permettre au dashboard de relier ce run à un déploiement précis. Tous les champs sont optionnels et validés strictement (regex sur les SHA, URLs HTTPS sur domaines whitelistés, troncature des textes).
curl -X POST https://prod-watch.com/api/webhook/run/mon-client \ -H "Authorization: Bearer wh_..." \ -H "Content-Type: application/json" \ -d '{ "ci": { "commit_sha": "a1b2c3d4e5f6", "prev_commit_sha": "0011223344", "branch": "main", "actor": "alice", "event": "push", "environment": "production", "provider": "github", "run_url": "https://github.com/org/repo/actions/runs/123", "commit_url": "https://github.com/org/repo/commit/a1b2c3d4e5f6", "commit_message": "fix(checkout): patch race condition", "pr_number": "42", "changed_files": "src/checkout.js,src/api.js", "diff_stat": "2 files changed, 18 insertions(+), 4 deletions(-)" } }'
Domaines whitelistés pour run_url et commit_url : github.com, gitlab.com, bitbucket.org, dev.azure.com (et leurs sous-domaines courants *.github.com, *.gitlab.io, *.visualstudio.com, etc.). Toute URL hors whitelist est silencieusement ignorée.
4. Callback URL (push)
Au lieu de poll le status, vous pouvez fournir une callback_url dans le body du trigger. Prod Watch enverra les résultats en POST à cette URL dès que le run est terminé.
Trigger avec callback
curl -X POST https://prod-watch.com/api/webhook/run/mon-client \ -H "Authorization: Bearer wh_..." \ -H "Content-Type: application/json" \ -d '{ "callback_url": "https://mon-serveur.com/webhook/results" }'
Payload envoyé au callback
{
"event": "run_completed",
"jobId": 42,
"status": "done",
"exitCode": 0,
"finishedAt": "2026-04-03T08:35:12.000Z",
"result": {
"run_id": 42,
"status": "pass",
"passed": 14,
"failed": 0,
"total": 14,
"date": "2026-04-03 08:30:00",
"scenarios": "login,checkout,search",
"report_url": "https://prod-watch.com/report/42"
}
}
Vérification de signature (HMAC)
Si Prod Watch est configuré avec une clé partagée (variable d'environnement WEBHOOK_SECRET côté serveur), chaque callback inclut une signature HMAC-SHA256 dans le header X-ProdWatch-Signature. Vérifiez-la côté réception pour confirmer que le payload vient bien de Prod Watch.
# Header inclus dans le POST callback
X-ProdWatch-Signature: sha256=<hex digest>
La signature est calculée sur le body brut (JSON) avec la même clé partagée. Exemple de vérification en Node.js :
// Express + raw body parser const crypto = require("crypto"); const SECRET = process.env.PROD_WATCH_SECRET; app.post("/webhook/results", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-prodwatch-signature"] || ""; const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).end(); } const payload = JSON.parse(req.body.toString()); // ... traiter payload res.status(200).end(); });
Si la variable n'est pas configurée côté Prod Watch, le header est absent. En cas de doute, contactez votre administrateur pour activer la signature.
5. Suivre l'exécution
Alternative au callback : interrogez le status d'un job par polling. Les jobs sont persistés en base - le status reste accessible même après un redémarrage du serveur.
Vérifiez l'état d'un run en cours ou terminé.
| Paramètre | Type | Description |
|---|---|---|
jobId |
URL path | requis ID du job retourné par l'appel trigger |
Réponse (en cours)
{
"status": "running",
"exitCode": null
}
Réponse (terminé)
Une fois le run terminé, la réponse inclut les résultats complets :
{
"status": "done", // "done" | "error"
"exitCode": 0, // 0 = succès, >0 = erreur
"finishedAt": "2026-04-03T08:35:12.000Z",
"result": {
"run_id": 42,
"status": "fail", // "pass" | "fail"
"passed": 12,
"failed": 2,
"total": 14,
"date": "2026-04-03 08:30:00",
"scenarios": "login,checkout,search",
"report_url": "https://prod-watch.com/report/42"
}
}
Polling recommandé
# Boucle de polling (bash) JOB_ID=42 while true; do STATUS=$(curl -s https://prod-watch.com/api/webhook/status/$JOB_ID \ -H "Authorization: Bearer wh_..." | jq -r .status) echo "Status: $STATUS" [ "$STATUS" != "running" ] && break sleep 10 done
6. Payload des alertes
Lorsqu'un run se termine, Prod Watch envoie une notification aux alertes configurées. Voici la structure du payload JSON envoyé en POST :
{
"event": "test_run",
"client": {
"name": "Mon Client",
"slug": "mon-client",
"plan": "Scale"
},
"run": {
"status": "fail", // "pass" | "fail"
"passed": 12,
"failed": 2,
"total": 14,
"date": "2026-04-03 08:30:00",
"scenarios": "login,checkout,search"
},
"report_url": "https://prod-watch.com/report/42?token=...",
"errors": [
{
"test": "Login avec email invalide",
"error": "Expected status 200, got 500"
}
],
"error_summary": "2 tests en échec sur 14",
"subject": "FAIL - Mon Client",
"trigger_reason": "fail", // "fail" | "recovery" | "pass" | "manual" | "test"
"channels": ["#monitoring"],
"recipients": [{"type":"email", "to":"dev@client.com"}],
"html": "<div>...email HTML...</div>"
}
Déclencheurs d'alerte
| trigger_reason | Description |
|---|---|
fail | Au moins un test a échoué |
recovery | Tous les tests passent après un échec précédent |
pass | Tous les tests passent (si l'alerte est configurée sur « tous ») |
manual | Déclenchement manuel depuis l'admin |
test | Test de notification |
7. Codes d'erreur et limites
| Code | Erreur | Cause |
|---|---|---|
400 | Parametres 'scenario' et 'group' sont mutuellement exclusifs | Vous avez envoyé les deux query params dans la même requête |
400 | Aucun scenario actif dans la selection demandee | Tous les scénarios listés dans ?scenario= sont désactivés ou inexistants |
400 | Aucun scenario actif dans le groupe '...' | Le groupe existe mais aucun scénario actif ne correspond |
400 | All scenarios are disabled for this client | Aucun scénario actif côté client (sans ?scenario= ni ?group=) |
401 | Missing Authorization header | Header Authorization: Bearer ... absent |
401 | Invalid token | Token incorrect ou révoqué |
403 | Webhook not available for this plan | Plan Starter ou Growth (upgrade nécessaire) |
403 | Webhook not configured | Token non généré pour ce client |
403 | Client workspace is disabled | Le workspace client a été désactivé par l'admin |
404 | Client not found | Slug invalide |
404 | Groupe '...' introuvable pour le client | Le ?group= ne correspond à aucun groupe défini |
404 | Job not found | (GET status) jobId inexistant |
409 | A run is already in progress | Un run est déjà en cours pour ce client (anti-doublon) |
429 | Rate limit exceeded | Trop d'appels par minute depuis la même IP (voir limites ci-dessous) |
Limites d'appel
Les endpoints publics sont limités par IP pour protéger le service. Ces seuils sont largement dimensionnés pour un usage normal ; si vous les atteignez, c'est probablement qu'une boucle tourne trop vite.
| Endpoint | Limite | Usage typique |
|---|---|---|
POST /api/webhook/run/:slug | 60 / min | 1 à 10 triggers/min maximum |
GET /api/webhook/status/:jobId | 600 / min | polling toutes les 5-10s (6 à 12/min) |
Les en-têtes RateLimit-Remaining et RateLimit-Reset sont retournés sur chaque réponse. En cas de 429, attendez la fin de la fenêtre (1 min) avant de ré-essayer.
8. Exemples d'intégration
Guide générique
Avant de copier un exemple plateforme-spécifique, voici les 4 concepts transversaux à comprendre pour intégrer Prod Watch depuis n'importe quel outil (CI/CD, cron, low-code, script maison).
1. Déclencher un run. Un simple POST authentifié suffit. Le serveur répond immédiatement avec un jobId ; le run tourne en arrière-plan.
# Pseudo-code response = POST https://prod-watch.com/api/webhook/run/{slug} headers: { "Authorization: Bearer {token}" } jobId = response.jobId
2. Choisir entre polling et callback. Deux stratégies, au choix selon votre environnement :
- Polling (pull) : vous interrogez
/api/webhook/status/{jobId}toutes les 5-10 secondes jusqu'à ce questatusne soit plusrunning. Simple, pas besoin d'URL publique, mais consomme des requêtes. - Callback (push) : vous fournissez un
callback_urlpublic dans le body du trigger ; Prod Watch vous POSTera le résultat final à la fin. Plus efficace, mais nécessite une URL accessible depuis internet.
Règle simple : CI/CD court (< 10 min) → polling. Run long, scheduler externe, ou intégration low-code (n8n/Make) → callback.
3. Récupérer le statut (polling). Boucle avec timeout côté client.
# Pseudo-code polling loop up to N times: sleep 10s result = GET https://prod-watch.com/api/webhook/status/{jobId} headers: { "Authorization: Bearer {token}" } if result.status != "running": break if result.exitCode != 0: fail the build
4. Recevoir le callback (mode push). Votre endpoint doit accepter un POST application/json. Le body contient jobId, status, exitCode, result (passed/failed), et éventuellement un report_url. Répondez 2xx rapidement ; si vous répondez une erreur, Prod Watch ne retentera pas automatiquement.
Pièges courants :
- Timeout : prévoyez un timeout côté client (ex. 5 min max) et un nombre max de tentatives de polling.
- 409 Conflict : un seul run peut tourner à la fois par client. Gérez ce code en retry ou en skip selon votre logique.
- 429 Rate limit : respectez l'intervalle de polling recommandé (5-10s). Un poll plus aggressif (< 1s) sera throttlé.
- Idempotence : si votre CI redéclenche le même commit, pensez à checker
statusavant de relancer un run. - Secrets : stockez le token dans les secrets de votre CI (GitHub Secrets, GitLab CI Variables, etc.), jamais en clair dans le repo.
GitHub Actions
Workflow complet avec gestion des erreurs, timeout et resume lisible dans l'onglet Actions. Prerequis : deux secrets de depot PW_SLUG et PW_TOKEN (Settings → Secrets and variables → Actions).
# .github/workflows/prod-watch.yml name: Prod Watch QA on: push: branches: [main] workflow_dispatch: jobs: qa: runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Declencher et attendre le run Prod Watch env: PW_SLUG: ${{ secrets.PW_SLUG }} PW_TOKEN: ${{ secrets.PW_TOKEN }} run: | set -euo pipefail # 1. Declencher le run TRIGGER=$(curl -sS -X POST \ "https://prod-watch.com/api/webhook/run/$PW_SLUG" \ -H "Authorization: Bearer $PW_TOKEN") JOB_ID=$(echo "$TRIGGER" | jq -r '.jobId // empty') if [ -z "$JOB_ID" ]; then echo "::error::Impossible de declencher le run: $TRIGGER" exit 1 fi echo "Job $JOB_ID lance" # 2. Polling jusqu'a 5 min (30 x 10s) for i in $(seq 1 30); do sleep 10 RESULT=$(curl -sS \ "https://prod-watch.com/api/webhook/status/$JOB_ID" \ -H "Authorization: Bearer $PW_TOKEN") STATUS=$(echo "$RESULT" | jq -r .status) echo "[$i/30] status=$STATUS" [ "$STATUS" != "running" ] && break done # 3. Verifier le resultat final if [ "$STATUS" = "running" ]; then echo "::error::Timeout apres 5 min" exit 1 fi PASSED=$(echo "$RESULT" | jq -r '.result.passed // 0') FAILED=$(echo "$RESULT" | jq -r '.result.failed // 0') REPORT=$(echo "$RESULT" | jq -r '.result.report_url // ""') echo "### Prod Watch" >> $GITHUB_STEP_SUMMARY echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY echo "- Failed: $FAILED" >> $GITHUB_STEP_SUMMARY [ -n "$REPORT" ] && echo "- [Rapport]($REPORT)" >> $GITHUB_STEP_SUMMARY [ "$FAILED" != "0" ] && exit 1 || exit 0
GitLab CI
Meme logique pour GitLab. Prerequis : deux variables CI/CD PW_SLUG et PW_TOKEN (Settings → CI/CD → Variables, les marquer Masked et Protected).
# .gitlab-ci.yml prod-watch: stage: test image: alpine:3.20 timeout: 10 minutes before_script: - apk add --no-cache curl jq script: - | set -eu # 1. Declencher le run TRIGGER=$(curl -sS -X POST \ "https://prod-watch.com/api/webhook/run/$PW_SLUG" \ -H "Authorization: Bearer $PW_TOKEN") JOB_ID=$(echo "$TRIGGER" | jq -r '.jobId // empty') [ -z "$JOB_ID" ] && { echo "Trigger failed: $TRIGGER"; exit 1; } echo "Job $JOB_ID lance" # 2. Polling jusqu'a 5 min for i in $(seq 1 30); do sleep 10 RESULT=$(curl -sS \ "https://prod-watch.com/api/webhook/status/$JOB_ID" \ -H "Authorization: Bearer $PW_TOKEN") STATUS=$(echo "$RESULT" | jq -r .status) echo "[$i/30] status=$STATUS" [ "$STATUS" != "running" ] && break done # 3. Verifier le resultat final [ "$STATUS" = "running" ] && { echo "Timeout apres 5 min"; exit 1; } FAILED=$(echo "$RESULT" | jq -r '.result.failed // 0') PASSED=$(echo "$RESULT" | jq -r '.result.passed // 0') echo "Passed: $PASSED, Failed: $FAILED" [ "$FAILED" != "0" ] && exit 1 || exit 0 only: - main
Mode callback (push)
Alternative sans polling : Prod Watch vous POST le resultat final sur une URL que vous fournissez. Ideal pour les integrations low-code (n8n, Make, Zapier) ou les runs longs.
curl -X POST https://prod-watch.com/api/webhook/run/{slug} \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "callback_url": "https://votre-endpoint.com/hook" }'
n8n / Make / Zapier
Configurez un nœud HTTP Request avec :
- Méthode :
POST - URL :
https://prod-watch.com/api/webhook/run/{slug} - Header :
Authorization: Bearer {votre_token} - Body (optionnel) :
{ "callback_url": "https://..." }pour recevoir les résultats automatiquement
Si vous configurez un callback_url, n8n peut écouter sur un nœud Webhook pour traiter les résultats à la réception. Sinon, ajoutez un nœud de polling sur /api/webhook/status/{jobId}.
cURL (ligne de commande)
# Lancer + attendre le résultat TOKEN="wh_..." SLUG="mon-client" JOB=$(curl -s -X POST https://prod-watch.com/api/webhook/run/$SLUG \ -H "Authorization: Bearer $TOKEN" | jq -r .jobId) echo "Job $JOB lancé" while true; do R=$(curl -s https://prod-watch.com/api/webhook/status/$JOB \ -H "Authorization: Bearer $TOKEN") S=$(echo $R | jq -r .status) echo "→ $S" [ "$S" != "running" ] && break sleep 5 done echo "Exit code: $(echo $R | jq -r .exitCode)"