Développeurs
Webhooks
Sur cette page
Configurer un endpoint
- Ajoutez à votre serveur une route https qui lit le corps brut et répond
2xxen moins de 10 secondes. Faites le traitement après la réponse. - Créez l’endpoint avec l’API, ou demandez à notre équipe. Enregistrez le secret
whsec_qu’il renvoie : il n’est affiché qu’une fois. - Vérifiez chaque requête (ci-dessous), puis dédoublonnez sur l’
idde l’événement. - Envoyez-vous un événement de test de chaque type que vous traitez.
L’événement
Chaque événement a la même enveloppe. data contient les champs propres au type.
idstring- Commence par evt_. Identique à chaque nouvelle tentative, et dans l’en-tête webhook-id.
typestring- L’un des événements ci-dessous.
api_versionstring- La version dans laquelle data est écrit : celle de votre endpoint.
created_atstring- Quand cela s’est produit, ce qui peut précéder sa réception.
testboolean- true pour les événements de test.
dataobject- Les identifiants pour récupérer l’objet complet, et ce qui a changé.
POST /hooks/ninjabuy HTTP/1.1
Host: acme-goods.com
Content-Type: application/json
User-Agent: NinjaBuy-Webhooks/1.0
webhook-id: evt_4m2x
webhook-timestamp: 1793685600
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "evt_4m2x",
"type": "order.shipped",
"api_version": "2026-11",
"created_at": "2026-11-03T06:00:00Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"shipment": "shp_5r8w",
"carrier": "YunExpress",
"tracking_number": "YT2630812345678",
"tracking_url": "https://t.17track.net/en#nums=YT2630812345678",
"shipping_charged": { "amount": "134.52", "currency": "CNY" },
"pushed_to_channel": true
}
}Événements
order.created
Une commande nous est parvenue, de votre boutique Shopify ou de l’API. Payée, ou awaiting_payment.
{
"id": "evt_1a7c",
"type": "order.created",
"api_version": "2026-11",
"created_at": "2026-11-01T09:14:02Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"source": "shopify",
"status": "buying",
"payment_status": "paid",
"lines": [
{
"line": "lin_9a2c",
"variant": "var_41hz",
"sku": "5187209554",
"quantity": 1
}
]
}
}order.paid
Une commande a été payée depuis votre solde. Déclenché aussi pour les commandes qui attendaient une recharge.
{
"id": "evt_1a9f",
"type": "order.paid",
"api_version": "2026-11",
"created_at": "2026-11-01T11:02:40Z",
"test": false,
"data": {
"order": "ord_3n8a",
"external_id": "1013",
"amount": { "amount": "57.00", "currency": "CNY" },
"paid_from": "balance",
"was_awaiting_payment": true
}
}order.bought
Nous avons payé le vendeur. Les lignes sont en with_seller jusqu’à leur arrivée dans notre entrepôt.
{
"id": "evt_2c4e",
"type": "order.bought",
"api_version": "2026-11",
"created_at": "2026-11-01T13:30:05Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"lines": [
{
"line": "lin_9a2c",
"sku": "5187209554",
"quantity": 1,
"status": "with_seller"
}
]
}
}order.checked
Les articles sont dans notre entrepôt, vérifiés, avec leurs photos. L’emballage suit.
{
"id": "evt_9w1c",
"type": "order.checked",
"api_version": "2026-11",
"created_at": "2026-11-02T03:20:11Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"fulfillment_order": "fo_7k2m",
"lines": [
{
"line": "lin_9a2c",
"qc": "passed",
"photos": [
"https://media.ninjabuy.com/qc/ord_3n7q/1.webp",
"https://media.ninjabuy.com/qc/ord_3n7q/2.webp"
]
}
]
}
}order.shipped
Un colis est parti avec un suivi ; les frais d’expédition sont définitifs. La boutique d’origine de la commande a aussi le suivi.
{
"id": "evt_4m2x",
"type": "order.shipped",
"api_version": "2026-11",
"created_at": "2026-11-03T06:00:00Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"shipment": "shp_5r8w",
"carrier": "YunExpress",
"tracking_number": "YT2630812345678",
"tracking_url": "https://t.17track.net/en#nums=YT2630812345678",
"shipping_charged": { "amount": "134.52", "currency": "CNY" },
"pushed_to_channel": true
}
}product.cost_changed
Un vendeur a changé un prix, et ce que votre règle de synchronisation a fait : prix recalculé, mise en pause ou notification.
{
"id": "evt_8q0d",
"type": "product.cost_changed",
"api_version": "2026-11",
"created_at": "2026-11-04T02:10:00Z",
"test": false,
"data": {
"product": "prd_1x0a",
"variant": "var_2b7k",
"sku": "4410923871",
"cost": {
"from": { "amount": "42.00", "currency": "CNY" },
"to": { "amount": "48.00", "currency": "CNY" }
},
"action": "repriced",
"price": {
"from": { "amount": "18.99", "currency": "USD" },
"to": { "amount": "20.99", "currency": "USD" }
},
"rule": "rule_default"
}
}product.out_of_stock
Le vendeur est en rupture sur une variante et votre étagère n’en a pas : nous la mettons à 0 sur vos canaux.
{
"id": "evt_2b9n",
"type": "product.out_of_stock",
"api_version": "2026-11",
"created_at": "2026-11-04T02:11:30Z",
"test": false,
"data": {
"product": "prd_8f2k",
"variant": "var_41j0",
"sku": "5187209555",
"warehouse_on_hand": 0,
"channels_updated": [
{
"channel": "shopify",
"shop": "acme-goods.myshopify.com",
"available": 0
}
]
}
}qc.issue
Notre contrôle a trouvé un problème sur un article : mauvaise couleur ou taille, dommage, pièce manquante. Choisissez avant respond_by ; sans réponse, nous le gardons et vous écrivons.
{
"id": "evt_6c3h",
"type": "qc.issue",
"api_version": "2026-11",
"created_at": "2026-11-02T03:20:11Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"fulfillment_order": "fo_7k2m",
"line": "lin_9a2c",
"sku": "5187209554",
"issue": "wrong_color",
"note": "Received grey, ordered black.",
"photos": ["https://media.ninjabuy.com/qc/ord_3n7q/3.webp"],
"choices": ["ship_anyway", "exchange", "refund"],
"respond_by": "2026-11-05T03:20:11Z"
}
}Signatures
Les livraisons sont signées selon Standard Webhooks : une bibliothèque que vous utilisez peut-être déjà les vérifie. Trois en-têtes accompagnent chaque livraison :
| En-tête | Ce qu’il contient |
|---|---|
| webhook-id | L’identifiant de l’événement. |
| webhook-timestamp | L’envoi de cette tentative, en secondes Unix. |
| webhook-signature | v1, puis une virgule et le HMAC-SHA256 en base64 de id.timestamp.body avec votre secret. Pendant un renouvellement, deux, séparés par une espace. |
La clé est votre secret après whsec_, décodé en base64. Signez le corps brut tel qu’il est arrivé ; refusez un horodatage décalé de plus de cinq minutes, pour qu’une requête interceptée ne puisse pas être rejouée.
Vérifier une livraison
Avec la bibliothèque standardwebhooks, ou en quelques lignes de votre cru :
Nouvelles tentatives et ordre
Tout ce qui n’est pas un 2xx en moins de 10 secondes est un échec, et nous réessayons, chaque fois avec le même webhook-id et un nouvel horodatage et une nouvelle signature :
| Tentative | Après la précédente |
|---|---|
| 1 | Immédiatement |
| 2 | 5 secondes |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 heures |
| 6 | 5 heures |
| 7 | 10 heures |
| 8 | 10 heures, la dernière |
- Après la dernière tentative, la livraison est en échec. Renvoyez-la depuis la liste des livraisons.
- Un endpoint dont toutes les livraisons ont échoué pendant cinq jours est désactivé, après un e-mail. Le réactiver envoie ce qu’il a manqué les trois derniers jours.
- Les événements peuvent arriver deux fois et dans le désordre. Dédoublonnez sur id et, quand l’ordre compte, récupérez l’objet et fiez-vous à son statut plutôt qu’à celui de l’événement.