Aller au contenu principal

Guide d'intégration Webhook

Construisez des intégrations en temps réel avec Stockaj en utilisant les webhooks. Ce guide couvre la configuration, le traitement des charges utiles, la vérification de sécurité et les patterns courants.

Comment ça fonctionne

  1. Vous enregistrez une URL de webhook dans Stockaj
  2. Lorsque des événements se produisent (ex. : location créée), Stockaj envoie un HTTP POST à votre URL
  3. Votre serveur traite la charge utile et répond avec un statut 2xx
Événement → Stockaj crée la charge utile → POST vers votre URL → Vous la traitez

Configuration d'un récepteur de webhook

Node.js (Express)

const express = require('express');
const crypto = require('crypto');

const app = express();

// Important : utiliser le body brut pour la vérification de signature
app.post('/webhooks/stockaj', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-stockaj-signature'];
const event = req.headers['x-stockaj-event'];
const secret = process.env.STOCKAJ_WEBHOOK_SECRET;

// Vérifier la signature
const computed = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Signature invalide');
}

const payload = JSON.parse(req.body);

// Traiter l'événement
switch (event) {
case 'rent.created':
console.log('Nouvelle location :', payload.data.id);
// Votre logique ici
break;
case 'item.low_stock':
console.log('Alerte stock bas :', payload.data.name);
// Votre logique ici
break;
default:
console.log(`Événement non géré : ${event}`);
}

res.status(200).json({ received: true });
});

app.listen(3000);

Python (Flask)

import hmac
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['STOCKAJ_WEBHOOK_SECRET']

@app.route('/webhooks/stockaj', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Stockaj-Signature')
event = request.headers.get('X-Stockaj-Event')

# Vérifier la signature
computed = hmac.new(
WEBHOOK_SECRET.encode(),
request.data,
hashlib.sha256
).hexdigest()

if not hmac.compare_digest(computed, signature):
return jsonify({'error': 'Signature invalide'}), 401

payload = request.get_json()

# Traiter les événements
if event == 'rent.created':
handle_new_rental(payload['data'])
elif event == 'rent.status_changed':
handle_status_change(payload['data'])
elif event == 'item.low_stock':
handle_low_stock(payload['data'])

return jsonify({'received': True}), 200

PHP (Laravel)

Route::post('/webhooks/stockaj', function (Request $request) {
$signature = $request->header('X-Stockaj-Signature');
$secret = config('services.stockaj.webhook_secret');

$computed = hash_hmac('sha256', $request->getContent(), $secret);

if (!hash_equals($computed, $signature)) {
abort(401, 'Signature invalide');
}

$event = $request->header('X-Stockaj-Event');
$payload = $request->all();

match ($event) {
'rent.created' => dispatch(new HandleNewRental($payload['data'])),
'rent.status_changed' => dispatch(new HandleStatusChange($payload['data'])),
'item.low_stock' => dispatch(new HandleLowStock($payload['data'])),
default => Log::info("Événement webhook non géré : {$event}"),
};

return response()->json(['received' => true]);
});

Patterns d'intégration courants

Notifications Slack

Envoyez des notifications de location vers un canal Slack :

case 'rent.created':
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `📋 Nouvelle location créée par ${payload.data.renter.name}`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Nouvelle location #${payload.data.id}*\n` +
`Locataire : ${payload.data.renter.name}\n` +
`Articles : ${payload.data.items.length}\n` +
`Statut : ${payload.data.status}`
}
}]
})
});
break;

Synchronisation d'inventaire

Gardez une base de données externe synchronisée :

case 'item.updated':
await externalDb.items.update({
where: { stockaj_id: payload.data.id },
data: {
name: payload.data.name,
quantity: payload.data.quantity,
updated_at: payload.timestamp,
}
});
break;

Alertes de stock bas

Déclenchez un réapprovisionnement automatique :

case 'item.low_stock':
const item = payload.data;
if (item.quantity <= item.minimum_quantity) {
await purchaseOrderSystem.createOrder({
item_sku: item.external_id,
quantity: item.minimum_quantity * 2 - item.quantity,
notes: `Réapprovisionnement automatique depuis l'alerte stock bas Stockaj`,
});
}
break;

Bonnes pratiques

  1. Répondez rapidement — Retournez une réponse 2xx dans le délai imparti (par défaut : 10s). Traitez le travail lourd de manière asynchrone.
  2. Soyez idempotent — Utilisez l'ID de livraison (X-Stockaj-Delivery) pour détecter et ignorer les livraisons en double.
  3. Vérifiez toujours les signatures — Ne faites jamais confiance à une charge utile de webhook sans vérifier la signature HMAC.
  4. Gérez les erreurs gracieusement — Enregistrez les erreurs et alertez votre équipe si le traitement du webhook échoue.
  5. Utilisez HTTPS — Votre URL de webhook doit utiliser HTTPS en production.
  6. Surveillez les livraisons — Vérifiez le journal de livraison dans Stockaj pour identifier les livraisons échouées.

Dépannage

Le webhook ne reçoit pas d'événements

  1. Vérifiez que le webhook est actif dans Paramètres → Webhooks
  2. Vérifiez que l'URL est accessible publiquement (pas localhost)
  3. Vérifiez que la liste des événements inclut les événements attendus
  4. Utilisez Envoyer un test pour envoyer une charge utile de test

La vérification de signature échoue

  1. Assurez-vous d'utiliser le body brut de la requête (pas le JSON parsé) pour le hachage
  2. Vérifiez que vous utilisez HMAC-SHA256 (pas SHA-1 ou SHA-256 simple)
  3. Vérifiez que le secret correspond exactement (pas d'espaces en fin de chaîne)
  4. Utilisez une comparaison résistante aux attaques temporelles pour éviter les attaques par timing

Expiration de la requête

  1. Augmentez le délai d'attente du webhook (jusqu'à 30 secondes)
  2. Déplacez le traitement vers une file d'attente en arrière-plan
  3. Retournez 200 OK immédiatement, puis traitez de manière asynchrone