CallwiseDocs

عقد خدمة الصوت

ويب هوك الموقَّع الذي تستدعيه خدمة الصوت لإنشاء المكالمات وإضافة أدوار النص وإغلاق المكالمات.

عقد فقط

هذا عقد. خدمة الصوت بـ Python (تحويل الكلام إلى نص، ونموذج اللغة، وتحويل النص إلى كلام عبر WebRTC وSIP) لم تُبنَ بعد. جانب Callwise من ويب هوك مبني ومختبر؛ ولا شيء يستدعيه في الإنتاج.

خدمة الصوت هي الكاتب الوحيد لسجلات المكالمات. تبلّغ التطبيق عن كل مكالمة بسلسلة أحداث. مصدر الحقيقة للحمولة هو مخطط zod المسمى voiceEventSchema في apps/web/lib/callwise/voice-events.ts.

نقطة النهاية

POST /api/voice/events
Content-Type: application/json
x-callwise-timestamp: 1760530212
x-callwise-signature: 5f0c2e…(64 hex characters)

التوقيع

الترويسةالقيمة
x-callwise-timestampوقت Unix بالثواني عند توقيع الطلب
x-callwise-signatureHMAC-SHA256 بالنظام الست عشري لـ `${timestamp}.${rawBody}` باستخدام السر المشترك CALLWISE_VOICE_SECRET

وقّع البايتات التي ترسلها بالضبط. يعيد التطبيق حساب التوقيع على الجسم الخام قبل تحليله.

import { createHmac } from 'node:crypto';

const timestamp = Math.floor(Date.now() / 1000).toString();
const rawBody = JSON.stringify(event);
const signature = createHmac('sha256', process.env.CALLWISE_VOICE_SECRET!)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');
import hmac, hashlib, json, os, time

timestamp = str(int(time.time()))
raw_body = json.dumps(event, separators=(",", ":"))
signature = hmac.new(
    os.environ["CALLWISE_VOICE_SECRET"].encode(),
    f"{timestamp}.{raw_body}".encode(),
    hashlib.sha256,
).hexdigest()

تُرفض الطلبات التي يبعد طابعها الزمني أكثر من 5 دقائق عن ساعة الخادم، وهو ما يوقف إعادة إرسال الطلبات القديمة.

الاستجابات

الحالةمتى
200 { "ok": true, "duplicate": false }طُبِّق الحدث
200 { "ok": true, "duplicate": true }طُبِّق حدث بهذا id من قبل؛ ولم يتغير شيء
400الجسم ليس JSON صالحًا، أو لا يطابق المخطط (مثلًا نوع حدث مجهول)
401التوقيع مفقود أو خاطئ، أو الطابع الزمني خارج نافذة 5 دقائق

تماثل المفعول

لكل حدث id خاص به. إرسال الحدث نفسه مرتين آمن: يُقر الثاني بـ duplicate: true ويُتجاهل. أعد المحاولة عند أخطاء الشبكة و5xx بـ نفس id.

شكل الحدث

لكل حدث الغلاف نفسه:

الحقلالنوعملاحظات
idسلسلة، من 1 إلى 200 حرفًامعرّف الحدث، فريد لكل حدث
typecall.started | call.turn | call.ended
accountIduuidمساحة العمل (حساب الفريق)
callكائنcall.id (uuid) مطلوب. تختاره خدمة الصوت وتستخدم المعرّف نفسه لكل حدث من تلك المكالمة.
turnكائنعلى call.turn فقط

call.started

ينشئ مكالمة مباشرة. يُطابَق المتصل برقم الهاتف، أو يُنشأ. إذا تُرك agentId، يُختار الوكيل من الرقم الذي تم الاتصال به.

{
  "id": "evt_01JA8S0K6Q2M3",
  "type": "call.started",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": {
    "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33",
    "to": "+15552108842",
    "callerPhone": "+15551237890",
    "callerName": "Emily Johnson",
    "direction": "inbound",
    "startedAt": "2026-10-15T13:41:02Z",
    "isTest": false,
    "afterHours": false
  }
}

حقول call الاختيارية: numberId (uuid) أو to (E.164)، وagentId، وlocationId، وcallerPhone، وcallerName، وdirection (inbound أو outbound)، وstartedAt (ISO 8601)، وisTest، وafterHours.

call.turn

يضيف سطرًا واحدًا من النص. لا يحمل call إلا المعرّف. تُخزَّن أدوار النص حسب (معرّف المكالمة، seq)، لذا إعادة إرسال دور تستبدله.

{
  "id": "evt_01JA8S0M1T7Z4",
  "type": "call.turn",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": { "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33" },
  "turn": {
    "seq": 1,
    "atMs": 4200,
    "speaker": "caller",
    "text": "Hi, I need a cleaning next week."
  }
}
حقل turnالنوع
seqعدد صحيح ≥ 0، الترتيب داخل المكالمة
atMsعدد صحيح ≥ 0، ميلي ثانية منذ بدء المكالمة
speakeragent | caller | staff | system
textسلسلة نصية

call.ended

يغلق المكالمة. الدقائق المفوترة هي ceil(durationS / 60) وتُضاف إلى استخدام اليوم. ثم يحصّل التطبيق أرصدة للمكالمة: الدقائق المفوترة × معدل مكدس نماذج الوكيل + ملخص واحد (انظر نماذج الذكاء الاصطناعي والأرصدة)، تُؤخذ من رصيد الأرصدة المشترك. إذا تُرك summary، يُملأ ملخص من قالب.

عند call.started، تخبر الاستجابة خدمة الصوت كيف توجّه المكالمة: route: "agent"، أو route: "fallback" مع reason عندما يقول رصيد الأرصدة أو حد التزامن أو الحد الساعي لا. لا تُسقط المكالمات أبدًا.

{
  "id": "evt_01JA8S2B9W0K8",
  "type": "call.ended",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": {
    "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33",
    "endedAt": "2026-10-15T13:42:26Z",
    "durationS": 84,
    "intent": "booking",
    "topic": "Dental cleaning",
    "outcome": "booked",
    "sentiment": "positive",
    "confidence": 0.96,
    "summary": "New caller booked a dental cleaning for Tuesday at 10:00.",
    "recordingPath": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10/9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33.mp3"
  }
}
حقل callالقيم
endedAtISO 8601
durationSثوانٍ بعدد صحيح
intentbooking reschedule cancel pricing insurance billing hours emergency general spam
topicنص حر
outcomebooked rescheduled cancelled info transferred voicemail missed spam
sentimentpositive neutral negative
confidence0–1
summaryنص حر
handoffReasonنص حر، عندما تكون النتيجة transferred
recordingPath<accountId>/<callId>.mp3 في الحاوية الخاصة cw-recordings

كيف يُطبَّق

يتحقق المسار من التوقيع والمخطط، ثم يستدعي دالة Postgres المسماة cw_apply_voice_event(jsonb) بدور الخدمة. دور الخدمة وحده يستطيع تشغيل تلك الدالة، وليس للمستخدمين الذين سجّلوا الدخول صلاحية إدراج على المكالمات أو أدوار النص، لذا ويب هوك هو الطريقة الوحيدة لإنشاء سجلات المكالمات.

قبل الإطلاق

انظر مدخل خدمة الصوت ضمن الاتصالات لمعرفة ما يجب التحقق منه: زمن الاستجابة، والتعامل مع المقاطعة، وتدوير السر.

On this page