KashBoss · كاش بوس

دليل المبرمج: ربط موقعك الخاص مع كاش بوس

واجهة برمجية عامة تعمل مع أي موقع (Laravel أو ووكومرس أو غيرهما): طلبات الشراء من موقعك تُسجَّل فواتير في كاش بوس خلال ثوانٍ، وكميات الأصناف وأسعارها تتحدّث على موقعك لحظة تغيّرها في كاش بوس.

١. كيف يعمل الربط

٢. البداية: المفتاح والرابط

  1. صاحب الشركة يفتح في كاش بوس: الإعدادات ← المتجر الإلكتروني ← موقعك الخاص (API)، ويختار الفرع الذي يبيع منه الموقع، ثم يضغط «إنشاء مفتاح الربط».
  2. يرسل لك مفتاح الربط (يبدأ بـ kbs_) ورابط الواجهة الظاهرين في الشاشة.
  3. تضع أنت رابط الاستقبال (Webhook URL) على موقعك، ويحفظه صاحب الشركة في نفس الشاشة، فيظهر له سرّ التوقيع (يبدأ بـ whsec_) ليرسله لك.

رابط الواجهة:

https://csgmozibjjmqgwcwewnv.supabase.co/functions/v1/site-api/v1
تنبيه أمني: المفتاح يُنشئ فواتير ويحرّك مخزونًا وخزنة، فاستخدمه من خادم الموقع فقط (ملف .env)، ولا تضعه أبدًا في JavaScript المتصفح أو تطبيق جوال. الواجهة لا تقبل طلبات المتصفح أصلًا. وإذا تسرّب المفتاح، يغيّره صاحب الشركة من نفس الشاشة فيتوقف القديم فورًا.

٣. المصادقة والحدود

أرسل المفتاح في ترويسة Authorization مع كل طلب (أو في X-Api-Key):

Authorization: Bearer kbs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
curl -H "Authorization: Bearer $KASHBOSS_KEY" \
  https://csgmozibjjmqgwcwewnv.supabase.co/functions/v1/site-api/v1/ping
{"ok":true,"data":{"business_name":"شركتك","currency":"EGP","online_branch":"المخزن الرئيسي",
 "orders_enabled":true,"push_enabled":true,
 "order_quota":{"used":120,"limit":500,"remaining":380,"reached":false,"resets_on":"2026-11-01"},
 "server_time":"2026-10-08T21:37:59+03:00"}}

٤. شكل الرد والأخطاء

النجاح: {"ok": true, "data": …}. والخطأ:

{"ok":false,"error":{"code":"total_mismatch","message":"…","message_ar":"…","details":{"expected":220,"received":230}}}
HTTPcodeالمعنى
400invalid_request، invalid_jsonجسم الطلب أو باراميتراته غير صحيحة
401unauthorizedالمفتاح غير موجود أو غير صحيح أو تم تغييره
402feature_lockedاشتراك ربط المتجر الإلكتروني غير مفعّل على الحساب
402order_limit_reachedوصلت الشركة إلى حدّ طلبات الشهر — احتفظ بالطلب وأعِد إرساله بنفس external_id لاحقًا
403orders_disabled، connection_disabledاستقبال الطلبات موقوف من إعدادات الربط
404not_foundمسار أو طلب أو صنف غير موجود
409no_online_branch، conflictلم يُحدَّد فرع الموقع، أو معرّف مربوط بصنف آخر
409branch_unavailableفرع البيع المربوط أُغلق أو لم يعد صالحًا — مؤقت: يغيّره صاحب الشركة من الإعدادات، احتفظ بالطلب وأعِد إرساله لاحقًا
409invoice_missingعند الإلغاء: فاتورة هذا الطلب لم تعد موجودة في كاش بوس (حُذفت) — لا تُعِد المحاولة، وراجع صاحب الشركة
409no_webhookعند POST /webhooks/test: لم يُضبط رابط الاستقبال في إعدادات الربط بعد
413payload_too_largeالطلب أكبر من ٢٥٦ كيلوبايت
422validation_failed، total_mismatch، tax_mismatch، currency_mismatch، variant_required، unknown_productبيانات الطلب لا تُقبل — لا تُعِد المحاولة بنفس البيانات
429rate_limitedتجاوزت الحد — انتظر Retry-After
500server_errorخطأ مؤقت — أعِد المحاولة بنفس external_id
أخطاء مؤقتة وأخطاء نهائية: 401 (بعد تغيير المفتاح) و402 feature_locked و402 order_limit_reached و403 orders_disabled/connection_disabled و409 branch_unavailable و429 و5xx تزول بتدخّل صاحب الشركة أو بمرور الوقت، فاحتفظ بالطلب وأعِد إرساله لاحقًا بنفس external_id ولا تعتبره فاشلًا نهائيًّا. أمّا 400 و404 و422 فبياناتٌ لا تُقبل: راجعها ولا تكرّرها كما هي.

