تخطي للذهاب إلى المحتوى

المطوّرون

API و Webhooks من Fulfiller

اربط متجرك أو برنامج التسيير أو أداة الأتمتة بـ Fulfiller: أرسل الطلبات، وتابع حالتها ومخزونك، واستقبل حدثًا بمجرد تغيّر أي طلب.

البداية

API متاحة للبائعين الذين تمّت المصادقة على حساباتهم.

  1. في فضاء البائع، افتح API و Webhooks.
  2. أنشئ مفتاحًا: “قراءة فقط” لقراءة الطلبات والمنتجات، أو “قراءة وكتابة” لإنشاء الطلبات وإلغائها أيضًا.
  3. انسخ المفتاح: لا يُعرض إلا مرة واحدة. احتفظ به على خادمك، وليس أبدًا في صفحة ويب أو تطبيق جوال.

عنوان API: https://fulfiller.net/api/v1

الطلبات والردود بصيغة JSON (UTF-8). التواريخ بصيغة ISO 8601 بتوقيت UTC، والمبالغ بالدينار التونسي (TND).

curl https://fulfiller.net/api/v1/me \
  -H "Authorization: Bearer ffk_your_key"

{"account": {"id": 42, "name": "Najma Store"},
 "key": {"name": "My ERP", "scope": "write", "ends_with": "x9Qa"},
 "webhooks": 1}

المصادقة

أرسل المفتاح في ترويسة Authorization بعد كلمة Bearer. المفتاح الغائب أو الخاطئ أو الملغى يتلقى الحالة 401؛ والمفتاح للقراءة فقط الذي يحاول تغيير طلب يتلقى 403.

Authorization: Bearer ffk_your_key

يمكنك إلغاء أي مفتاح في أي وقت من API و Webhooks: يتوقف عن العمل فورًا.

الأخطاء والحدود

يُجاب عن الخطأ بحالة HTTP ومحتوى JSON:

{"error": {"code": "unknown_product", "message": "Unknown product(s): KETO-90. Use the SKUs of GET /api/v1/products.", "skus": ["KETO-90"]}}
الحالةالمعنى
400الطلب غير سليم (معامل أو صيغة JSON).
401مفتاح غائب أو خاطئ أو ملغى.
403المفتاح للقراءة فقط.
404لا يوجد هذا الطلب في حسابك.
409لم يعد بالإمكان تغيير الطلب: ملغى مسبقًا، أو غادر مع شركة التوصيل، أو يجري الاتصال بالحريف.
422حقل ناقص أو خاطئ، أو منتج غير معروف.
429أكثر من 120 طلبًا في الدقيقة: انتظر عدد الثواني المذكور في ترويسة Retry-After.

قراءة الطلبات

GET /orders

طلباتك، الأحدث أولًا، 50 في الصفحة (حتى 100 مع limit).

المعاملالمعنى
statusالطلبات في هذه الحالة فقط (انظر حالات الطلبات).
updated_sinceالطلبات التي تغيّرت منذ هذا التاريخ فقط: أبسط طريقة للبقاء متزامنًا.
created_since, created_beforeالطلبات الواردة في هذه الفترة.
referenceالطلب الذي يحمل مرجعك الخاص.
phoneطلبات رقم الحريف هذا.
page, limitالصفحة المطلوبة وحجمها.
curl "https://fulfiller.net/api/v1/orders?status=delivered&updated_since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer ffk_your_key"

{"data": [{
   "id": 1234, "number": "FF-001234", "reference": "1042", "source": "api", "store": null,
   "status": "delivered", "confirmation_status": "confirmed", "delivery_status": "delivered",
   "customer": {"name": "Amina B.", "phone": "+21698123456", "phone2": null},
   "address": {"line": "Route de Tunis km 5", "city": "Sakiet Ezzit", "governorate": "sfax", "governorate_name": "Sfax"},
   "total": 89.0, "currency": "TND",
   "items": [{"sku": "KETO-60", "name": "Keto 60 caps", "qty": 2, "price": 44.5}],
   "carrier": "First Delivery", "tracking_number": "FD123456789", "return_reason": null,
   "recovered_cart": false,
   "created_at": "2026-10-28T09:12:03Z", "confirmed_at": "2026-10-28T10:01:44Z", "updated_at": "2026-10-30T15:20:11Z"}],
 "page": 1, "limit": 50, "total": 1, "has_more": false}

