Developers

Webhooks

Κάνουμε POST ένα συμβάν στον server σας τη στιγμή που προχωρά μια παραγγελία ή αλλάζει μια καταχώριση, ώστε να μη χρειάζεστε ποτέ περιοδικούς ελέγχους. Υπογεγραμμένο, με επαναλήψεις για περίπου μία ημέρα, και ίδιο σε κάθε διαδρομή του API.
Σε αυτή τη σελίδα
  1. 1Ρύθμιση endpoint
  2. 2Το συμβάν
  3. 3Συμβάντα
  4. 4Υπογραφές
  5. 5Επαλήθευση παράδοσης
  6. 6Επαναλήψεις και σειρά

Ρύθμιση endpoint

  1. Προσθέστε στον server σας μια διαδρομή https που διαβάζει το ακατέργαστο σώμα και απαντά 2xx μέσα σε 10 δευτερόλεπτα. Κάντε την επεξεργασία μετά την απάντηση.
  2. Δημιουργήστε το endpoint με το API ή ζητήστε το από την ομάδα μας. Αποθηκεύστε το μυστικό whsec_ που επιστρέφει: εμφανίζεται μία φορά.
  3. Επαληθεύστε κάθε αίτημα (παρακάτω) και μετά αφαιρέστε διπλότυπα με βάση το id του συμβάντος.
  4. Στείλτε στον εαυτό σας ένα δοκιμαστικό συμβάν για κάθε τύπο που χειρίζεστε.

Το συμβάν

Κάθε συμβάν έχει τον ίδιο φάκελο. Το data περιέχει τα πεδία του τύπου.

idstring
Ξεκινά με evt_. Ίδιο σε κάθε επανάληψη και στην κεφαλίδα webhook-id.
typestring
Ένα από τα παρακάτω συμβάντα.
api_versionstring
Η έκδοση με την οποία γράφεται το data: του endpoint σας.
created_atstring
Πότε συνέβη, που μπορεί να είναι πριν το λάβετε.
testboolean
true για δοκιμαστικά συμβάντα.
dataobject
Ids για ανάκτηση του πλήρους αντικειμένου, και τι άλλαξε.
Μια παράδοση
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
  }
}

Συμβάντα

order.created

Μια παραγγελία έφτασε σε εμάς, από το κατάστημά σας στο Shopify ή από το API. Πληρωμένη ή awaiting_payment.

order.created
{
  "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

Μια παραγγελία πληρώθηκε από το υπόλοιπό σας. Ενεργοποιείται και για παραγγελίες που περίμεναν φόρτιση.

order.paid
{
  "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

Πληρώσαμε τον πωλητή. Οι γραμμές είναι with_seller μέχρι να φτάσουν στην αποθήκη μας.

order.bought
{
  "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

Τα προϊόντα είναι στην αποθήκη μας, ελεγμένα, με τις φωτογραφίες τους. Ακολουθεί η συσκευασία.

order.checked
{
  "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

Ένα δέμα έφυγε με παρακολούθηση· η χρέωση αποστολής είναι οριστική. Το κατάστημα από το οποίο ήρθε η παραγγελία έχει επίσης την παρακολούθηση.

order.shipped
{
  "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

Ένας πωλητής άλλαξε τιμή, και τι έκανε ο κανόνας συγχρονισμού σας: νέα τιμή, παύση ή ειδοποίηση.

product.cost_changed
{
  "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

Ο πωλητής εξάντλησε μια παραλλαγή και το ράφι σας δεν έχει: την ορίζουμε σε 0 στα κανάλια σας.

product.out_of_stock
{
  "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

Ο έλεγχός μας βρήκε πρόβλημα σε ένα προϊόν: λάθος χρώμα ή μέγεθος, ζημιά, ένα κομμάτι που λείπει. Επιλέξτε έως το respond_by· χωρίς απάντηση το κρατάμε και σας γράφουμε.

qc.issue
{
  "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"
  }
}

Υπογραφές

Οι παραδόσεις υπογράφονται κατά Standard Webhooks, οπότε τις επαληθεύει μια βιβλιοθήκη που ίσως ήδη χρησιμοποιείτε. Κάθε παράδοση φέρει τρεις κεφαλίδες:

ΚεφαλίδαΤι περιέχει
webhook-idΤο id του συμβάντος.
webhook-timestampΠότε στείλαμε αυτή την προσπάθεια, σε δευτερόλεπτα Unix.
webhook-signaturev1, μετά κόμμα και το HMAC-SHA256 σε base64 του id.timestamp.body με το μυστικό σας. Κατά την εναλλαγή, δύο από αυτά, χωρισμένα με κενό.

Το κλειδί είναι το μυστικό σας μετά το whsec_, αποκωδικοποιημένο από base64. Υπογράψτε το ακατέργαστο σώμα ακριβώς όπως έφτασε· απορρίψτε χρονοσφραγίδα με απόκλιση άνω των πέντε λεπτών, ώστε ένα υποκλαπέν αίτημα να μην μπορεί να επαναληφθεί.

Επαλήθευση παράδοσης

Με τη βιβλιοθήκη standardwebhooks, ή με λίγες δικές σας γραμμές:

// npm install standardwebhooks express
import express from "express"
import { Webhook } from "standardwebhooks"

const webhook = new Webhook(process.env.NINJABUY_WEBHOOK_SECRET) // whsec_...
const app = express()

// The raw body: a parsed and re-serialised body no longer matches.
app.post("/hooks/ninjabuy", express.raw({ type: "application/json" }), (req, res) => {
  let event
  try {
    event = webhook.verify(req.body.toString("utf8"), req.headers)
  } catch {
    return res.sendStatus(400)
  }
  res.sendStatus(204) // answer first, then do the work
  queue.push(event) // deduplicate on event.id: a retry repeats it
})

Επαναλήψεις και σειρά

Οτιδήποτε εκτός από 2xx μέσα σε 10 δευτερόλεπτα θεωρείται αποτυχία και ξαναδοκιμάζουμε, κάθε φορά με το ίδιο webhook-id και νέα χρονοσφραγίδα και υπογραφή:

ΠροσπάθειαΜετά την προηγούμενη
1Αμέσως
25 δευτερόλεπτα
35 λεπτά
430 λεπτά
52 ώρες
65 ώρες
710 ώρες
810 ώρες, η τελευταία
  • Μετά την τελευταία προσπάθεια η παράδοση αποτυγχάνει. Στείλτε την ξανά από τη λίστα παραδόσεων.
  • Ένα endpoint του οποίου όλες οι παραδόσεις αποτύγχαναν για πέντε ημέρες απενεργοποιείται, αφού σας στείλουμε email. Η επανενεργοποίηση στέλνει ό,τι έχασε τις τελευταίες τρεις ημέρες.
  • Τα συμβάντα μπορεί να φτάσουν δύο φορές και εκτός σειράς. Αφαιρέστε διπλότυπα με βάση το id, και όταν η σειρά μετρά, ανακτήστε το αντικείμενο και εμπιστευτείτε τη δική του κατάσταση αντί για του συμβάντος.