Guide

Send orders to your ERP / accounting

Receive orders the moment they complete or are cancelled and write them into your system — no duplicates, nothing missed.

  1. 1Register a Sales webhook

    In the app: Settings › Apps & API › Webhooks → choose Sales → paste your URL → Verify and save → enable order.completed, order.cancelled, order.status_changed.

    Webhooks →order.completed →

  2. 2Verify the signature, answer 2xx fast

    Check X-NextX-Signature-256 on the raw body, return 200 right away and queue the ERP write on your side. Slow or failed answers are redelivered.

    Node.js (Express)
    import crypto from "crypto";
    
    app.post("/webhooks/orders", express.raw({ type: "application/json" }), (req, res) => {
      const event = JSON.parse(req.body.toString("utf8"));
      // the URL verification ping uses a one-time secret: answer it BEFORE checking the signature
      if (event.type === "endpoint.verification") return res.json({ challenge: event.challenge });
    
      const ts = req.header("X-NextX-Timestamp");
      const sig = req.header("X-NextX-Signature-256") ?? "";
      const expected = "sha256=" + crypto
        .createHmac("sha256", process.env.WEBHOOK_SECRET)
        .update(ts + "." + req.body.toString("utf8"))
        .digest("hex");
      const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
      if (!fresh || sig.length !== expected.length ||
          !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401);
    
      queue.add(event);          // write to the ERP in the background
      res.sendStatus(200);
    });

    Verify signatures →

  3. 3De-duplicate by event id

    An event can arrive more than once. Store processed ids (evt_…) and skip repeats. Use orderId as the key in your ERP.

    worker
    async function handle(event) {
      if (await db.processed.exists(event.id)) return;      // "evt_..."
      const order = event.data.object;                       // orderId, orderCode, total, items...
      if (event.type === "order.completed") await erp.upsertInvoice(order.orderId, order);
      if (event.type === "order.cancelled") await erp.voidInvoice(order.orderId, order.cancelReason);
      await db.processed.insert(event.id);
    }
  4. 4Reconcile periodically

    Re-read the day's orders every night to catch events missed while your server was down for long.

    curl
    curl "https://api.nextx.vn/api/v1/sales/orders?dateFrom=2026-10-27&dateTo=2026-10-27&pageNumber=1&pageSize=100" \
      -H "Authorization: Bearer <ACCESS_TOKEN>"

    API reference →