Order webhooks
Get notified when orders and returns change, instead of polling.
Instead of waiting for your next sync, Nucleo can call your server when an order or a return changes. Open the connection in Nucleo, write your Webhook URL in Settings (https only, on a public address) and save. GET /ping then shows webhooks.enabled: true and the events your key receives — those of its scopes:
| Event | Scope | When |
|---|---|---|
order.created | orders:read | A new order arrives from the sales channel or through POST /orders, or the merchant creates a replacement order. |
order.status_changed | orders:read | The order's public status changes. |
shipment.shipped | orders:read | A shipment leaves (one event per shipment). |
return.created | returns:read | A return is opened, or arrives at the warehouse unannounced. |
return.status_changed | returns:read | The return's public status changes. |
Each call is a POST with a small JSON body: id, type, created_at and data with the ids and numbers to read. It is a hint, not the data: read the order or the return with the API to get its current state.
Check the signature before you trust the body. X-Nucleo-Signature is t=<unix time>,v1=<hex>, where <hex> is the HMAC-SHA256 of <t>.<raw body> with your webhook secret:
import crypto from "node:crypto";
// rawBody: the exact bytes received, before any JSON parsing.
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 minutes
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}- Answer
2xxwithin 10 seconds and do the work afterwards. Any other answer, a redirect or a timeout is retried: 8 attempts in about an hour, then Nucleo gives up and alerts the merchant. - Every attempt carries the same
id: skip anidyou already handled. Events can arrive out of order, so always read the current state. - While the connection is paused no webhook is sent, and those events are not sent later. Keep the periodic sync with
updated_sinceas a safety net.