Contents

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:

EventScopeWhen
order.createdorders:readA new order arrives from the sales channel or through POST /orders, or the merchant creates a replacement order.
order.status_changedorders:readThe order's public status changes.
shipment.shippedorders:readA shipment leaves (one event per shipment).
return.createdreturns:readA return is opened, or arrives at the warehouse unannounced.
return.status_changedreturns:readThe 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 2xx within 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 an id you 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_since as a safety net.