المطوّرون
API و Webhooks من Fulfiller
اربط متجرك أو برنامج التسيير أو أداة الأتمتة بـ Fulfiller: أرسل الطلبات، وتابع حالتها ومخزونك، واستقبل حدثًا بمجرد تغيّر أي طلب.
البداية
API متاحة للبائعين الذين تمّت المصادقة على حساباتهم.
- في فضاء البائع، افتح API و Webhooks.
- أنشئ مفتاحًا: “قراءة فقط” لقراءة الطلبات والمنتجات، أو “قراءة وكتابة” لإنشاء الطلبات وإلغائها أيضًا.
- انسخ المفتاح: لا يُعرض إلا مرة واحدة. احتفظ به على خادمك، وليس أبدًا في صفحة ويب أو تطبيق جوال.
عنوان 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. تصل الطلبات، والسلال المتروكة إن أردت، تلقائيًا.