GET /orders/{number}

طلب واحد، برقمه في Fulfiller (FF-001234) أو معرّفه، مع سجل تتبّع شركة التوصيل في history.

إنشاء طلبية

POST /orders قراءة وكتابة

أرسل الطلب كما وصل إلى متجرك. يتصل مركز الاتصال بالحريف لتأكيده، ثم نغلّف ونشحن ونستخلص المبلغ، كما في بقية طلباتك.

curl -X POST https://fulfiller.net/api/v1/orders \
  -H "Authorization: Bearer ffk_your_key" -H "Content-Type: application/json" \
  -d '{
    "reference": "1042",
    "customer": {"name": "Amina B.", "phone": "98 123 456"},
    "address": {"line": "Route de Tunis km 5", "city": "Sakiet Ezzit", "governorate": "Sfax"},
    "items": [{"sku": "KETO-60", "qty": 2, "price": 44.5}],
    "total": 89
  }'
الحقلالمعنى
reference *رقمك الفريد الخاص بالطلب. إذا أُرسل مجددًا يُعيد الطلب المنشأ مسبقًا: لا يُنشأ شيء مرتين.
customer.name *اسم الحريف.
customer.phone *رقم تونسي، بأي طريقة كتابة.
customer.phone2رقم ثانٍ.
address.line *الشارع، العمارة، المعالم.
address.city, address.governorateالمدينة والولاية، بالفرنسية أو العربية أو الإنجليزية.
items *المنتجات: sku (منتج أو حزمة من GET /products)، وqty وprice.
totalالمبلغ المطلوب استخلاصه؛ وإن غاب فمجموع المنتجات.
type“order”، أو “abandoned_cart” لطلب لم يُتمّه الحريف: يتصل به مركز الاتصال بعد 30 دقيقة ليقترح عليه تأكيده.

الردّ 201 مع الطلب عند إنشائه، و200 مع الطلب المعروف إذا أُرسل المرجع من قبل. وSKU غير معروف يتلقى 422 مع قائمة المنتجات غير المعروفة.

إلغاء طلب

POST /orders/{number}/cancel قراءة وكتابة

لحريف لم يعد يريد الطلب. قبل التأكيد يُلغى فورًا؛ وبعد التأكيد فقط ما دام الطرد في مخزننا. اذكر السبب.

curl -X POST https://fulfiller.net/api/v1/orders/FF-001234/cancel \
  -H "Authorization: Bearer ffk_your_key" -H "Content-Type: application/json" \
  -d '{"reason": "The customer ordered twice"}'

المنتجات والمخزون

GET /products

ما يمكنك بيعه، مع SKU الذي تضعه في طلباتك: منتجاتك الخاصة، ومنتجات السوق في كتالوجك (بسعر بيعك)، وحزمك. stock.available هو عدد الوحدات المتاحة للبيع في مخزننا؛ وstock.status قيمته ok أو low أو out أو order (يُوفَّر عند الطلب).

{"data": [
  {"type": "marketplace", "sku": "KETO-60", "name": "Keto 60 caps", "price": 44.5,
   "stock": {"available": 120, "status": "ok"}},
  {"type": "pack", "sku": "PACK-DUO", "name": "Duo pack", "price": 79.0,
   "items": [{"sku": "KETO-60", "qty": 2}], "stock": null}
]}

حالات الطلبات

statusالمعنى
to_confirmفي انتظار مركز الاتصال (جديد، إعادة اتصال، تعذّر الوصول).
confirmedأكّده الحريف، ولم يُجهَّز بعد.
to_shipقيد التجهيز في مخزننا.
shippedسُلّم لشركة التوصيل.
in_transitفي الطريق إلى الحريف.
deliveredسُلّم ودفعه الحريف.
returningمرفوض أو لم يُسلَّم، في طريق العودة.
returnedعاد إلى مخزننا.
cancelledملغى.

يقدّم confirmation_status وdelivery_status تفاصيل كل مرحلة.

Webhooks

يرسل Webhook حدثًا إلى عنوانك بمجرد تغيّر أي طلب، فلا تحتاج إلى سؤال API مرارًا. أضف حتى 5 عناوين https في API و Webhooks واختر أحداث كل منها.