كل طلب مرفوض يظهر لصاحب الشركة بسببه في «تقرير المتجر»، فيستطيع إخبارك بما حدث.

٥. الأصناف والمخزون

GET /products

الكتالوج بالصفحات (حتى ٢٠٠ صنف في الصفحة). استخدمه للمزامنة الأولى، ولإعادة المطابقة الليلية إن أردت.

باراميترالوصف
limitعدد الأصناف في الصفحة (الافتراضي ١٠٠، والأقصى ٢٠٠)
cursorقيمة next_cursor من الصفحة السابقة
updated_sinceالأصناف التي تغيّرت بعد وقت معيّن (ISO-8601)
in_stock_only1 = المتوفّر فقط
include_inactive1 = يشمل الأصناف الموقوفة
{"ok":true,"next_cursor":"0e9c…","seq":18234,"data":[
 {"product_id":"94e0…","sku":"TS-1","barcode":"622…","name":"تيشيرت قطن","description":null,
  "category":"ملابس","brand":null,"image_url":"https://…","unit":"قطعة","unit_kind":"main","pack_note":null,
  "price":250.00,"currency":"EGP","available_qty":12,"available_qty_exact":12.0,"in_stock":true,
  "is_active":true,"has_variants":false,"variants":[],"external_id":null,"updated_at":"…"},
 {"product_id":"23f6…","sku":"SHIRT","name":"قميص","has_variants":true,"in_stock":true,"available_qty":5,
  "variants":[{"variant_id":"e9df…","sku":"SHIRT-L","name":"قميص — L / أزرق",
               "attributes":{"size":"L","color":"أزرق"},"price":220.0,"available_qty":5,"in_stock":true}]}
]}

GET /stock?skus=TS-1,SHIRT-L

كمية وسعر أصناف محدّدة حالًا (حتى ٢٠٠). يقبل أيضًا product_ids وvariant_ids. مفيد للتأكد من التوفّر لحظة إتمام الشراء.

PUT /products/{product_id}/link (اختياري)

يربط صنف كاش بوس بمعرّف الصنف عندك، ليصلك external_id في التحديثات. ويمكنك تثبيت وحدة البيع بـ unit_kind:

{"external_id":"55","external_variant_id":null,"unit_kind":"main"}

ولا يلزم ذلك عادةً: المطابقة بالكود (sku) تكفي، وكل طلب يحمل external_product_id يُنشئ الربط تلقائيًا.

٦. إرسال طلب شراء

POST /orders

أرسل الطلب عندما يصبح مؤكَّدًا على موقعك (بعد الدفع الإلكتروني، أو عند تأكيد طلب الدفع عند الاستلام).

{
  "external_id": "10234",
  "order_number": "#10234",
  "currency": "EGP",
  "customer": {"name": "أحمد علي", "phone": "01012345678", "email": "[email protected]",
               "address": "١٠ شارع التحرير", "city": "القاهرة"},
  "items": [
    {"sku": "TS-1", "name": "تيشيرت قطن", "quantity": 2, "unit_price": 250.00, "external_product_id": "55"},
    {"variant_id": "e9df…", "quantity": 1, "unit_price": 220.00}
  ],
  "shipping": 50.00,
  "discount": 0,
  "tax_total": 0,
  "prices_include_tax": true,
  "total": 770.00,
  "payment": {"status": "paid", "method": "card", "reference": "pi_3Nx…"},
  "notes": "التوصيل بعد الخامسة"
}
الحقلالوصف
external_id مطلوبرقم الطلب عندك (حتى ١٠٠ حرف). مفتاح عدم التكرار: إرسال نفس الرقم مرة ثانية يرجع نفس الفاتورة ولا يُنشئ فاتورة جديدة — فأعِد المحاولة بأمان عند انقطاع الشبكة أو خطأ 500.
items مطلوبسطر لكل صنف، ويُحدَّد الصنف بأحد: variant_id أو product_id (من /products) أو sku (كود الصنف أو المتغيّر أو الباركود). وquantity وunit_price بوحدة البيع على الموقع. ويمكن إرسال unit = "main" أو "sub" للبيع بالوحدة الكبيرة أو الصغيرة صراحةً.
total مطلوبالإجمالي الذي دفعه العميل. يجب أن يساوي: مجموع (الكمية × السعر) − الخصم + الشحن (+ الضريبة إذا كانت الأسعار بدونها)، بفارق لا يزيد على ٠٫٠٥؛ وإلا يُرفض الطلب بـ total_mismatch.
shipping، discountالشحن يُسجَّل بندًا مستقلًا في الفاتورة، والخصم على مستوى الطلب.
tax_total، prices_include_taxإن كان موقعك يحسب ضريبة: أرسلها في tax_total (أو tax_amount لكل سطر). prices_include_tax (الافتراضي true) يعني أن أسعار السطور شاملة الضريبة. وإن لم يحسب موقعك ضريبة فلا ترسلها، وفي الشركات السعودية يحسب كاش بوس ضريبة القيمة المضافة تلقائيًّا من الإجمالي.
currencyاختياري، ويجب أن يساوي عملة الشركة (يظهر في /ping)، وإلا يُرفض بـ currency_mismatch.
payment.statuspaid = مدفوع كاملًا (يدخل الخزنة) · cod أو unpaid = على حساب العميل · partial مع paid_amount = جزء مدفوع والباقي على الحساب.
customerيُطابَق العميل بالموبايل ثم بالإيميل، وإلا يُنشأ عميل جديد. وبدون موبايل ولا إيميل يُسجَّل على «عميل الموقع».
صنف غير موجود في كاش بوس؟ يُنشأ تلقائيًا بالاسم والكود والسعر المرسَلة، ويُذكر في ملاحظات الفاتورة ليراجعه صاحب الشركة. ولتجنّب ذلك اجعل أكواد أصناف موقعك مطابقة لأكواد كاش بوس، أو استخدم product_id من الكتالوج.

الرد 201 (أو 200 مع "duplicate": true للتكرار):

{"ok":true,"data":{"external_id":"10234","invoice_id":"fe7a…","invoice_number":"INV-00431","status":"recorded",
 "duplicate":false,"total":770,"paid":770,"remaining":0,"payment_status":"paid","customer_id":"6970…",
 "auto_created_products":[],"warnings":[],"journal_posted":true}}

التجربة بلا تسجيل: POST /orders?dry_run=1 ينفّذ كل التحقق والمطابقة ويرجع المعاينة (الأصناف ووحداتها والإجمالي) دون إنشاء أي فاتورة.

٧. حالة الطلب وإلغاؤه

GET /orders/{external_id} يرجع الفاتورة وحالتها (recorded أو cancelled).

POST /orders/{external_id}/cancel بجسم اختياري {"reason":"…"}: يُسجَّل مرتجعًا كاملًا، فيرجع المخزون، ويُرَدّ المدفوع من الخزنة أو يُخصم من حساب العميل. والإلغاء المكرّر آمن.

٨. التحديثات اللحظية (Webhooks)

جهّز على موقعك مسارًا يقبل POST بـ JSON، وأعطِ رابطه (يبدأ بـ https://) لصاحب الشركة ليحفظه. بعدها يصلك حدث inventory.updated مع كل تغيّر:

POST https://your-site.com/kashboss/webhook
X-KashBoss-Event: inventory.updated
X-KashBoss-Event-Id: evt_4ead176b55b04bc3b5c2f64fb02153f6
X-KashBoss-Timestamp: 1791484636
X-KashBoss-Signature: v1=41748c84231bed035543f62d3561a29d448db1a8df8a9a8e045cf187a3febfca

{"id":"evt_4ead…","type":"inventory.updated","created_at":"2026-10-08T21:38:05+03:00",
 "data":{"seq":18240,"items":[
   {"product_id":"94e0…","variant_id":null,"sku":"TS-1","barcode":"622…","external_id":"55",
    "name":"تيشيرت قطن","unit":"قطعة","unit_kind":"main","price":250.00,"currency":"EGP",
    "available_qty":11,"available_qty_exact":11.0,"in_stock":true,"is_active":true,"deleted":false,
    "seq":18240,"updated_at":"…"}]}}

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

التوقيع = HMAC-SHA256(سرّ التوقيع, timestamp + "." + الجسم الخام) بالنظام الست عشري. تحقّق منه على الجسم الخام قبل تحويله، وارفض الحدث إذا كان الوقت أبعد من ٥ دقائق. وعند تغيير السرّ تصل ترويسة فيها توقيعان مفصولان بفاصلة لمدة ٢٤ ساعة؛ اقبل الحدث إن طابق أيٌّ منهما.

PHP

$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_KASHBOSS_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_KASHBOSS_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, getenv('KASHBOSS_WEBHOOK_SECRET'));
$valid = false;
foreach (explode(',', $sig) as $part) {
    if (hash_equals($expected, preg_replace('/^v1=/', '', trim($part)))) { $valid = true; }
}
if (!$valid || abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }

Node.js

const crypto = require('crypto');
function verify(rawBody, ts, header, secret) {
  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  const ok = header.split(',').some(p => {
    const v = p.trim().replace(/^v1=/, '');
    return v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));
  });
  return ok && Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
}

Python

