Get a quote

بناء نظام تسليم Webhook موثوق في Go لمنصات SaaS

نظام الـ Webhook يبدو بسيطاً حتى يفشل صامتاً الساعة الثالثة فجراً ويعطل مزامنة بيانات عميل يدفع اشتراكاً شهرياً. هذا كيف نصمم أنظمة التسليم التي تعمل فعلاً في بيئة الإنتاج.

نظام الـ Webhook يبدو بسيطاً في البداية: مجرد POST إلى URL سجله العميل. لكن الصعوبة الحقيقية تكمن في كل ما يحيط بهذا الطلب. ماذا يحدث عندما يكون خادم العميل معطلاً؟ عندما ترسل نفس الحدث مرتين؟ عندما يعيد الخادم 200 لكنه يتجاهل البيانات؟ هذا الدليل يشرح كيف نصمم أنظمة تسليم Webhook موثوقة في Go لمنصات SaaS تخدم لبنان ومنطقة الشرق الأوسط.

لماذا تسليم الـ Webhook أصعب من مجرد POST بسيط؟

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

التطبيقات الساذجة تستدعي نقطة نهاية العميل داخل معاملة قاعدة البيانات مباشرةً. هذا يخلق ارتباطاً صلباً بين صحة نظامك وتوافر طرف خارجي. الحل الصحيح يفصل توليد الأحداث عن التسليم.

نمط الصندوق الصادر: كتابة الأحداث بشكل ذري

نمط الصندوق الصادر (Outbox Pattern) يخزن أحداث الـ Webhook في نفس معاملة قاعدة البيانات مع الحدث التجاري الذي يطلقها. هذا يضمن أنك لن تولد حدثاً تجارياً بدون سجل Webhook مقابل، ولن تخلق سجل Webhook بدون حدث تجاري مقابل.

في PostgreSQL:

CREATE TABLE webhook_events (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id   UUID NOT NULL,
    event_type  TEXT NOT NULL,
    payload     JSONB NOT NULL,
    status      TEXT DEFAULT 'pending',
    attempts    INT DEFAULT 0,
    next_retry  TIMESTAMPTZ DEFAULT NOW()
);

داخل المعالج الذي ينفذ العملية التجارية:

func (s *Service) ProcessOrder(ctx context.Context, order Order) error {
    return s.db.BeginTxFunc(ctx, func(tx *sqlx.Tx) error {
        if err := insertOrder(ctx, tx, order); err != nil {
            return err
        }
        payload, _ := json.Marshal(OrderCreatedEvent{OrderID: order.ID})
        _, err := tx.ExecContext(ctx,
            `INSERT INTO webhook_events (tenant_id, event_type, payload)
             VALUES ($1, 'order.created', $2)`,
            order.TenantID, payload)
        return err
    })
}

عمال التسليم: منطق إعادة المحاولة

عامل التسليم يستطلع الصندوق للأحداث المعلقة ويحاول التسليم. الأسلوب الصحيح هو التراجع الأسي مع التذبذب:

  • المحاولة الأولى: فورية
  • المحاولة الثانية: 30 ثانية
  • المحاولة الثالثة: 5 دقائق
  • المحاولة الرابعة: 30 دقيقة
  • المحاولة الخامسة: ساعتان
  • ما بعد ذلك: 24 ساعة ثم تُعلَّم الحالة كفاشلة

يستخدم العامل FOR UPDATE SKIP LOCKED في PostgreSQL لضمان أن عدة عمال متوازيين لا يتسلمون نفس الحدث:

func (w *Worker) claimEvent(ctx context.Context) (*WebhookEvent, error) {
    var event WebhookEvent
    err := w.db.QueryRowContext(ctx, `
        UPDATE webhook_events
        SET status = 'processing'
        WHERE id = (
            SELECT id FROM webhook_events
            WHERE status = 'pending'
              AND next_retry <= NOW()
            ORDER BY next_retry
            LIMIT 1
            FOR UPDATE SKIP LOCKED
        )
        RETURNING *
    `).Scan(&event.ID, ...)
    return &event, err
}

التوقيع الرقمي والتحقق من الهوية

يجب أن يتمكن العملاء من التحقق من أن التسليم صادر فعلاً من نظامك. الأسلوب المعياري هو توقيع HMAC-SHA256:

func sign(payload []byte, secret string) string {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}

أرسل التوقيع في الرأس:

X-Voxire-Signature: sha256=a4b8c2d1...

كل مستأجر يحصل على مفتاح توقيع مستقل. يجب أن يكون المفتاح قابلاً للتدوير مع فترة سماح للمرور السلس دون توقف الخدمة.

قواطع الدائرة وإدارة صحة نقاط النهاية

ليست كل نقاط نهاية العملاء متساوية. بعضها غير مستقر ويفشل 20 بالمئة من الوقت، وبعضها معطل بشكل دائم. التسليم المكثف لنقطة نهاية ميتة يهدر الموارد الحسابية.

احتفظ بحالة صحية لكل نقطة نهاية. بعد إخفاقات متتالية، افصل الدائرة: توقف عن محاولة التسليم حتى تنتهي فترة التهدئة، ثم أرسل طلباً اختبارياً واحداً. إذا نجح، استأنف التسليم الطبيعي.

في منطقة الشرق الأوسط وخلال نوافذ صيانة الإنترنت وليالي رمضان، يكون قاطع الدائرة أساسياً لمنع تراكم طوابير التسليم.

المراقبة والرصد

تسليم الـ Webhook عملية خلفية. بدون مراقبة، لن تعلم بالإخفاقات حتى يشكو العملاء.

الحد الأدنى من المقاييس لكل مستأجر:

  • معدل نجاح التسليم خلال الـ 24 ساعة الأخيرة
  • عمق طابور الانتظار الحالي
  • متوسط وقت الاستجابة
  • عدد الإخفاقات المتتالية

سجلات منظمة على كل محاولة تسليم مع النتيجة وزمن الاستجابة وكود HTTP ومعرف المستأجر تجعل التحقيق في شكاوى العملاء سريعاً. عملاء الشركات اللبنانية والخليجية يتوقعون سجل تسليم يمكنهم فحصه عندما يلاحظون شيئاً غير صحيح في أنظمتهم.

هل تبني نظام Webhook لمنصة SaaS؟

فريق Voxire يبني أنظمة خلفية احترافية لفرق SaaS في لبنان والشرق الأوسط، تشمل البنية التحتية لتسليم الأحداث، وأنظمة Webhook، وخطوط معالجة غير متزامنة. تواصل معنا إذا كنت تصمم نظاماً مشابهاً أو تواجه مشكلات موثوقية في تطبيق قائم.

https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp