المطوّرون

Webhooks

نرسل حدثًا بـ POST إلى خادمك لحظة تقدّم طلب أو تغيّر صفحة منتج، فلا تحتاج أبدًا إلى الاستعلام المتكرر. موقّع، وتُعاد محاولته لنحو يوم، وهو نفسه في كل مسارات الـ API.
في هذه الصفحة
  1. 1إعداد نقطة نهاية
  2. 2الحدث
  3. 3الأحداث
  4. 4التوقيعات
  5. 5التحقق من عملية تسليم
  6. 6إعادة المحاولات والترتيب

إعداد نقطة نهاية

  1. أضف مسار https إلى خادمك يقرأ النص الخام ويرد بـ 2xx خلال 10 ثوانٍ. نفّذ العمل بعد الرد.
  2. أنشئ نقطة النهاية عبر الـ API، أو اطلب من فريقنا. احفظ سرّ whsec_ الذي يُرجعه: فهو يظهر مرة واحدة فقط.
  3. تحقّق من كل طلب (أدناه)، ثم أزل التكرار بالاعتماد على id الحدث.
  4. أرسل لنفسك حدثًا تجريبيًا من كل نوع تتعامل معه.

الحدث

لكل حدث الغلاف نفسه. ويحمل data حقول النوع الخاصة به.

idstring
يبدأ بـ evt_. يبقى نفسه في كل إعادة محاولة، وفي ترويسة webhook-id.
typestring
أحد الأحداث أدناه.
api_versionstring
الإصدار الذي كُتب به data: إصدار نقطة النهاية لديك.
created_atstring
متى حدث، وقد يكون ذلك قبل أن يصلك.
testboolean
true للأحداث التجريبية.
dataobject
معرّفات لجلب الكائن كاملًا، وما الذي تغيّر.
عملية تسليم
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معرّف الحدث.
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 دقيقة
5ساعتان
65 ساعات
710 ساعات
810 ساعات، الأخيرة
  • بعد المحاولة الأخيرة تُعدّ عملية التسليم فاشلة. أرسلها مجددًا من قائمة عمليات التسليم.
  • نقطة النهاية التي فشلت كل عمليات التسليم إليها لخمسة أيام تُعطَّل، بعد إرسال بريد إليك. إعادة تفعيلها ترسل ما فاتها في آخر ثلاثة أيام.
  • قد تصل الأحداث مرتين وبغير ترتيب. أزل التكرار بالاعتماد على id، وحين يهمّ الترتيب، اجلب الكائن واعتمد حالته بدل حالة الحدث.