إنتقل إلى المحتوى الرئيسي

الإعادة الآمنة (Idempotency)

أعطال الشبكة لا مفرّ منها. حين ترسل رسائل وقوالب واتساب، أو ترد في محادثة، تستخدم الواجهة البرمجية العامة لمجيب رأس Idempotency-Key لجعل الإعادات آمنة — نفس المفتاح ونفس الجسم يُعيدان الاستجابة الأصلية، ولا يُرسَل شيء مرتين.

كيف أجعل الإعادات آمنة؟​

أرسل أي نص يختاره العميل بطول حتى 255 حرفاً في رأس Idempotency-Key حين ترسل رسالة أو قالب واتساب (POST /v1/whatsapp/messages وPOST /v1/whatsapp/templates) أو ترد في محادثة (POST /v1/agents/{agentId}/conversations/{conversationId}/messages):

curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-12345-confirmation" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "order_shipped",
"language": "en",
"parameters": {
"customer_name": "Sara",
"tracking_url": "https://example.com/track/A123"
}
}
}'

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

ماذا يحدث عند الإعادة؟​

لا يُخزَّن إلا الجواب الناجح: الإرسال المقبول (202)، أو الرد في محادثة (201). ولا يُحجَز المفتاح إلا بعد أن يجتاز الطلب فحوص التوثيق والنطاق وحد المعدّل والصيغة، وفي الرد فحص الوكيل والمحادثة. الطلب المرفوض عندها (401 أو 403 أو 413 أو 429، أو 422 بسبب الصيغة مثل invalid_request_body أو invalid_agent_id أو invalid_phone_number أو template_header_media_url_invalid، أو في الرد 404 لوكيل أو محادثة لا يصل إليها المفتاح، أو 503) لا يمسّ المفتاح أبداً: صحّحه وأعد المحاولة بالمفتاح نفسه فوراً.

وبعد حجز المفتاح، يعتمد السلوك على كيف انتهى الطلب الأصلي وما إذا كان الجسم متطابقاً:

السيناريوالاستجابةHTTP
أول إرسال بهذا المفتاحاستجابة طبيعية202 (201 للرد)
نفس المفتاح بأي جسم، ما دام طلب به قيد التنفيذ، أو لم تمضِ 60 ثانية على بدء طلب به رُفض أو فشل بعد حجزه (مثل agent_not_found أو from_phone_not_found_for_agent أو template_not_found في إرسال واتساب، أو messaging_window_closed أو channel_not_supported في رد، أو 5xx)idempotency_key_in_progress (مع Retry-After)409
نفس المفتاح + نفس الجسم، الأصلي مقبولإعادة الاستجابة الأصلية، رأس Idempotent-Replayed: true202 (201 للرد)
نفس المفتاح + جسم أو نقطة نهاية أو محادثة مختلفة، الأصلي مقبولidempotency_key_in_use_with_different_params422
نفس المفتاح وقد بدأ ذلك الطلب المرفوض أو الفاشل منذ 60 ثانية أو أكثر، أو مفتاح مختلفيُعالَج كطلب جديد—

كم تُخزَّن الاستجابات؟​

24 ساعة على الأقل: إعادة المحاولة بالمفتاح نفسه والجسم نفسه خلالها تُعيد الجواب المُخزَّن دائماً. ولا تعتمد على أن يصبح المفتاح جديداً بعدها. أعطِ كل عملية جديدة مفتاحاً جديداً، واختر مفاتيح ثابتة مرتبطة بعملية أعمالك، لا UUIDs عشوائية تتغيّر بين المحاولات.

كيف أختار مفاتيح إعادة جيّدة؟​

المفتاح الجيد ثابت، فريد، ومرتبط بعملية أعمال واحدة.

جيّد:

  • order-12345-confirmation
  • signup-456-welcome
  • appointment-reminder-2026-04-30-user-789

سيّئ:

  • الطوابع الزمنية (كل إعادة تأخذ واحدة مختلفة — يُفقِد المعنى)
  • UUIDs عشوائية تُولَّد وقت الطلب (نفس المشكلة)
  • نصوص عامة مثل retry-1 (تصادم بين العمليات)
  • مفاتيح مُعاد استخدامها مع أجسام رسائل أو محادثات مختلفة (يُحفّز اختلاف 422)

