Référence
API
Webhooks API

Webhooks API

API pour configurer et gérer les webhooks CSWeb.

Deux familles de webhooks distinctes

CSWeb expose deux mécanismes différents :

  1. API REST /api/webhooks (cette page) — webhooks sortants authentifiés par OAuth2. CSWeb appelle vos systèmes externes lorsqu'un événement métier se produit (case uploaded, case modified, etc.).

  2. Webhooks PHP racine — endpoints entrants authentifiés par un Bearer token statique. Vos systèmes externes (Kairos, scripts cron, monitoring) appellent CSWeb pour piloter le breakout, lire les logs, ou consulter le statut. Voir leur documentation dédiée :

    • Breakout Status WebhookGET /breakout-status-webhook.php
    • POST /breakout-webhook.php — déclenche un breakout
    • GET|POST /dictionary-schema-webhook.php — gère la configuration des cibles breakout
    • GET /log-reader-webhook.php — lit les logs CSWeb

    Tous les webhooks racine partagent le même contrat de réponse :

    { "success": true,  "data": ... | null, "error": null,             "meta": {...}? }
    { "success": false, "data": null,        "error": { "code": "...", "message": "..." } }

    et le même catalogue de codes d'erreur (missing_token, invalid_token, server_misconfigured, method_not_allowed, body_too_large, rate_limited, invalid_body, invalid_dictionary, invalid_filename, invalid_action, dictionary_not_found, file_not_found, file_not_readable, process_failed, breakout_failed, internal_error).

    Sécurité commune : variable BREAKOUT_WEBHOOK_TOKEN obligatoire, rate-limit 60 req/min/IP, body POST max 64 KB, header X-Robots-Tag: noindex, nofollow.


Authentification Requise

Toutes les requêtes nécessitent un Bearer token OAuth2.

Authorization: Bearer YOUR_ACCESS_TOKEN

Concept des Webhooks

Les webhooks permettent à CSWeb de notifier des applications externes lors d'événements spécifiques :

  • Case uploaded : Nouveau questionnaire uploadé
  • Case modified : Questionnaire modifié
  • Case verified : Questionnaire vérifié
  • Case deleted : Questionnaire supprimé
  • Dictionary uploaded : Nouveau dictionnaire uploadé

Use Case : Synchronisation temps réel avec applications externes (BI, analytics, dashboards).


Endpoints

GET /api/webhooks Liste tous les webhooks configurés.

Request:

curl -X GET http://localhost:8080/api/webhooks \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response (200 OK):

[
 {
 "id": 1,
 "name": "Analytics Sync",
 "url": "https://analytics.example.com/api/csweb/webhook",
 "events": ["case_uploaded", "case_modified"],
 "active": true,
 "secret": "whsec_abc123...",
 "created_at": "2026-01-15T10:00:00Z"
 },
 {
 "id": 2,
 "name": "Dashboard Notification",
 "url": "https://dashboard.example.com/webhooks/csweb",
 "events": ["case_verified"],
 "active": true,
 "secret": "whsec_xyz789...",
 "created_at": "2026-02-10T14:30:00Z"
 }
]

POST /api/webhooks Créer un nouveau webhook.

Request Body:

{
 "name": "My Webhook",
 "url": "https://myapp.example.com/webhook",
 "events": ["case_uploaded", "case_verified"],
 "secret": "my_secret_key_123",
 "active": true
}

Parameters:

  • name (string, required): Nom du webhook
  • url (string, required): URL de destination (HTTPS recommandé)
  • events (array, required): Liste des événements à écouter
  • secret (string, optional): Clé secrète pour vérifier signature
  • active (boolean, optional): Activer immédiatement (défaut: true)

Request:

curl -X POST http://localhost:8080/api/webhooks \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "name": "Analytics Sync",
 "url": "https://analytics.example.com/api/webhook",
 "events": ["case_uploaded"],
 "secret": "secure_secret_123"
 }'

Response (201 Created):

{
 "id": 3,
 "name": "Analytics Sync",
 "url": "https://analytics.example.com/api/webhook",
 "events": ["case_uploaded"],
 "active": true,
 "secret": "secure_secret_123",
 "created_at": "2026-03-15T12:00:00Z"
}

PUT /api/webhooks/{id}

Mettre à jour un webhook existant.

Request:

curl -X PUT http://localhost:8080/api/webhooks/1 \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "active": false
 }'

Response (200 OK):

{
 "id": 1,
 "name": "Analytics Sync",
 "url": "https://analytics.example.com/api/webhook",
 "events": ["case_uploaded", "case_modified"],
 "active": false,
 "updated_at": "2026-03-15T12:30:00Z"
}

DELETE /api/webhooks/{id}

Supprimer un webhook.

Request:

curl -X DELETE http://localhost:8080/api/webhooks/1 \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response (200 OK):

{
 "message": "Webhook deleted successfully",
 "id": 1
}

POST /api/webhooks/{id}/test Tester un webhook en envoyant un événement de test.

Request:

curl -X POST http://localhost:8080/api/webhooks/1/test \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response (200 OK):

{
 "status": "success",
 "webhook_id": 1,
 "test_payload_sent": {
 "event": "webhook_test",
 "timestamp": "2026-03-15T12:00:00Z",
 "data": {
 "message": "This is a test webhook"
 }
 },
 "response_status": 200,
 "response_time": "245ms"
}

Response (502 Bad Gateway):