import hmac, hashlib, time
def verify(raw: bytes, ts: str, header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    ok = any(hmac.compare_digest(p.strip().removeprefix("v1="), expected) for p in header.split(","))
    return ok and abs(time.time() - int(ts)) <= 300

١٠. مثال كامل بـ Laravel

الإعدادات

# .env
KASHBOSS_API_URL=https://csgmozibjjmqgwcwewnv.supabase.co/functions/v1/site-api/v1
KASHBOSS_API_KEY=kbs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
KASHBOSS_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// config/services.php
'kashboss' => [
    'url'            => env('KASHBOSS_API_URL'),
    'key'            => env('KASHBOSS_API_KEY'),
    'webhook_secret' => env('KASHBOSS_WEBHOOK_SECRET'),
],

العميل (Client)

// app/Services/KashBoss.php
namespace App\Services;

use Illuminate\Support\Facades\Http;

class KashBoss
{
    private function http()
    {
        return Http::baseUrl(config('services.kashboss.url'))
            ->withToken(config('services.kashboss.key'))
            ->acceptJson()
            ->timeout(30)
            // إعادة المحاولة عند انقطاع الشبكة أو 429/5xx فقط — آمنة بفضل external_id
            ->retry(3, 2000, fn ($e) => !($e instanceof \Illuminate\Http\Client\RequestException)
                || in_array($e->response->status(), [429, 500, 502, 503, 504]), throw: false);
    }

    /** هل البوابة جاهزة لاستقبال الطلبات الآن؟ (المفتاح سليم، الاستقبال شغّال، والحد لم يمتلئ) */
    public function ready(): bool
    {
        $res = $this->http()->get('/ping')->json() ?? [];
        return ($res['ok'] ?? false) === true
            && ($res['data']['orders_enabled'] ?? false) === true
            && !($res['data']['order_quota']['reached'] ?? false);
    }

    public function products(?string $cursor = null): array
    {
        return $this->http()->get('/products', ['limit' => 200, 'cursor' => $cursor])->json() ?? [];
    }

    public function stock(array $skus): array
    {
        return $this->http()->get('/stock', ['skus' => implode(',', $skus)])->json() ?? [];
    }

    public function sendOrder(array $order): array
    {
        return $this->http()->post('/orders', $order)->json() ?? [];
    }

    public function cancelOrder(string $externalId, string $reason = ''): array
    {
        return $this->http()->post('/orders/' . rawurlencode($externalId) . '/cancel', ['reason' => $reason])->json() ?? [];
    }
}

إرسال الطلب بعد تأكيده (في طابور)

// app/Jobs/SendOrderToKashBoss.php
namespace App\Jobs;

use App\Models\Order;
use App\Services\KashBoss;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;

class SendOrderToKashBoss implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public $tries = 10;
    public $backoff = [10, 30, 60, 300, 900];

    public function __construct(public Order $order) {}

    public function handle(KashBoss $kashboss): void
    {
        $o = $this->order->load('items', 'customer');
        $res = $kashboss->sendOrder([
            'external_id'  => (string) $o->id,
            'order_number' => $o->number,
            // 'currency' اختياري: اتركه فارغًا ليأخذ عملة الشركة (أو مرّر عملتها من GET /ping)
            'customer'     => [
                'name'    => $o->customer->name,
                'phone'   => $o->customer->phone,
                'email'   => $o->customer->email,
                'address' => $o->shipping_address,
                'city'    => $o->shipping_city,
            ],
            'items' => $o->items->map(fn ($i) => [
                'sku'                 => $i->sku,
                'name'                => $i->name,
                'quantity'            => $i->quantity,
                'unit_price'          => $i->price,
                'external_product_id' => (string) $i->product_id,
            ])->values()->all(),
            'shipping' => $o->shipping_total,
            'discount' => $o->discount_total,
            'total'    => $o->grand_total,
            'payment'  => [
                'status'    => $o->is_paid ? 'paid' : 'cod',
                'method'    => $o->payment_method,
                'reference' => $o->payment_reference,
            ],
        ]);

        if (($res['ok'] ?? false) === true) {
            $o->update(['kashboss_invoice' => $res['data']['invoice_number'], 'kashboss_status' => null]);
            return;
        }
        $code = $res['error']['code'] ?? 'server_error';
        // أخطاء تزول بتدخّل صاحب الشركة أو بمرور الوقت (حدّ الشهر · الوحدة غير مفعّلة · المفتاح اتغيّر ·
        // الاستقبال موقوف · الفرع مغلق) ⇒ الطلب محفوظ عندك ويُعاد إرساله من الجدولة تحت، بلا فشل نهائي
        if (in_array($code, ['order_limit_reached', 'feature_locked', 'unauthorized', 'orders_disabled',
                             'connection_disabled', 'branch_unavailable', 'no_online_branch'])) {
            $o->update(['kashboss_status' => 'waiting_kashboss']);
            return;
        }
        if (in_array($code, ['server_error', 'rate_limited'])) {
            $this->release(60);            // مؤقت ⇒ إعادة بنفس external_id
        } else {
            logger()->error('KashBoss rejected order', ['order' => $o->id, 'error' => $res['error'] ?? null]);
            $this->fail();                  // بيانات لا تُقبل ⇒ راجعها ولا تكرّرها كما هي
        }
    }
}

// عند تأكيد الطلب (بعد الدفع أو عند تأكيد الدفع عند الاستلام):
SendOrderToKashBoss::dispatch($order);

// routes/console.php (أو Kernel::schedule) — الطلبات المنتظرة (حدّ الشهر أو إعداد يحتاج صاحب الشركة):
// تحقّق من GET /ping أولًا حتى لا تمتلئ سجلات «تقرير المتجر» بمحاولات ستُرفض حتمًا.
Schedule::call(function () {
    if (!app(\App\Services\KashBoss::class)->ready()) {
        return;
    }
    Order::where('kashboss_status', 'waiting_kashboss')
        ->each(fn ($o) => SendOrderToKashBoss::dispatch($o));
})->hourly();

استقبال التحديثات

