Référence
API
Webhooks API

Webhooks API API pour configurer et gérer les webhooks CSWeb.

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)