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 :

1
Générez votre token depuis votre dashboard (Connecteurs > Triggers entrants > Webhook CI) ou demandez-le à votre administrateur.
2
Lancez un run avec un simple appel HTTP :
# Lancer tous les tests du client
curl -X POST https://prod-watch.com/api/webhook/run/{slug} \
  -H "Authorization: Bearer {votre_token}"
3
Recevez les résultats via vos alertes configurées (email, Slack, etc.).

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

# 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.

POST /api/webhook/run/{slug}

Lance l'ensemble des scénarios de test pour le client identifié par son slug.

Paramètres

ParamètreTypeDescription
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.

GET /api/webhook/status/{jobId}

Vérifiez l'état d'un run en cours ou terminé.

ParamètreTypeDescription
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_reasonDescription
failAu moins un test a échoué
recoveryTous les tests passent après un échec précédent
passTous les tests passent (si l'alerte est configurée sur « tous »)
manualDéclenchement manuel depuis l'admin
testTest de notification

7. Codes d'erreur et limites

CodeErreurCause
400Parametres 'scenario' et 'group' sont mutuellement exclusifsVous avez envoyé les deux query params dans la même requête
400Aucun scenario actif dans la selection demandeeTous les scénarios listés dans ?scenario= sont désactivés ou inexistants
400Aucun scenario actif dans le groupe '...'Le groupe existe mais aucun scénario actif ne correspond
400All scenarios are disabled for this clientAucun scénario actif côté client (sans ?scenario= ni ?group=)
401Missing Authorization headerHeader Authorization: Bearer ... absent
401Invalid tokenToken incorrect ou révoqué
403Webhook not available for this planPlan Starter ou Growth (upgrade nécessaire)
403Webhook not configuredToken non généré pour ce client
403Client workspace is disabledLe workspace client a été désactivé par l'admin
404Client not foundSlug invalide
404Groupe '...' introuvable pour le clientLe ?group= ne correspond à aucun groupe défini
404Job not found(GET status) jobId inexistant
409A run is already in progressUn run est déjà en cours pour ce client (anti-doublon)
429Rate limit exceededTrop 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.

EndpointLimiteUsage typique
POST /api/webhook/run/:slug60 / min1 à 10 triggers/min maximum
GET /api/webhook/status/:jobId600 / minpolling 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 :

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 :

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 :

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)"