الحدثمتى
order.createdوصل طلب جديد إلى حسابك (من المتجر أو API أو يدويًا).
order.confirmedأكّده الحريف عبر الهاتف.
order.cancelledأُلغي.
order.shippedسُلّم الطرد لشركة التوصيل.
order.in_transitالطرد في الطريق.
order.deliveredسُلّم ودُفع.
order.returningمرفوض أو لم يُسلَّم: يعود.
order.returnedعاد إلى مخزننا.
pingالتجربة التي يرسلها زر “إرسال تجربة”.
POST https://your-site.example/fulfiller/webhook
Content-Type: application/json; charset=utf-8
X-Fulfiller-Event: order.delivered
X-Fulfiller-Delivery: evt_5f0c2a9d4b7e4f61a2c3d4e5f6a7b8c9
X-Fulfiller-Timestamp: 1793372411
X-Fulfiller-Signature: sha256=4c1f…

{"id": "evt_5f0c2a9d4b7e4f61a2c3d4e5f6a7b8c9", "event": "order.delivered",
 "created_at": "2026-10-30T15:20:11Z",
 "data": {"id": 1234, "number": "FF-001234", "reference": "1042", "status": "delivered", …}}

data هو الطلب كما يعرضه GET /orders/{number}، ويُقرأ عند أول إرسال للحدث.

  • X-Fulfiller-Delivery هو معرّف الحدث: نفسه في كل محاولة جديدة. احتفظ به لتجاهل حدث عالجته مسبقًا.
  • أجب بحالة 2xx خلال 10 ثوانٍ، ثم قم بمعالجتك. أي ردّ آخر أو غياب الردّ يُعاد إرساله بعد دقيقة، ثم 5 دقائق، و30 دقيقة، وساعتين، و6 ساعات، و24 ساعة.
  • يُوقَف Webhook الذي يفشل طوال يوم كامل ويصلك إشعار. أعد تشغيله في API و Webhooks: تُرسل الأحداث المنتظرة.
  • قد تصل الأحداث بغير ترتيب أو مرتين: اعتمد على status وupdated_at في data.

التحقق من التوقيع

لكل Webhook سرّه الخاص (whsec_…) المعروض في API و Webhooks. احسب HMAC-SHA256 للطابع الزمني ثم نقطة ثم المحتوى الخام، بالسرّ كمفتاح، وقارنه بالقيمة بعد sha256= في X-Fulfiller-Signature. ارفض أي طابع زمني أقدم من 5 دقائق.

Python

import hashlib, hmac, time

def is_from_fulfiller(secret, headers, raw_body):
    stamp = headers["X-Fulfiller-Timestamp"]
    if abs(time.time() - int(stamp)) > 300:
        return False
    expected = hmac.new(secret.encode(), stamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    given = headers.get("X-Fulfiller-Signature", "").removeprefix("sha256=")
    return hmac.compare_digest(expected, given)

PHP

$raw = file_get_contents('php://input');
$stamp = $_SERVER['HTTP_X_FULFILLER_TIMESTAMP'] ?? '0';
$given = substr($_SERVER['HTTP_X_FULFILLER_SIGNATURE'] ?? '', 7);
$expected = hash_hmac('sha256', $stamp . '.' . $raw, $secret);
if (abs(time() - (int) $stamp) > 300 || !hash_equals($expected, $given)) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);   // $event['event'], $event['data']['status']…

Node.js (Express)

const crypto = require('crypto');

app.post('/fulfiller/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const stamp = req.get('X-Fulfiller-Timestamp') || '0';
  const given = (req.get('X-Fulfiller-Signature') || '').replace('sha256=', '');
  const expected = crypto.createHmac('sha256', process.env.FULFILLER_SECRET)
    .update(stamp + '.').update(req.body).digest('hex');
  const same = given.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!same || Math.abs(Date.now() / 1000 - Number(stamp)) > 300) return res.sendStatus(401);
  res.sendStatus(200);
  const event = JSON.parse(req.body);   // event.event, event.data.status…
});

دون مطوّر

اربط متجرك من المتاجر المربوطة: Shopify أو WooCommerce أو YouCan أو TikTak PRO أو Converty أو Google Sheet. تصل الطلبات، والسلال المتروكة إن أردت، تلقائيًا.

ملفات تعريف الارتباط

لا نستعمل إلا ملفات تعريف الارتباط التي يحتاجها الموقع ليعمل: تسجيل دخولك ولغتك واختيارات العرض. لا ملفات إشهارية. بموافقتك، قد نقيس الجمهور أيضًا لتحسين المنصة.