Webhooks
Les webhooks vous permettent de recevoir des notifications en temps réel lorsque des événements se produisent dans votre compte LabelGrid. Utilisez les webhooks pour automatiser des workflows et vous intégrer à des systèmes externes.
Comment fonctionnent les webhooks
Section intitulée « Comment fonctionnent les webhooks »- Vous configurez un webhook - Spécifiez une URL et les événements à écouter
- Un événement se produit - Par exemple, une sortie est livrée à un store
- LabelGrid envoie une requête POST - Votre serveur reçoit les données de l’événement
- Votre système les traite - Automatisez des workflows en fonction de l’événement
Accéder aux webhooks
Section intitulée « Accéder aux webhooks »- Cliquez sur votre icône de profil en haut à droite
- Sélectionnez Webhooks dans le menu déroulant
Créer un webhook
Section intitulée « Créer un webhook »- Cliquez sur Create Webhook
- Saisissez un Name pour identifier ce webhook
- Saisissez l’URL ou vous souhaitez recevoir les notifications
- Sélectionnez les Events qui doivent déclencher ce webhook
- Cliquez sur Create
Secret du webhook
Section intitulée « Secret du webhook »Lorsque vous créez un webhook, vous recevez une clé secrète. Utilisez-la pour vérifier que les requêtes entrantes proviennent bien de LabelGrid :
- Copiez le secret immédiatement : LabelGrid ne l’affiche qu’une seule fois et n’en conserve aucune copie lisible
- Stockez le secret de manière sécurisée
- Vérifiez la signature des requêtes entrantes
- En cas de compromission ou de perte, renouvelez le secret (voir Renouveler le secret)
Événements disponibles
Section intitulée « Événements disponibles »Configurez votre webhook pour écouter ces événements. L’identifiant d’événement est la valeur que vous verrez dans la propriété event du payload et dans le header X-Webhook-Event :
| Identifiant d’événement | Description |
|---|---|
delivery.completed | Déclenché lorsqu’une sortie est livrée avec succès à un store |
delivery.failed | Déclenché lorsque la livraison à un store échoue |
takedown.completed | Déclenché lorsqu’une demande de retrait est terminée |
release.review.status_changed | Déclenché lorsque le statut de révision d’une sortie change |
track.review.status_changed | Déclenché lorsque le statut de révision d’une piste change |
release.preflight.report_ready | Déclenché lorsque le rapport Preflight QC d’une sortie en attente est prêt à être récupéré |
stream_radar.flag_created | Déclenché lorsqu’un signalement Stream Radar est créé, ou qu’un signalement résolu rouvre sur une nouvelle détection |
stream_radar.flag_resolved | Déclenché lorsqu’un signalement Stream Radar se résout parce que les détections ont cessé |
release.distributed | Déclenché lorsqu’une sortie est distribuée |
release.deleted | Déclenché lorsqu’une sortie est supprimée ; l’API renvoie ensuite 404 pour cette sortie |
payment.statement_ready | Déclenché lorsqu’un relevé de paiement est prêt à être consulté |
transcode.completed | Déclenché lorsque le transcodage audio d’une piste se termine avec succès |
transcode.failed | Déclenché lorsque le transcodage d’une piste échoue ou se termine de façon incomplète |
distribution.outlet.status_changed | Déclenché à chaque transition de statut de distribution par outlet |
Vous pouvez sélectionner plusieurs événements pour un seul webhook, ou créer des webhooks séparés pour différents types d’événements.
Vous pouvez aussi lire cette liste de manière programmatique : GET /api/public/webhooks/event-types renvoie chaque événement accompagné d’un schéma data décrivant les clés et les types de son payload, afin que les consommateurs à schéma strict puissent élargir leur validation entrante à l’avance.
Gérer les webhooks
Section intitulée « Gérer les webhooks »Consulter vos webhooks
Section intitulée « Consulter vos webhooks »La liste des webhooks affiche :
| Colonne | Description |
|---|---|
| Name | Le nom que vous avez attribué au webhook |
| URL | Ou les notifications sont envoyées |
| Events | Nombre d’événements configurés |
| Status | Active ou Inactive |
| Success / Fail | Nombre de livraisons réussies et échouées |
| Last Triggered | Dernière activation du webhook |
Modifier un webhook
Section intitulée « Modifier un webhook »- Cliquez sur l’action Edit sur la ligne du webhook
- Modifiez le nom, l’URL ou les événements
- Cliquez sur Save
Activer / Désactiver
Section intitulée « Activer / Désactiver »Basculez le statut actif d’un webhook sans le supprimer :
- Active - Le webhook recevra les notifications
- Inactive - Le webhook est en pause, aucune notification envoyée
Supprimer un webhook
Section intitulée « Supprimer un webhook »- Cliquez sur l’action Delete sur la ligne du webhook
- Confirmez la suppression
Tester les webhooks
Section intitulée « Tester les webhooks »Avant de dépendre d’un webhook en production, testez-le :
- Cliquez sur l’action Test sur votre webhook
- LabelGrid envoie un payload de test à votre URL
- Vérifiez que votre endpoint a bien reçu et traite correctement
Consulter les journaux des webhooks
Section intitulée « Consulter les journaux des webhooks »Surveillez l’activité des webhooks et dépannez les problèmes :
- Cliquez sur l’action View Logs sur un webhook
- Consultez l’historique de toutes les livraisons du webhook
Détails des journaux
Section intitulée « Détails des journaux »Chaque entrée de journal affiche :
| Champ | Description |
|---|---|
| Event Type | L’événement qui a déclenché cette livraison |
| Response Status | Code de statut HTTP de votre serveur |
| Duration | Durée de la requête |
| Attempt | Numéro de la tentative de renvoi |
| Timestamp | Date et heure de la livraison |
Format du payload webhook
Section intitulée « Format du payload webhook »Lorsqu’un événement se produit, LabelGrid envoie une requête POST à votre URL avec un payload JSON :
{ "event": "delivery.completed", "event_id": "5f0c8a52-3d1e-4b7a-9c2f-6e8d1a4b7c90", "timestamp": "2026-05-05T10:00:00+00:00", "webhook_id": "123", "data": { // Donnees specifiques a l'evenement }}Le champ timestamp utilise le format ISO 8601. webhook_id est l’ID de votre webhook configuré (il correspond au header X-Webhook-Id).
event_id identifie l’événement lui-même, et non la tentative de livraison. Chaque renvoi d’une livraison porte le même event_id, tout comme chaque webhook auquel l’événement est envoyé. Il correspond au header X-Webhook-Event-Id. Les exemples de payload par événement ci-dessous l’omettent pour rester courts.
Payloads des événements
Section intitulée « Payloads des événements »La structure de l’objet data dépend du type d’event. Tous les types de champs ci-dessous sont des types JSON tels que sérialisés dans le payload.
delivery.completed
Section intitulée « delivery.completed »Déclenché une fois par outlet lorsqu’une livraison de sortie atteint un état de succès terminal.
{ "event": "delivery.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete" }}| Champ | Type | Description |
|---|---|---|
distro_queue_id | integer | ID interne de la file pour cette tentative de livraison |
release_id | integer | La sortie qui a été livrée |
label_id | integer | Le label propriétaire de la sortie, pour router l’événement sans requête supplémentaire |
release_cat | string | null | Votre référence de catalogue de sortie |
outlet_id | integer | null | L’ID de l’outlet de destination |
outlet_name | string | null | Nom lisible de l’outlet (par exemple, "Spotify") |
status | string | Toujours "complete" pour cet événement |
delivery.failed
Section intitulée « delivery.failed »Déclenché une fois par outlet lorsqu’une livraison de sortie atteint un état d’échec terminal. Même payload que delivery.completed plus un champ message.
{ "event": "delivery.failed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "error", "message": "Outlet rejected the delivery: missing ISRC." }}| Champ | Type | Description |
|---|---|---|
status | string | L’un de error, fault, rejected, batch_exception |
message | string | null | Raison de l’échec depuis l’outlet ou le pipeline de distribution |
takedown.completed
Section intitulée « takedown.completed »Déclenché une fois par outlet lorsqu’une demande de retrait réussit. Même forme que delivery.completed plus un indicateur takedown: true.
{ "event": "takedown.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete", "takedown": true }}release.distributed
Section intitulée « release.distributed »Déclenché une fois par sortie lorsque la sortie passe à l’état de livraison distributed. Ne se déclenche que sur la transition vers distributed, pas sur les enregistrements suivants pendant que la sortie est déjà distribuée.
{ "event": "release.distributed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "delivery_status": "distributed" }}release.deleted
Section intitulée « release.deleted »Déclenché une seule fois lorsqu’une sortie est supprimée. Après cet événement, l’API renvoie 404 pour cette sortie : n’essayez donc pas de la récupérer à nouveau.
{ "event": "release.deleted", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "barcode_number": "0123456789012", "release_cat": "ABC123", "release_title": "Summer EP" }}release.review.status_changed
Section intitulée « release.review.status_changed »Déclenché chaque fois qu’une sortie passe d’un état de révision à un autre.
{ "event": "release.review.status_changed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "previous_status": "to_review", "new_status": "approved" }}| Champ | Type | Description |
|---|---|---|
previous_status | string | Statut précédent. L’un de draft, to_review, approved, rejected, require_changes, audit, pending_customer_review |
new_status | string | Nouveau statut. Même ensemble de valeurs |
review_issues | array (optionnel) | Présent uniquement sur les transitions vers require_changes et rejected : les problèmes qui demandent votre attention. La clé est omise sur toute autre transition, ne considérez donc pas qu’elle est toujours présente |
track.review.status_changed
Section intitulée « track.review.status_changed »Déclenché chaque fois qu’une piste passe d’un état de révision à un autre, uniquement lorsque le statut change réellement.
{ "event": "track.review.status_changed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "track_id": 456, "release_id": 789, "label_id": 321, "previous_status": "to_review", "new_status": "require_changes" }}| Champ | Type | Description |
|---|---|---|
track_id | integer | La piste dont le statut de révision a changé |
previous_status | string | Statut précédent. Mêmes valeurs que pour release.review.status_changed |
new_status | string | Nouveau statut. Même ensemble de valeurs |
release.preflight.report_ready
Section intitulée « release.preflight.report_ready »Déclenché lorsque le rapport de qualité Preflight QC d’une sortie en mise en attente avant révision est prêt à être récupéré. Nécessite l’option Preflight QC sur votre compte.
{ "event": "release.preflight.report_ready", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "generated_at": "2026-07-07T09:58:12+00:00", "profile": { "name": "quality_report", "version": 2 }, "counts": { "blocking": 1, "informational": 2, "requires_feedback": 1 } }}| Champ | Type | Description |
|---|---|---|
release_id | integer | La sortie à laquelle appartient le rapport |
label_id | integer | Le label propriétaire de la sortie |
release_cat | string | null | Votre référence de catalogue de sortie |
release_title | string | null | Le titre de la sortie |
generated_at | string | Quand les vérifications se sont terminées (ISO 8601). Correspond au report.generated_at de l’endpoint du rapport de qualité |
profile | object | Le profil de qualité selon lequel les décomptes ont été calculés : {name, version} |
counts | object | Uniquement des décomptes agrégés : {blocking, informational, requires_feedback}. Le décompte requires_feedback recoupe les deux autres |
stream_radar.flag_created
Section intitulée « stream_radar.flag_created »Déclenché lorsqu’un signalement Stream Radar est créé, soit un signalement entièrement nouveau, soit un signalement précédemment résolu qui rouvre sur une nouvelle détection. Nécessite l’option Stream Radar sur votre compte. Le champ transition distingue les deux cas : published pour un nouveau signalement, reopened pour un signalement redevenu actif.
{ "event": "stream_radar.flag_created", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "flag_id": 4501, "dsp": "SPOTIFY", "isrc": "USRC12345678", "release_id": 789, "track_id": 654, "severity": "high", "status": "active", "transition": "published", "first_detected_at": "2026-07-06T00:00:00+00:00", "last_detected_at": "2026-07-07T00:00:00+00:00", "estimated_affected_streams": 12500, "published_at": "2026-07-07T09:58:12+00:00", "resolved_at": null }}| Champ | Type | Description |
|---|---|---|
flag_id | integer | L’identifiant stable du signalement ; correspond à id sur les endpoints Stream Radar |
dsp | string | La plateforme sur laquelle le motif a été observé (p. ex. SPOTIFY) |
isrc | string | L’ISRC de l’enregistrement concerne |
release_id | integer | La sortie à laquelle appartient l’enregistrement |
track_id | integer | null | La piste précise, lorsque l’ISRC correspond sans ambiguïté à l’une de vos pistes |
severity | string | medium ou high |
status | string | active pour cet événement |
transition | string | published pour un nouveau signalement, reopened lorsqu’un signalement résolu est redevenu actif |
first_detected_at | string | null | Quand le motif a été observé pour la première fois pour cette piste et cette plateforme (ISO 8601) |
last_detected_at | string | null | La détection la plus récente (ISO 8601) |
estimated_affected_streams | integer | null | Une estimation du nombre d’écoutes impliquées |
published_at | string | Quand le signalement vous a été remonté pour la première fois (ISO 8601) |
resolved_at | string | null | null tant que le signalement est actif |
stream_radar.flag_resolved
Section intitulée « stream_radar.flag_resolved »Déclenché lorsqu’un signalement Stream Radar se résout parce que les détections ont cessé. Nécessite l’option Stream Radar. Mêmes champs que stream_radar.flag_created (sans transition), avec status réglé sur resolved et resolved_at renseigné.
{ "event": "stream_radar.flag_resolved", "timestamp": "2026-07-14T10:00:00+00:00", "webhook_id": "123", "data": { "flag_id": 4501, "dsp": "SPOTIFY", "isrc": "USRC12345678", "release_id": 789, "track_id": 654, "severity": "high", "status": "resolved", "first_detected_at": "2026-07-06T00:00:00+00:00", "last_detected_at": "2026-07-12T00:00:00+00:00", "estimated_affected_streams": 18700, "published_at": "2026-07-07T09:58:12+00:00", "resolved_at": "2026-07-14T09:55:03+00:00" }}payment.statement_ready
Section intitulée « payment.statement_ready »Déclenché lorsqu’un relevé de paiement est généré et prêt à être consulté.
{ "event": "payment.statement_ready", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "payment_request_id": 1024, "invoice_number": "INV-2026-001", "period": "2026-04-30", "amount": 1234.56, "total_due_usd": 1234.56, "currency": "USD" }}| Champ | Type | Description |
|---|---|---|
payment_request_id | integer | ID interne de la demande de paiement |
invoice_number | string | Référence de facture pour le relevé |
period | string | null | Date de fin de période (date ISO 8601, YYYY-MM-DD) |
amount | number | Montant du relevé dans la devise currency |
total_due_usd | number | Total du relevé converti en USD |
currency | string | Code de devise ISO 4217 (par défaut USD) |
transcode.completed et transcode.failed
Section intitulée « transcode.completed et transcode.failed »Déclenché lorsque le transcodage audio d’une piste se termine. transcode.completed se déclenche en cas de succès ; transcode.failed se déclenche lorsque le transcodage échoue ou se termine de façon incomplète. Les deux partagent la même forme de payload.
{ "event": "transcode.completed", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "track_id": 654, "transcoder_queue_id": 987, "status": "complete", "status_message": "transcode_complete", "files": [ { "asset_type_id": 2, "status": "complete" } ] }}| Champ | Type | Description |
|---|---|---|
release_id | integer | La sortie à laquelle appartient la piste |
label_id | integer | Le label propriétaire de la sortie |
track_id | integer | La piste qui a été transcodée |
transcoder_queue_id | integer | ID interne de la file de transcodage |
status | string | Statut brut de la file : complete, error ou incomplete |
status_message | string | Code de motif sûr et énuméré : transcode_complete, transcode_error ou transcode_incomplete |
files | array | Détail par fichier pour la piste : {asset_type_id, status} par fichier transcode |
distribution.outlet.status_changed
Section intitulée « distribution.outlet.status_changed »Déclenché à chaque transition de statut de distribution par outlet (par exemple scheduled → transcoding → batched → complete), pas seulement les transitions terminales couvertes par delivery.completed, delivery.failed et takedown.completed. Cet événement est bavard par nature : abonnez-vous à lui uniquement si vous voulez la progression complète par outlet.
{ "event": "distribution.outlet.status_changed", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "previous_status": "transcoding", "status": "batched" }}| Champ | Type | Description |
|---|---|---|
distro_queue_id | integer | ID interne de la file pour cette livraison |
release_id | integer | La sortie en cours de distribution |
label_id | integer | Le label propriétaire de la sortie |
release_cat | string | null | Votre référence de catalogue de sortie |
outlet_id | integer | null | L’ID de l’outlet de destination |
outlet_name | string | null | Nom lisible de l’outlet |
previous_status | string | null | Le statut précédent ; null lorsque la ligne n’avait aucun statut précédent reconnu |
status | string | Le nouveau statut |
Vérification des signatures webhook
Section intitulée « Vérification des signatures webhook »Chaque livraison de webhook est signée afin que vous puissiez vérifier qu’elle provient bien de LabelGrid. Vérifiez toujours la signature avant de traiter l’événement.
Headers de la requête
Section intitulée « Headers de la requête »Chaque requête POST de webhook inclut ces headers :
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 du corps brut de la requête, en hexadécimal minuscule, sans préfixe d’algorithme |
X-Webhook-Timestamp | Copie de commodité de la propriété timestamp du corps. Non couverte par la signature — ne l’utilisez jamais pour décider si une livraison est récente. |
X-Webhook-Event | Identifiant d’événement (par exemple, delivery.completed) |
X-Webhook-Id | L’ID de la configuration du webhook qui reçoit la livraison (et non un ID propre à chaque livraison) |
X-Webhook-Event-Id | La valeur event_id de l’événement, identique à chaque renvoi |
User-Agent | LabelGrid-Webhooks/1.0 |
Content-Type | application/json |
Algorithme
Section intitulée « Algorithme »- Algorithme : HMAC-SHA256
- Encodage : Hexadécimal minuscule
- Préfixe : Aucun — la valeur est juste le digest hex, pas
sha256=... - Contenu signé : Le corps JSON brut complet de la requête — et rien d’autre. Aucun header n’est signé.
Le corps porte sa propre propriété timestamp : cette valeur est donc protégée par la signature. Le header X-Webhook-Timestamp n’en est qu’une copie, envoyée par commodité, et un attaquant qui intercepte une livraison peut modifier le header à volonté sans invalider la signature. Les contrôles de fraîcheur doivent donc lire timestamp dans le corps parsé, jamais dans le header.
Recette de vérification
Section intitulée « Recette de vérification »- Lisez le corps brut de la requête avant tout parsing ou transformation JSON. Re-sérialiser le JSON parse peut produire des octets différents et casser la signature.
- Calculez
HMAC-SHA256(corps_brut, votre_secret_webhook)et prenez le digest hexadécimal minuscule. - Comparez avec
X-Webhook-Signatureen utilisant une comparaison à temps constant. Arrêtez-vous là si elle ne correspond pas. - Ce n’est qu’à ce moment que vous parsez le corps, et vous rejetez la requête si sa propriété
timestampest plus ancienne que votre fenêtre de tolérance de rejeu — nous suggérons 5 minutes. Comme cette valeur est signée, un attaquant ne peut pas la rafraîchir pour faire passer une livraison interceptée pour récente.
Chaque renvoi est signé à nouveau avec un nouveau timestamp : une fenêtre de 5 minutes ne rejette donc jamais un renvoi légitime, aussi tard qu’il arrive dans le calendrier de backoff.
Exemple PHP
Section intitulée « Exemple PHP »$rawBody = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $signature)) { http_response_code(401); exit('Signature invalide');}
// Ne parsez qu'une fois les octets authentifiés.$payload = json_decode($rawBody, true);
// La fraîcheur vient de l'horodatage SIGNÉ dans le corps,// jamais du header X-Webhook-Timestamp.if (! isset($payload['timestamp']) || abs(time() - strtotime($payload['timestamp'])) > 300) { http_response_code(401); exit('Livraison obsolete');}
// ... traiter l'événement (voir « Gérer les livraisons répétées » ci-dessous)http_response_code(200);Exemple Node.js
Section intitulée « Exemple Node.js »const crypto = require('crypto');
// Express : capturer le corps brut AVANT tout middleware JSONapp.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const rawBody = req.body; // Buffer const signature = req.header('X-Webhook-Signature') || '';
const expected = crypto .createHmac('sha256', webhookSecret) .update(rawBody) .digest('hex');
const sigBuf = Buffer.from(signature, 'hex'); const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) { return res.status(401).send('Signature invalide'); }
// Ne parsez qu'une fois les octets authentifiés. const payload = JSON.parse(rawBody.toString('utf8'));
// La fraîcheur vient de l'horodatage SIGNÉ dans le corps, // jamais du header X-Webhook-Timestamp. const sentAt = new Date(payload.timestamp).getTime();
if (Number.isNaN(sentAt) || Math.abs(Date.now() - sentAt) > 5 * 60 * 1000) { return res.status(401).send('Livraison obsolete'); }
// ... traiter l'événement (voir « Gérer les livraisons répétées » ci-dessous) res.sendStatus(200);});Exemple Python
Section intitulée « Exemple Python »import hmac, hashlib, jsonfrom datetime import datetime, timezone
raw_body = request.get_data() # Flask : bytes, avant tout parsing JSONsignature = request.headers.get('X-Webhook-Signature', '')
expected = hmac.new( webhook_secret.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature): return ('Signature invalide', 401)
payload = json.loads(raw_body) # parser seulement une fois les octets authentifiés
delivery_time = datetime.fromisoformat(payload['timestamp']) # la valeur SIGNÉE, jamais le headerif abs((datetime.now(timezone.utc) - delivery_time).total_seconds()) > 300: return ('Livraison obsolete', 401)
return ('', 200) # traiter l'evenement puis acquitterPièges courants
Section intitulée « Pièges courants »- Contrôler la fraîcheur avec le header
X-Webhook-Timestamp. Le header n’est pas signé. Quiconque intercepte une livraison peut rejouer indéfiniment le même corps et la même signature avec une valeur de header récente et passer ainsi un contrôle fondé sur le header. Lisez plutôttimestampdans le corps parsé — cette valeur, elle, est signée. - Re-sérialiser le corps avant de le hacher. Les frameworks qui parsent automatiquement le JSON (Express
express.json(), le corps de requête par défaut de Laravel) perdent les octets originaux. Capturez d’abord le corps brut. - Utiliser une comparaison qui n’est pas à temps constant (
==,===). Susceptible aux attaques temporelles — utilisez toujourshash_equals(PHP),crypto.timingSafeEqual(Node),hmac.compare_digest(Python), ou l’équivalent dans votre langage. - S’attendre à un préfixe
sha256=. La valeur du header est juste le digest hex sans préfixe. - Omettre le contrôle de fraîcheur. Sans lui, une livraison interceptée peut être rejouée indéfiniment contre votre endpoint.
- Prendre
X-Webhook-Idpour un identifiant de livraison. Il identifie la configuration du webhook, pas la livraison individuelle, et il n’est pas signé non plus.
Gérer les livraisons répétées
Section intitulée « Gérer les livraisons répétées »La livraison des webhooks se fait au moins une fois : une livraison que votre endpoint a bel et bien traitée peut arriver de nouveau si votre réponse 2xx s’est perdue ou est arrivée après le timeout de 10 secondes et que LabelGrid la renvoie. Vérifier la signature prouve qu’une requête est authentique — cela ne prouve pas que vous ne l’avez pas déjà traitée.
Utilisez event_id comme clé d’idempotence : il reste le même à chaque renvoi d’un événement. Enregistrez-le lorsque vous traitez un événement et ignorez toute livraison dont vous avez déjà enregistré l’event_id. Comme il fait partie du corps signé, une signature valide le garantit aussi.
N’utilisez pas la signature ni le timestamp comme clé. Chaque tentative est signée à nouveau au moment de l’envoi : le renvoi d’un événement que vous avez déjà traité arrive donc avec un timestamp différent et une signature différente — la clé doit venir des identifiants propres à l’événement.
Combinez cela avec le contrôle de fraîcheur ci-dessus : la fraîcheur borne la durée pendant laquelle une livraison interceptée reste rejouable, et l’idempotence rend une répétition inoffensive, qu’elle vienne d’un renvoi ou d’un attaquant à l’intérieur de la fenêtre.
Limites et fiabilité
Section intitulée « Limites et fiabilité »Limites de requête
Section intitulée « Limites de requête »| Limite | Valeur |
|---|---|
| Timeout de requête | 10 secondes |
| Taille maximale du payload | 64 Ko |
| Maximum de webhooks par utilisateur | 10 |
Si votre endpoint ne répond pas dans les 10 secondes, la livraison est considérée comme un échec et renvoyée.
Calendrier de renvoi
Section intitulée « Calendrier de renvoi »Si votre endpoint retourne un statut non-2xx ou expire, LabelGrid renvoie avec un backoff exponentiel :
| Tentative | Attente avant le renvoi |
|---|---|
| 1 → 2 | 30 secondes |
| 2 → 3 | 1 minute |
| 3 → 4 | 2 minutes |
| 4 → 5 | 4 minutes |
| 5 → 6 | 8 minutes |
| 6 → 7 | 16 minutes |
| 7 → 8 | 32 minutes |
| 8 → 9 | 64 minutes |
| 9 → 10 | 128 minutes |
Chaque intervalle inclut 0 à 30 secondes de jitter. Après 10 tentatives (~4,5 heures de temps total écoulé), la livraison est journalisée comme définitivement échouée et n’est plus renvoyée.
Désactivation automatique
Section intitulée « Désactivation automatique »Si l’endpoint d’un webhook échoue de manière répétée — des livraisons échouées consécutives sans aucune livraison réussie entre elles —, LabelGrid désactivé automatiquement le webhook pour cesser de réessayer un endpoint qui ne peut manifestement pas recevoir d’événements. Le compteur d’échecs est remis à zéro à chaque livraison réussie, de sorte qu’un incident ponctuel ne désactivé jamais un webhook ; seul un échec continu et ininterrompu le fait.
Lorsqu’un webhook est désactivé de cette manière, son propriétaire reçoit un email. L’email indique le nom du webhook et l’URL de son endpoint, ainsi que le type d’échec ayant déclenché la désactivation — par exemple un délai de connexion dépassé ou des erreurs HTTP répétées.
La réactivation se fait en autonomie : corrigez votre endpoint, puis réactivez le webhook depuis Webhooks dans le menu du compte (votre avatar, en haut à droite). Réactiver un webhook désactivé remet son compteur d’échecs à zéro. La liste des webhooks affiche le statut actif et le nombre d’échecs actuel de chaque webhook, ce qui vous permet de repérer un endpoint défaillant d’un coup d’oeil.
Bonnes pratiques pour la fiabilité
Section intitulée « Bonnes pratiques pour la fiabilité »- Retournez une réponse 2xx rapidement (en moins de 10 secondes)
- Traitez les données de manière asynchrone après avoir acquitté
- Vérifiez la signature de chaque requête (voir Vérification des signatures webhook)
- Rendez votre gestionnaire idempotent — la livraison se fait au moins une fois (voir Gérer les livraisons répétées)
- Surveillez votre compteur d’échecs dans la liste des webhooks
- Consultez les journaux de livraison lorsque vous enquêtez sur des événements manques
Cas d’utilisation
Section intitulée « Cas d’utilisation »Notifications automatisées
Section intitulée « Notifications automatisées »- Envoyer des messages Slack lorsque les sorties sont en ligne
- Emailer votre équipe en cas d’échec de livraison
- Mettre à jour les tableaux de bord internes
Automatisation de workflow
Section intitulée « Automatisation de workflow »- Déclencher des campagnes marketing lorsque les sorties sont distribuées
- Mettre à jour votre site web lorsque du nouveau contenu est disponible
- Synchroniser le statut avec des outils de gestion de projet externes
Surveillance et alertes
Section intitulée « Surveillance et alertes »- Obtenir des alertes instantanées en cas d’échec de livraison
- Suivre la progression de la distribution en temps réel
- Surveiller les changements de statut de révision
Dépannage
Section intitulée « Dépannage »Le webhook ne reçoit pas d’événements
Section intitulée « Le webhook ne reçoit pas d’événements »- Vérifiez le statut - Le webhook est-il Active ?
- Vérifiez l’URL - L’endpoint est-il accessible depuis internet ?
- Vérifiez les événements - Les bons événements sont-ils sélectionnés ?
- Consultez les journaux - Des erreurs sont-elles enregistrées ?
Nombre d’échecs élevé
Section intitulée « Nombre d’échecs élevé »- Vérifiez votre endpoint - Retourne-t-il 200 OK ?
- Vérifiez le temps de réponse - Répond-il dans le délai imparti ?
- Examinez les messages d’erreur - Qu’est-ce qui échoue ?
- Testez manuellement - Envoyez un webhook de test
Renouveler le secret
Section intitulée « Renouveler le secret »Si votre secret webhook est compromis ou perdu, renouvelez-le. LabelGrid ne peut pas vous afficher à nouveau le secret actuel : le renouvellement est le seul moyen d’en obtenir un que vous puissiez copier.
- Ouvrez le menu du webhook (⋮) et cliquez sur Edit
- À côté de Webhook Secret, cliquez sur Rotate
- Cliquez sur Rotate secret pour confirmer
- Cliquez sur Copy dans l’encadré Copy it now et enregistrez le nouveau secret dans votre application
- Cliquez sur I’ve copied it
L’ancien secret cesse de fonctionner dès le renouvellement, et la vérification des signatures sur votre endpoint échoue tant que vous n’avez pas enregistré le nouveau. Mettez donc à jour votre endpoint dans la foulée. Le nouveau secret s’applique immédiatement : inutile de cliquer sur Save Changes.
LabelGrid n’affiche le nouveau secret qu’une seule fois. Si vous essayez de fermer la fenêtre avant d’avoir cliqué sur I’ve copied it, LabelGrid vous demande d’abord de confirmer, car la fermeture est votre dernière occasion de le copier. Si vous le perdez plus tard, renouvelez-le une nouvelle fois.
Besoin d’aide ?
Section intitulée « Besoin d’aide ? »Si vous avez des questions sur les webhooks, contactez notre équipe support.
Vous n’utilisez pas encore LabelGrid ?
Tout ce que vous venez de lire est disponible sur notre plateforme.
Découvrez ce que LabelGrid peut faire →