// routes/api.php   (مسارات api بلا CSRF)
use App\Http\Controllers\KashBossWebhookController;
Route::post('/kashboss/webhook', KashBossWebhookController::class);
في لارافيل ١١ فما فوق لا يوجد routes/api.php افتراضيًّا؛ أنشئه بالأمر php artisan install:api. ومسارات هذا الملف تحمل البادئة /api تلقائيًّا، فرابط الاستقبال الذي تعطيه لصاحب الشركة هو https://your-site.com/api/kashboss/webhook (وليس /kashboss/webhook). تأكّد أنه يرد مباشرة دون تحويل (مثل إضافة شرطة أخيرة أو الانتقال من http إلى https).
// app/Http/Controllers/KashBossWebhookController.php
namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class KashBossWebhookController
{
    public function __invoke(Request $request)
    {
        $raw = $request->getContent();
        $ts  = $request->header('X-KashBoss-Timestamp', '');
        $sig = $request->header('X-KashBoss-Signature', '');
        $expected = hash_hmac('sha256', $ts . '.' . $raw, config('services.kashboss.webhook_secret'));
        $valid = collect(explode(',', $sig))
            ->contains(fn ($p) => hash_equals($expected, preg_replace('/^v1=/', '', trim($p))));
        abort_unless($valid && abs(time() - (int) $ts) <= 300, 401);

        // لا تعتمد على X-KashBoss-Event-Id لمنع التكرار (يتغيّر مع كل إعادة محاولة)؛
        // الحماية هي مقارنة seq لكل صنف أدناه.
        $event = json_decode($raw, true);
        if (($event['type'] ?? '') === 'inventory.updated') {
            foreach ($event['data']['items'] as $item) {
                // صنف بلا كود (null): where('sku', null) كان يحدّث أول صنف عندك بلا كود!
                $product = !empty($item['sku'])
                    ? Product::where('sku', $item['sku'])->first()
                    : (!empty($item['external_id']) ? Product::find($item['external_id']) : null);
                if (!$product || ($product->kashboss_seq ?? 0) >= $item['seq']) {
                    continue;                // صنف غير موجود عندك أو تحديث أقدم
                }
                $product->update([
                    'stock'        => $item['deleted'] ? 0 : ($item['available_qty'] ?? $product->stock),
                    'price'        => $item['price'] ?? $product->price,
                    'is_active'    => $item['is_active'] && !$item['deleted'],
                    'kashboss_seq' => $item['seq'],
                ]);
            }
        }
        return response()->noContent();      // 204 خلال ثوانٍ
    }
}

أضف عمود kashboss_seq (bigint) لجدول الأصناف لحفظ ترتيب التحديثات. وللأصناف ذات المقاسات طابِق sku المتغيّر بجدول المتغيّرات عندك.

المزامنة الأولى (أمر Artisan)

// app/Console/Commands/KashBossSync.php → php artisan kashboss:sync
public function handle(\App\Services\KashBoss $kashboss)
{
    $cursor = null;
    do {
        $page = $kashboss->products($cursor);
        foreach ($page['data'] as $p) {
            // أنشئ أو حدّث الصنف عندك بـ $p['sku'] و$p['price'] و$p['available_qty'] و$p['variants']
        }
        $cursor = $page['next_cursor'] ?? null;
    } while ($cursor);
}

١١. ووكومرس باختصار

١٢. قائمة قبل التشغيل

  1. المفتاح والسرّ في .env على الخادم فقط.
  2. GET /ping يرجع اسم الشركة والعملة الصحيحة.
  3. أكواد أصناف موقعك تطابق أكواد كاش بوس (راجعها من GET /products).
  4. جرّب طلبًا بـ ?dry_run=1 وراجع المعاينة.
  5. رابط الاستقبال يتحقق من التوقيع ويرد 2xx سريعًا، و«إرسال حدث تجريبي» ينجح.
  6. إرسال الطلب يتم في طابور بإعادة محاولة، ونفس external_id في كل محاولة.
  7. الطلب المرفوض بخطأ مؤقت (order_limit_reached · feature_locked · 401 بعد تغيير المفتاح · branch_unavailable) لا يُحذف ولا يُعدّ فاشلًا، ويُعاد إرساله تلقائيًّا بعد أن يرد GET /ping بالجاهزية.
  8. رابط الاستقبال نهائي (لا يمرّ بتحويل) ويشمل /api إن كان في routes/api.php.

للاستفسار التقني تواصل مع فريق كاش بوس عن طريق صاحب الشركة.