إن لم يكن لديك معرّف ثابت طبيعي، أنشئه مرة واحدة في بداية عمليتك وأعد استخدامه لكل إعادة محاولة لذلك الإرسال بالذات.

كيف يبدو الاختلاف؟​

عند إعادة استخدام نفس المفتاح بجسم مختلف، أو على نقطة نهاية أو محادثة أخرى، الواجهة ترفض الطلب وتُعطيك معرّف الارتباط للإرسال الأصلي حتى تجده في سجلاتك:

{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_in_use_with_different_params",
"message": "The idempotency key was used previously with a different request body. Use a different key for this operation.",
"correlation_id": "req_01KQE...",
"original_correlation_id": "req_01KQE..."
}
}

original_correlation_id هو معرّف التتبّع للطلب الذي حجز المفتاح أصلاً. ابحث في سجلات تطبيقك عن هذا المعرّف لترى ما أُرسل.

كيف تبدو إعادة محاولة قيد التنفيذ؟​

إن أعدت المحاولة، بأي جسم، بينما الإرسال الأصلي ما زال يُعالَج (نادر، عادة في غضون ثوانٍ)، أو خلال 60 ثانية من بدء طلب بالمفتاح رُفض أو فشل بعد حجزه:

{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_in_progress",
"message": "A request with this idempotency key is currently being processed. Retry shortly.",
"correlation_id": "req_01KQE..."
}
}

تحمل الاستجابة أيضاً رأس Retry-After بالثواني. انتظر هذه المدة، ثم أعد بنفس المفتاح ونفس الجسم — ستحصل على إعادة الاستجابة الأصلية (202، أو 201 للرد) إن كان الأصلي مقبولاً، أو محاولة جديدة بعد مرور 60 ثانية على بدء الطلب المرفوض أو الفاشل، أو 409 آخر قبل ذلك أو ما دام قيد التنفيذ.

أسئلة شائعة​

هل تُجزّئ الواجهة جسمي لاكتشاف الاختلاف؟​

نعم. أول طلب بمفتاح معيّن يُخزّن hash بـSHA-256 للطلب: طريقته ومساره وجسمه المُقَنَّن. الطلبات اللاحقة تُعيد حساب الـhash وتقارن. ترتيب الحقول لا يهم؛ نُرتّب المفاتيح قبل التجزئة — {"a":1,"b":2} و {"b":2,"a":1} هما نفس الجسم منطقياً. والمسار جزء منه أيضاً: بعد قبول الأصلي، المفتاح نفسه بالجسم نفسه على نقطة نهاية أخرى، أو في رد على محادثة أخرى، اختلافٌ (422) لا إعادة.

ماذا عن الإعادة الآمنة على طلبات GET؟​

نقاط GET إعادة-آمنة بطبيعتها — استدعاؤها عدة مرات لا أثر له. رأس Idempotency-Key يُتجاهَل على GET؛ لا حاجة لإرساله.

هل أحتاج مفاتيح إعادة لاستعلامات الحالة؟​

لا. GET /v1/whatsapp/messages/{id} للقراءة فقط.

ماذا لو كانت الاستجابة المُخزَّنة خطأ؟​

لا تُخزَّن. لا يُخزَّن إلا الجواب الناجح. بعد طلب رُفض أو فشل بعد حجز المفتاح (4xx مثل template_not_found أو messaging_window_closed في رد، أو 5xx)، يُجيب المفتاح نفسه بـ409 idempotency_key_in_progress أياً كان الجسم، حتى تمضي 60 ثانية على بدء ذلك الطلب، ثم يُنفَّذ الطلب من جديد. لإعادة طلب مُصحَّح فوراً، استخدم مفتاحاً مختلفاً. أما الطلب المرفوض قبل حجز المفتاح، مثل 422 بسبب الصيغة، فلا يحجز المفتاح.

هل الإعادة هي الاستجابة الأصلية نفسها؟​

نعم. تُعيد الإعادة رمز الحالة الأصلي والجسم نفسه: الحقول نفسها، ومنها الحقول التي قيمتها null، بالقيم نفسها. ولا يُضاف إلا رأس Idempotent-Replayed: true، لتميّز الإعادة من الاستجابة الأصلية. وقد يختلف ترتيب الحقول، فقارن JSON بعد تحليله لا البايتات الخام.