naskel
GuidesMis à jour le

Webhooks sortants

Recevoir une notification HTTP à chaque événement, vérifier sa signature et gérer les nouvelles tentatives.

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": { }
}
ChampTypeDescription
eventstringLe nom de l'événement (voir la liste)
occurredAtstringDate ISO 8601 en UTC
tenantIdstringL'identifiant de votre espace
dataobjetLes données de l'événement (dépend de event)

Les en-têtes

En-têteValeur
Content-Typeapplication/json
X-Naskel-EventLe nom de l'événement
X-Naskel-SignatureLa signature HMAC (voir ci-dessous)
User-AgentNaskel-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
Facturesinvoice.created, invoice.sent, invoice.paid, invoice.cancelled, invoice.overdue
Devisquote.created, quote.sent, quote.signed, quote.refused, quote.expired
Contactscontact.created, contact.updated
Entreprisescompany.created
Opportunitésopportunity.created, opportunity.won, opportunity.lost
Stockstock.movement, stock.adjusted, stock.low, stock.out
Séquencessequence.completed, sequence.stopped_replied
Banquebank_transaction.reconciled
Commentairescomment.created, comment.mentioned
Testwebhook.test

Le contenu des événements de stock

Les quatre événements de stock portent, dans data, l'état du produit concerné.

ÉvénementQuand il part
stock.movementUn mouvement de stock est enregistré (entrée ou sortie)
stock.adjustedUn stock est ajusté (inventaire ou correction)
stock.lowUn produit passe sous son seuil d'alerte
stock.outUn 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.low part quand 0 < stockQuantity ≤ stockThreshold (passage sous le seuil) ;
  • stock.out part quand stockQuantity ≤ 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).

Voir aussi