Developers
Webhooks
Op deze pagina
Een endpoint inrichten
- Voeg een https-route toe aan je server die de ruwe body leest en binnen 10 seconden
2xxantwoordt. Doe het werk na het antwoord. - Maak het endpoint aan met de API, of vraag het ons team. Bewaar het
whsec_-secret uit het antwoord: het wordt één keer getoond. - Verifieer elk verzoek (hieronder) en ontdubbel daarna op de event-
id. - Stuur jezelf een testevent van elk type dat je verwerkt.
Het event
Elk event heeft dezelfde envelop. data bevat de eigen velden van het type.
idstring- Begint met evt_. Hetzelfde bij elke herhaling, en in de webhook-id-header.
typestring- Een van de events hieronder.
api_versionstring- De versie waarin data is geschreven: die van je endpoint.
created_atstring- Wanneer het gebeurde, wat eerder kan zijn dan je het ontvangt.
testboolean- true voor testevents.
dataobject- Id's om het volledige object mee op te halen, en wat er veranderde.
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
}
}Events
order.created
Er is een bestelling bij ons binnengekomen, uit je Shopify-winkel of via de API. Betaald, of 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
Een bestelling is betaald uit je saldo. Wordt ook verstuurd voor bestellingen die op een opwaardering wachtten.
{
"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
We hebben de verkoper betaald. De regels zijn with_seller tot ze in ons magazijn aankomen.
{
"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
De artikelen zijn in ons magazijn, gecontroleerd, met hun foto's. Daarna volgt het inpakken.
{
"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
Een pakket is vertrokken met tracking; de verzendkosten zijn definitief. De winkel waar de bestelling vandaan kwam, heeft de tracking ook.
{
"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
Een verkoper heeft een prijs gewijzigd, en wat je syncregel daarmee deed: prijs aangepast, gepauzeerd of gemeld.
{
"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
De verkoper is door een variant heen en je schap heeft er geen: we zetten hem op je kanalen op 0.
{
"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
Onze controle vond een probleem met een artikel: verkeerde kleur of maat, schade, een ontbrekend onderdeel. Kies vóór respond_by; zonder antwoord houden we het vast en schrijven we je.
{
"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"
}
}Handtekeningen
Afleveringen worden ondertekend volgens Standard Webhooks, dus een library die je misschien al gebruikt, verifieert ze. Bij elke aflevering horen drie headers:
| Header | Wat het bevat |
|---|---|
| webhook-id | De event-id. |
| webhook-timestamp | Wanneer we deze poging verstuurden, in Unix-seconden. |
| webhook-signature | v1, dan een komma en de base64-HMAC-SHA256 van id.timestamp.body met je secret. Tijdens een rotatie twee ervan, gescheiden door een spatie. |
De sleutel is je secret na whsec_, base64-gedecodeerd. Onderteken de ruwe body precies zoals hij binnenkwam; weiger een tijdstempel die meer dan vijf minuten afwijkt, zodat een onderschept verzoek niet opnieuw kan worden afgespeeld.
Een aflevering verifiëren
Met de library standardwebhooks, of in een paar eigen regels:
Herhalingen en volgorde
Alles behalve een 2xx binnen 10 seconden is een mislukking, en we proberen het opnieuw, elke keer met dezelfde webhook-id en een nieuwe tijdstempel en handtekening:
| Poging | Na de vorige |
|---|---|
| 1 | Meteen |
| 2 | 5 seconden |
| 3 | 5 minuten |
| 4 | 30 minuten |
| 5 | 2 uur |
| 6 | 5 uur |
| 7 | 10 uur |
| 8 | 10 uur, de laatste |
- Na de laatste poging is de aflevering mislukt. Stuur hem opnieuw vanuit de lijst met afleveringen.
- Een endpoint waarvan vijf dagen lang elke aflevering mislukte, wordt uitgeschakeld nadat we je hebben gemaild. Schakel je hem weer in, dan sturen we wat hij de afgelopen drie dagen heeft gemist.
- Events kunnen dubbel en in de verkeerde volgorde aankomen. Ontdubbel op id, en als de volgorde telt, haal het object op en vertrouw op de status daarvan in plaats van die van het event.