TelDel API للتجار
اربط متجرك الإلكتروني بتل دل: كل طلب على موقعك يتحول تلقائيًا لشحنة، تتابع حالتها من نظامك، وتوصلك التحديثات أول بأول على الـ webhook.
نظرة عامة
الـ API بيعمل نفس اللي بتعمله من التطبيق، بس من سيرفر موقعك:
- إنشاء طلب شحن من بيانات طلب العميل.
- تتبع حالة الطلب ورقم التتبع عند شركة الشحن.
- إلغاء الطلب قبل الاستلام.
- استقبال تحديثات الحالة تلقائيًا على رابط عندك (Webhook).
إزاي تبدأ
- لازم يكون عندك حساب تاجر مفعّل على تطبيق تل دل.
- تواصل مع فريق تل دل وهيبعتلك مفتاح API للتيست (بيبدأ بـ
tdk_) ويحدد لحسابك شركة الشحن الافتراضية، وبعد التجربة مفتاح الإنتاج. - لو عايز تحديثات تلقائية ابعت لنا رابط الـ webhook، وهتستلم مفتاح التوقيع (بيبدأ بـ
whsec_).
الرابط والمصادقة
عندنا بيئتين منفصلين تمامًا، ولكل واحدة مفتاحها:
| البيئة | الرابط الأساسي | الاستخدام |
|---|---|---|
| تجريبي (Test) | https://api.teldel.app/api/merchant | للتطوير والتجربة — الطلبات هنا ما بتتوصلش فعليًا. |
| إنتاج (Production) | https://api.teldel.om/api/merchant | الطلبات الحقيقية بعد ما تخلص التجربة. |
| الهيدر | القيمة |
|---|---|
X-Api-Key | مفتاحك tdk_... (أو Authorization: Bearer tdk_...). إلزامي. |
Content-Type | application/json في طلبات POST. |
Accept-Language | ar أو en — لغة رسائل الأخطاء. اختياري (الافتراضي إنجليزي). |
الرد الناجح دايمًا جوه غلاف ثابت: code (كود HTTP)، وresponse (البيانات)، وtimestamp. الحد الأقصى 120 طلب في الدقيقة.
إنشاء طلب
curl -X POST "https://api.teldel.app/api/merchant/orders" \
-H "X-Api-Key: tdk_your_key" \
-H "Content-Type: application/json" \
-H "Accept-Language: en" \
-d '{
"reference": "WEB-100234",
"description": "2 x T-shirt",
"recipient": { "name": "Ahmed Al Balushi", "phone": "91234567" },
"deliveryAddress": {
"wilayaCode": "5000",
"street": "Sohar, Al Hambar, Way 1203, House 7",
"landmark": "Behind Sohar Hospital"
},
"orderAmount": 12.5,
"cashOnDelivery": true,
"weight": 1.2,
"notes": "Call before arriving"
}'| الحقل | النوع | الوصف |
|---|---|---|
reference | string · إلزامي | رقم الطلب عندك (فريد في متجرك، حتى 100 حرف). لو بعته تاني بيرجعلك نفس الطلب بدل ما يتعمل طلب جديد. |
recipient.name | string · إلزامي | اسم العميل. |
recipient.phone | string · إلزامي | رقم عُماني 8 أرقام (مقبول بـ +968 أو 00968). |
recipient.whatsapp | string | رقم الواتساب للتحديثات (الافتراضي = phone). |
deliveryAddress | object · إلزامي | عنوان التسليم: wilayaCode أو cityId من قائمة الولايات، وstreet (إلزامي)، وlandmark (اختياري). |
pickupAddress | object | عنوان الاستلام بنفس الشكل. الافتراضي: عنوان فرعك المسجّل في التطبيق. |
orderAmount | number · إلزامي | قيمة البضاعة بالريال العُماني (حتى 3 أرقام عشرية). |
cashOnDelivery | boolean · إلزامي | true: العميل يدفع البضاعة + التوصيل كاش عند الاستلام. false: البضاعة مدفوعة. |
deliveryFeePaidBy | MERCHANT | CUSTOMER | لما cashOnDelivery = false: مين يدفع التوصيل. الافتراضي MERCHANT. |
description | string | وصف الشحنة (الافتراضي: Order <reference>). |
categoryId | uuid | التصنيف من GET /categories (الافتراضي: تصنيف حسابك). |
weight | number | الوزن بالكيلو — بيأثر على سعر شركة الشحن. |
shippingCompanyId | uuid | شركة شحن غير الافتراضية، من GET /shipping-companies. |
notes | string | ملاحظات للسائق (حتى 1000 حرف). |
express | boolean | شحن سريع بمناديب تل دل: رسوم إضافية مقابل توصيل أسرع. بين ولايتين ما بيحتاجش شركة شحن. شوف الشحن السريع. |
مين بيدفع إيه
| الحالة | الحقول | النتيجة |
|---|---|---|
| دفع عند الاستلام | cashOnDelivery: true | العميل بيدفع البضاعة + التوصيل: أونلاين من رابط الواتساب، أو كاش للسائق (amountToCollect). |
| البضاعة مدفوعة، والتوصيل عليك | cashOnDelivery: false, deliveryFeePaidBy: "MERCHANT" | رسوم التوصيل بتتخصم من محفظتك في تل دل وقت إنشاء الطلب. |
| البضاعة مدفوعة، والتوصيل على العميل | cashOnDelivery: false, deliveryFeePaidBy: "CUSTOMER" | العميل بيدفع رسوم التوصيل بس: أونلاين أو كاش للسائق. |
إيه اللي بيحصل بعد الإنشاء
- لو العميل هو اللي بيدفع (أي حالة غير إن التوصيل عليك): الطلب بيخرج كاش عند الاستلام خلال ثواني من غير انتظار (
awaitingRecipient: trueفي الثواني دي بس). بتوصل للعميل رسالة واتساب فيها المبلغ والرابط، ولو الطلب مع سائق تل دل يقدر يدفع أونلاين قبل الاستلام، وساعتهاamountToCollectبيبقى 0. طلبات شركات الشحن كاش بس. وتقدر تبعتله الرابط بنفسك منrecipientPaymentUrl. - داخل نفس الولاية: الطلب بيروح لسائقين تل دل.
- بين ولايتين: الطلب بيتعيّن لشركة الشحن (
shippingCompanyIdأو الافتراضية لحسابك) ويتبعت لها تلقائيًا. لو مفيش شركة محددة الطلب بيترفض. - الطلب بيتعمل كامل أو ما بيتعملش خالص: لو رصيد المحفظة مش كفاية أو الشركة مش بتغطي الولاية، بيرجعلك خطأ وما فيش طلب بيتسجّل — صلّح وابعت تاني بنفس
reference.
الرد
201 لطلب جديد، أو 200 مع existing: true لو الـ reference ده اتعمل قبل كده (آمن تعيد المحاولة بعد timeout).
{
"code": 201,
"response": {
"existing": false,
"order": {
"id": "6f1c2a7e-0b8d-4f3e-9a51-2c7d8e9f0a1b",
"orderNumber": "ORD-2026-0815",
"reference": "WEB-100234",
"status": "READY_FOR_DELIVERY",
"deliveryType": "NATIONAL",
"isExpress": false,
"expressFee": null,
"shippingCompany": { "id": "b2d4…", "name": "Dalilee" },
"trackingCode": null,
"trackingUrl": "https://teldel.om/track/ORD-2026-0815",
"deliveryFee": 2.5,
"orderAmount": 12.5,
"amountToCollect": 15,
"isPaid": false,
"awaitingRecipient": true,
"recipientPaymentUrl": "https://teldel.om/order-details/6f1c2a7e-0b8d-4f3e-9a51-2c7d8e9f0a1b?t=Qm9x…",
"cancelReason": null,
"createdAt": "2026-10-28T09:12:44.120Z",
"updatedAt": "2026-10-28T09:12:45.003Z"
}
},
"timestamp": "2026-10-28T09:12:45.010Z"
}| الحقل | الوصف |
|---|---|
orderNumber | رقم الطلب عندنا (ORD-...)، احفظه مع طلبك. |
status | حالة الطلب — شوف الحالات. |
trackingCode | رقم الشحنة عند شركة الشحن (بيظهر بعد ما الطلب يوصلها). |
trackingUrl | صفحة تتبع ممكن تبعتها للعميل. |
isExpress / expressFee | طلب سريع ورسوم السريع (جزء من deliveryFee). |
deliveryFee | رسوم التوصيل. |
amountToCollect | المبلغ اللي السائق هيحصّله كاش من العميل (بيبقى 0 لو العميل دفع أونلاين). |
awaitingRecipient | true لثواني بعد إنشاء الطلب، قبل ما يخرج كاش عند الاستلام. |
recipientPaymentUrl | رابط العميل: يدفع أونلاين أو يختار كاش، ويتابع الشحنة. null لو التوصيل عليك. |
حالة الطلب
بالـ reference بتاعك، أو رقم الطلب عندنا ORD-...، أو الـ id. الرد هو نفس كائن order اللي فوق.
curl "https://api.teldel.app/api/merchant/orders/WEB-100234" \
-H "X-Api-Key: tdk_your_key"DELIVERED أو CANCELED.إلغاء طلب
curl -X POST "https://api.teldel.app/api/merchant/orders/WEB-100234/cancel" \
-H "X-Api-Key: tdk_your_key" \
-H "Content-Type: application/json" \
-d '{ "reason": "Customer cancelled" }'مسموح بس قبل الاستلام (READY_FOR_DELIVERY أو PENDING_PICKUP أو ORDER_RECEIVED). رسوم التوصيل المخصومة من محفظتك بترجع لها تلقائيًا، والإلغاء بيتبعت لشركة الشحن. بعد الاستلام تواصل مع تل دل.
الولايات والتصنيفات وشركات الشحن
بيانات ثابتة تقريبًا — خزّنها عندك وحدّثها كل فترة.
| الطلب | الاستخدام |
|---|---|
GET /wilayas | الولايات ومناطقها: استخدم code كـ wilayaCode، أو id (ولاية أو منطقة) كـ cityId. |
GET /categories | تصنيفات الطلبات لـ categoryId. |
GET /shipping-companies | الشركات المتاحة لـ shippingCompanyId (isDefault = الافتراضية لحسابك). |
GET /express | الشحن السريع: متاح دلوقتي ولا لأ، رسومه، وساعاته ووعد التوصيل. |
{
"code": 200,
"response": [
{
"id": "0b9a6c2e-4c1f-4b8e-9d0a-1f2e3d4c5b6a",
"code": "5000",
"nameAr": "صحار",
"nameEn": "Sohar",
"areas": [
{ "id": "4e8f…", "nameAr": "الهمبار", "nameEn": "Al Hambar" }
]
}
],
"timestamp": "2026-10-28T09:00:00.000Z"
}الشحن السريع
ابعت express: true مع الطلب علشان يتوصل أسرع بمناديب تل دل: العميل بيدفع رسوم إضافية فوق سعر التوصيل، والطلب بيظهر الأول للمناديب. السريع دايمًا بمناديب تل دل حتى بين ولايتين، والعروض ما بتخصمش من رسومه.
{
"code": 200,
"response": {
"enabled": true,
"availableNow": true,
"startHour": 8,
"endHour": 22,
"fee": 1,
"promise": "Within 3 hours",
"national": { "enabled": true, "fee": 3, "promise": "Same day" }
},
"timestamp": "2026-10-29T09:00:00.000Z"
}availableNow: falseيعني السريع مقفول أو بره ساعاته (توقيت عُمان) — الطلب السريع وقتها بيترفض بـerrors.express_outside_hoursأوerrors.express_unavailable.feeبتتضاف لـdeliveryFeeوبترجع فيexpressFee.- بين ولايتين:
national.enabledلازم يكونtrue، والرسومnational.fee. الطلب بيروح لمندوب تل دل، فما تبعتشshippingCompanyId. لو مقفول بيترفض بـerrors.express_domestic_only. promiseهو وعد التوصيل اللي ممكن تعرضه للعميل في صفحة الدفع.
حالات الطلب
| status | المعنى |
|---|---|
READY_FOR_DELIVERY | الطلب اتعمل وجاهز للاستلام. |
PENDING_PICKUP | اتعيّن لسائق/شركة وفي انتظار الاستلام. |
ORDER_RECEIVED | السائق أو الشركة قبلوا الطلب. |
PICKED_UP | اتستلم من عندك. |
IN_TRANSIT | في الطريق للعميل. |
DELIVERED | اتسلّم للعميل. (نهائية) |
CANCELED | اتلغى. (نهائية) |
Webhooks
مع كل تغيير في حالة طلب اتعمل من الـ API، ولما الطلب يخرج (isPaid بتبقى true)، بنبعت POST بـ JSON للرابط اللي سجلته. الحدث حاليًا: order.status_changed (وفيه كمان ping لما فريق تل دل يجرب الرابط).
POST /teldel/webhook HTTP/1.1
Content-Type: application/json
User-Agent: TelDel-Webhooks/1
X-TelDel-Event: order.status_changed
X-TelDel-Delivery: 9a0e5c1b-…
X-TelDel-Timestamp: 1761643965
X-TelDel-Signature: sha256=5d41402abc4b2a76b9719d911017c592…{
"id": "9a0e5c1b-…",
"event": "order.status_changed",
"createdAt": "2026-10-28T10:32:45.000Z",
"data": {
"id": "6f1c2a7e-0b8d-4f3e-9a51-2c7d8e9f0a1b",
"orderNumber": "ORD-2026-0815",
"reference": "WEB-100234",
"status": "IN_TRANSIT",
"trackingCode": "A9658145",
"…": "same fields as GET /orders/{ref}"
}
}التحقق من التوقيع
X-TelDel-Signature = sha256= + HMAC-SHA256 بمفتاح التوقيع whsec_... للنص <X-TelDel-Timestamp>.<body كما وصل>. اتأكد منه قبل ما تثق في الرسالة، وارفض الرسائل اللي عدّى عليها أكتر من 5 دقايق.
// Node.js (Express) - use the raw body, not a re-serialized object
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.TELDEL_WEBHOOK_SECRET; // whsec_...
app.post("/teldel/webhook", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.header("X-TelDel-Timestamp");
const signature = req.header("X-TelDel-Signature") ?? "";
const expected =
"sha256=" +
crypto.createHmac("sha256", SECRET).update(`${timestamp}.${req.body}`).digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
// Reject old deliveries (replays)
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !fresh) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
// event.id is unique per delivery - ignore ids you already handled
// update your order event.data.reference with event.data.status ...
res.sendStatus(200);
});<?php
// PHP (Laravel, WooCommerce, plain PHP...)
$secret = getenv('TELDEL_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TELDEL_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_TELDEL_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// update your order $event['data']['reference'] with $event['data']['status'] ...
http_response_code(200);الرد وإعادة المحاولة
- رد بأي كود
2xxخلال 10 ثواني — اعمل المعالجة التقيلة بعد الرد. - لو الرد مش
2xxأو حصل timeout بنعيد بعد 1، 5، 15، 60 دقيقة، وبعدين 6 ساعات (6 محاولات في المجموع). - ممكن نفس الرسالة توصل مرتين — استخدم
idعشان تتجاهل المكرر. - الترتيب مش مضمون 100% — اعتمد على
data.statusوdata.updatedAt، أو اسألGET /orders/{ref}.
الأخطاء
الأخطاء فيها message مترجمة (حسب Accept-Language) وcode ثابت تقدر تعتمد عليه في الكود. أخطاء الحقول فيها كمان details بكل حقل غلط.
{
"statusCode": 400,
"timestamp": "2026-10-28T09:12:45.010Z",
"path": "/api/merchant/orders",
"method": "POST",
"requestId": "c1d2e3…",
"message": "Unknown wilaya: send a cityId or wilayaCode from the wilayas list.",
"code": "errors.merchant_api_unknown_wilaya"
}| HTTP | code | السبب |
|---|---|---|
| 401 | errors.merchant_api_key_missing / errors.merchant_api_key_invalid | مفيش مفتاح، أو المفتاح غلط/اتلغى، أو الحساب موقوف. |
| 400 | errors.validation_failed | حقل ناقص أو غلط — شوف details. |
| 400 | errors.merchant_api_invalid_phone | رقم الهاتف مش رقم عُماني 8 أرقام. |
| 400 | errors.merchant_api_unknown_wilaya | wilayaCode / cityId مش موجود. |
| 400 | errors.merchant_api_pickup_required | مفيش عنوان فرع لحسابك — ابعت pickupAddress. |
| 400 | errors.merchant_api_no_shipping_company | طلب بين ولايتين من غير شركة شحن. |
| 400 | errors.insufficient_balance | رصيد المحفظة مش كفاية لرسوم التوصيل. |
| 409 | — | شركة الشحن مش بتغطي الولاية، أو حالة الطلب ما تسمحش بالعملية. |
| 404 | errors.order_not_found | الطلب مش موجود أو مش تبعك. |
| 429 | errors.too_many_requests | أكتر من 120 طلب في الدقيقة — استنى وجرّب تاني. |
| 5xx | — | مشكلة عندنا — جرّب تاني بعد شوية (نفس reference آمن). |
reference ورقم الطلب ORD-...، والـ requestId من رد الخطأ، ووقت الطلب.