{
 "status": "failed",
 "webhook_id": 1,
 "error": "Connection timeout",
 "details": "Failed to connect to https://analytics.example.com/api/webhook after 30s"
}

Événements Disponibles

ÉvénementDescriptionPayload
case_uploadedNouveau questionnaire uploadécase_data
case_modifiedQuestionnaire modifiécase_data, changes
case_verifiedQuestionnaire vérifiécase_data
case_deletedQuestionnaire supprimé (soft delete)case_id, dictionary
dictionary_uploadedNouveau dictionnaire uploadédictionary_data

Format du Payload

case_uploaded

{
 "event": "case_uploaded",
 "timestamp": "2026-03-15T12:00:00Z",
 "webhook_id": 1,
 "data": {
 "dictionary": "EVAL_DICT",
 "case": {
 "guid": "abc123-def456-ghi789",
 "case_label": "001-2025-001",
 "modified_date": "2026-03-15T12:00:00Z",
 "verified": false,
 "deleted": false
 }
 }
}

case_modified

{
 "event": "case_modified",
 "timestamp": "2026-03-15T13:00:00Z",
 "webhook_id": 1,
 "data": {
 "dictionary": "EVAL_DICT",
 "case": {
 "guid": "abc123-def456-ghi789",
 "case_label": "001-2025-001",
 "modified_date": "2026-03-15T13:00:00Z",
 "verified": true
 },
 "changes": {
 "verified": {
 "old": false,
 "new": true
 }
 }
 }
}

Vérification de Signature Pour sécuriser les webhooks, CSWeb signe chaque requête avec HMAC-SHA256.

Header de Signature

X-CSWeb-Signature: sha256=abc123def456...

Vérification (Node.js)

const crypto = require('crypto');
 
function verifyWebhookSignature(payload, signature, secret) {
 const expectedSignature = crypto
 .createHmac('sha256', secret)
 .update(JSON.stringify(payload))
 .digest('hex');
 
 return `sha256=${expectedSignature}` === signature;
}
 
// Utilisation dans Express
app.post('/webhook', (req, res) => {
 const signature = req.headers['x-csweb-signature'];
 const secret = 'secure_secret_123';
 
 if (!verifyWebhookSignature(req.body, signature, secret)) {
 return res.status(401).json({ error: 'Invalid signature' });
 }
 
 // Traiter webhook console.log('Event:', req.body.event);
 console.log('Data:', req.body.data);
 
 res.status(200).json({ received: true });
});

Vérification (PHP)

function verifyWebhookSignature($payload, $signature, $secret) {
 $expectedSignature = 'sha256=' . hash_hmac('sha256', json_encode($payload), $secret);
 return hash_equals($expectedSignature, $signature);
}
 
// Utilisation dans Laravel
Route::post('/webhook', function (Request $request) {
 $signature = $request->header('X-CSWeb-Signature');
 $secret = 'secure_secret_123';
 
 if (!verifyWebhookSignature($request->all(), $signature, $secret)) {
 return response()->json(['error' => 'Invalid signature'], 401);
 }
 
 // Traiter webhook
 $event = $request->input('event');
 $data = $request->input('data');
 
 return response()->json(['received' => true]);
});

Best Practices

1. Toujours Utiliser HTTPS

{
 "url": "https://myapp.example.com/webhook" // Sécurisé
}

Éviter HTTP non chiffré en production.

2. Vérifier la Signature Toujours vérifier X-CSWeb-Signature pour éviter les requêtes malveillantes.

3. Répondre Rapidement (< 5s)

// Bon : Réponse immédiate + traitement async
app.post('/webhook', async (req, res) => {
 res.status(200).json({ received: true });
 
 // Traitement en arrière-plan await processWebhook(req.body);
});
 
// Mauvais : Traitement synchrone long
app.post('/webhook', async (req, res) => {
 await longProcessing(req.body); // Timeout possible res.status(200).json({ received: true });
});

4. Gérer les Retry CSWeb réessaie automatiquement 3 fois (1min, 5min, 15min) si webhook échoue.

Implémenter idempotence :

// Utiliser webhook_id + timestamp comme clé unique
const eventKey = `${req.body.webhook_id}_${req.body.timestamp}`;
 
if (await isAlreadyProcessed(eventKey)) {
 return res.status(200).json({ received: true, duplicate: true });
}
 
await markAsProcessed(eventKey);
await processWebhook(req.body);

Logs & Monitoring

GET /api/webhooks/{id}/deliveries Voir l'historique des envois d'un webhook.

Request:

curl -X GET http://localhost:8080/api/webhooks/1/deliveries?limit=10 \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response (200 OK):

{
 "webhook_id": 1,
 "total_deliveries": 1250,
 "successful": 1230,
 "failed": 20,
 "deliveries": [
 {
 "id": 5001,
 "event": "case_uploaded",
 "status": "success",
 "response_status": 200,
 "response_time": "245ms",
 "attempted_at": "2026-03-15T12:00:00Z"
 },
 {
 "id": 5000,
 "event": "case_uploaded",
 "status": "failed",
 "response_status": 500,
 "error": "Internal Server Error",
 "attempted_at": "2026-03-15T11:58:00Z",
 "retry_count": 3
 }
 ]
}

Ressources


CSWeb Community Platform v2.0 - Webhooks API Architecte : Bouna DRAME | Portfolio (opens in a new tab)