Webhooks sortants
Recevoir une notification HTTP à chaque événement, vérifier sa signature et gérer les nouvelles tentatives.
French only for now
This page has not been translated yet. You are reading the French version.
Les webhooks permettent à Naskel de prévenir vos propres systèmes en temps réel : à chaque événement (une facture payée, un devis signé, une opportunité gagnée), Naskel envoie une requête HTTP POST à l'URL que vous avez configurée.
Le format des messages
Le corps est un JSON avec quatre champs :
{
"event": "invoice.paid",
"occurredAt": "2026-06-02T15:30:45.123Z",
"tenantId": "clq1abc2xyz123",
"data": { }
}
| Champ | Type | Description |
|---|---|---|
event | string | Le nom de l'événement (voir la liste) |
occurredAt | string | Date ISO 8601 en UTC |
tenantId | string | L'identifiant de votre espace |
data | objet | Les données de l'événement (dépend de event) |
Les en-têtes
| En-tête | Valeur |
|---|---|
Content-Type | application/json |
X-Naskel-Event | Le nom de l'événement |
X-Naskel-Signature | La signature HMAC (voir ci-dessous) |
User-Agent | Naskel-Webhooks/1.0 |
Vérifier la signature
Chaque requête est signée pour que vous puissiez vérifier qu'elle vient bien de Naskel.
- En-tête :
X-Naskel-Signature. - Algorithme : HMAC-SHA256, résultat en hexadécimal.
- Ce qui est signé : le corps JSON brut, exactement tel qu'il est reçu.
- Clé : le secret du webhook, une chaîne de 32 caractères hexadécimaux fournie à la création du webhook.
Calculez la signature de votre côté et comparez-la à l'en-tête :
const crypto = require("crypto");
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody) // le corps brut, non reparsé
.digest("hex");
if (expected !== req.headers["x-naskel-signature"]) {
return res.status(401).end();
}
Attention
Signez le corps brut reçu, pas un JSON reconstruit. Re-sérialiser l'objet (espaces, ordre des clés) changerait la signature.
Les événements
| Domaine | Événements |
|---|---|
| Factures | invoice.created, invoice.sent, invoice.paid, invoice.cancelled, invoice.overdue |
| Devis | quote.created, quote.sent, quote.signed, quote.refused, quote.expired |
| Contacts | contact.created, contact.updated |
| Entreprises | company.created |
| Opportunités | opportunity.created, opportunity.won, opportunity.lost |
| Stock | stock.movement, stock.adjusted, stock.low, stock.out |
| Séquences | sequence.completed, sequence.stopped_replied |
| Banque | bank_transaction.reconciled |
| Commentaires | comment.created, comment.mentioned |
| Test | webhook.test |
Le contenu des événements de stock
Les quatre événements de stock portent, dans data, l'état du produit concerné.
| Événement | Quand il part |
|---|---|
stock.movement | Un mouvement de stock est enregistré (entrée ou sortie) |
stock.adjusted | Un stock est ajusté (inventaire ou correction) |
stock.low | Un produit passe sous son seuil d'alerte |
stock.out | Un produit tombe en rupture (stock à zéro) |
Pour un mouvement, data reprend la variation et l'état après mouvement :
// stock.movement
{ "productId": "…", "delta": -2, "after": 8, "reason": "…", "refKind": "…", "refId": "…" }
Pour un ajustement, data reprend l'avant/après et le delta calculé :
// stock.adjusted
{ "productId": "…", "before": 12, "after": 20, "delta": 8 }
Pour une alerte de stock faible ou de rupture, data identifie le produit, son nom, sa quantité courante et son seuil (stockQuantity et stockThreshold peuvent être null) :
// stock.low / stock.out
{ "productId": "…", "name": "Câble HDMI 2m", "stockQuantity": 3, "stockThreshold": 5 }
stock.lowpart quand0 < stockQuantity ≤ stockThreshold(passage sous le seuil) ;stock.outpart quandstockQuantity ≤ 0(rupture).
Émis une seule fois, à la transition
Ces alertes ne partent qu'à l'apparition de la condition, pas à chaque mouvement suivant (anti-spam). Quand le produit repasse au-dessus de son seuil, l'alerte est résolue automatiquement, sans événement de résolution.
À savoir
Un même mouvement peut déclencher plusieurs événements : une sortie qui fait passer le produit sous son seuil émet stock.movement et stock.low (ou stock.out s'il atteint zéro). Voir le guide Stock et inventaire côté application, et les ressources API Produits et Mouvements de stock.
Les nouvelles tentatives
La livraison est au mieux (best-effort), avec des relances en cas d'échec :
- Délai d'attente de la réponse : 10 secondes.
- Jusqu'à 5 tentatives.
- Espacement croissant : 1, 2, 4, 8 puis 16 minutes.
- Une file est traitée toutes les 5 minutes.
- Après le dernier échec, la livraison est marquée abandonnée.
Une livraison est en attente, réussie, en échec, ou abandonnée.
À savoir
Répondez vite (code 2xx) et traitez le message de façon asynchrone. Comme un même événement peut être livré plusieurs fois, rendez votre traitement idempotent (sans dommage en cas de doublon).