Skip to content

Webhooks

Envoyez depuis Potato des notifications HTTP par webhook signées en HMAC pour 7 types d'événements d'annotation, avec relance à backoff exponentiel, surveillance côté administration et vérification en Python ou en Node.

Nouveau dans la v2.4.0

Les webhooks permettent à Potato de prévenir des systèmes externes quand un événement d'annotation survient, sans interrogation répétée. Reliez-les à des pipelines de données, déclenchez des alertes, mettez à jour des tableaux de bord ou lancez automatiquement des traitements en aval.

Vue d'ensemble

Potato envoie une requête HTTP POST au point de terminaison que vous avez configuré chaque fois qu'un événement pris en charge se déclenche. Les payloads sont en JSON et signés en HMAC-SHA256, ce qui vous permet de vérifier qu'ils viennent bien de votre instance Potato.

La livraison des webhooks est entièrement non bloquante : les requêtes d'annotation ne sont jamais retardées pendant qu'un webhook est en cours. Les livraisons en échec sont relancées avec un backoff exponentiel.

Configuration

Ajoutez une section webhooks à votre configuration YAML :

yaml
webhooks:
  enabled: true
  endpoints:
    - name: "my-pipeline"
      url: "https://your-system.example.com/potato-events"
      secret: "your-signing-secret"        # optional but recommended
      events:
        - annotation.created
        - item.fully_annotated
        - task.completed
      active: true
      timeout: 10

Plusieurs points de terminaison

Vous pouvez configurer plusieurs points de terminaison, chacun avec ses propres abonnements aux événements :

yaml
webhooks:
  enabled: true
  endpoints:
    - name: "data-pipeline"
      url: "https://pipeline.example.com/annotations"
      secret: ${WEBHOOK_SECRET_1}
      events:
        - annotation.created
        - item.fully_annotated
    - name: "slack-alerts"
      url: "https://hooks.slack.com/services/..."
      events:
        - task.completed
        - quality.attention_check_failed
    - name: "catch-all"
      url: "https://logging.example.com/potato"
      events:
        - "*"          # subscribe to all events

Types d'événements

ÉvénementSe déclenche quand
annotation.createdUn annotateur soumet une étiquette pour une instance
annotation.updatedUn annotateur modifie une étiquette déjà soumise
item.fully_annotatedUne instance atteint le recoupement d'annotations exigé
task.completedToutes les instances de la tâche sont entièrement annotées
user.phase_completedUn annotateur termine une phase (flux du mode solo)
quality.attention_check_failedUn annotateur échoue à un contrôle d'attention
webhook.testDéclenché manuellement via l'API d'administration, pour tester

Utilisez "*" pour vous abonner à tous les types d'événements, actuels et futurs.

Format du payload

Tous les événements partagent une même enveloppe :

json
{
  "event_id": "evt_01HXYZ...",
  "event_type": "annotation.created",
  "timestamp": "2026-03-17T14:23:01Z",
  "task_name": "sentiment-study",
  "data": {
    ...
  }
}

Payload de annotation.created

json
{
  "event_type": "annotation.created",
  "data": {
    "annotator_id": "user123",
    "instance_id": "doc_042",
    "annotation": {
      "sentiment": "positive",
      "confidence": "high"
    },
    "submitted_at": "2026-03-17T14:23:01Z"
  }
}

Payload de item.fully_annotated

json
{
  "event_type": "item.fully_annotated",
  "data": {
    "instance_id": "doc_042",
    "annotator_count": 3,
    "annotations": [
      {"annotator_id": "user1", "sentiment": "positive"},
      {"annotator_id": "user2", "sentiment": "positive"},
      {"annotator_id": "user3", "sentiment": "neutral"}
    ]
  }
}

Payload de task.completed

json
{
  "event_type": "task.completed",
  "data": {
    "task_name": "sentiment-study",
    "total_instances": 500,
    "total_annotations": 1500,
    "completed_at": "2026-03-17T15:00:00Z"
  }
}

Vérifier les signatures

Quand un secret est configuré, Potato signe chaque requête selon Standard Webhooks (HMAC-SHA256). Trois en-têtes sont ajoutés :

En-têteValeur
webhook-idIdentifiant unique de livraison
webhook-timestampHorodatage Unix de la livraison
webhook-signatureSignature HMAC-SHA256

Vérification en Python

python
import hmac
import hashlib
import time
 
def verify_webhook(payload_bytes: bytes, headers: dict, secret: str) -> bool:
    webhook_id = headers.get("webhook-id", "")
    timestamp = headers.get("webhook-timestamp", "")
    signature = headers.get("webhook-signature", "")
 
    # Reject stale requests (older than 5 minutes)
    if abs(time.time() - int(timestamp)) > 300:
        return False
 
    signed_content = f"{webhook_id}.{timestamp}.{payload_bytes.decode()}"
    expected = hmac.new(
        secret.encode(),
        signed_content.encode(),
        hashlib.sha256
    ).hexdigest()
 
    return hmac.compare_digest(f"v1,{expected}", signature)

Vérification en Node.js

javascript
const crypto = require('crypto');
 
function verifyWebhook(payload, headers, secret) {
  const webhookId = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signature = headers['webhook-signature'];
 
  // Reject stale requests
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
    return false;
  }
 
  const signedContent = `${webhookId}.${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');
 
  return crypto.timingSafeEqual(
    Buffer.from(`v1,${expected}`),
    Buffer.from(signature)
  );
}

Comportement des relances

Les livraisons en échec (réponse non 2xx ou dépassement de délai) sont relancées automatiquement :

TentativeDélai
1 (initiale)Immédiat
25 secondes
330 secondes
45 minutes
530 minutes
61 heure

Après 6 tentatives infructueuses, la livraison est marquée comme définitivement en échec. La file de relance est conservée en SQLite dans {output_dir}/.webhooks/webhook_retries.db : les relances en attente survivent aux redémarrages du serveur.

Surveillance et tests

API d'administration

Consultez l'état des webhooks et leurs statistiques :

bash
# List all webhooks and their delivery statistics
curl -H "X-API-Key: $ADMIN_API_KEY" \
  http://localhost:8000/admin/api/webhooks

Réponse :

json
{
  "endpoints": [
    {
      "name": "my-pipeline",
      "url": "https://...",
      "events": ["annotation.created"],
      "active": true,
      "stats": {
        "total_emitted": 1240,
        "total_failed": 3,
        "pending_retries": 0,
        "last_success": "2026-03-17T14:23:01Z"
      }
    }
  ]
}

Envoyer un webhook de test

bash
curl -X POST -H "X-API-Key: $ADMIN_API_KEY" \
  http://localhost:8000/admin/api/webhooks/test \
  -H "Content-Type: application/json" \
  -d '{"endpoint_name": "my-pipeline"}'

Un événement webhook.test part immédiatement vers le point de terminaison nommé.

Référence complète de configuration

yaml
webhooks:
  enabled: true
  endpoints:
    - name: string           # unique name for this endpoint
      url: string            # HTTPS URL to POST to
      secret: string         # optional HMAC secret for signature verification
      events:                # list of event types, or ["*"] for all
        - annotation.created
      active: true           # set false to disable without removing
      timeout: 10            # request timeout in seconds (default: 10)
      max_retries: 6         # max retry attempts (default: 6)

Pour aller plus loin

Pour les détails d'implémentation, consultez la documentation source.