Trang chủ Kiến thức Truy dấu cuộc gọi qua nhiều chặng
Tổng đài PBX

Truy dấu cuộc gọi qua nhiều chặng — ghi lại toàn bộ hành trình từ IVR đến agent

Tháng 07/2026 12 phút đọc Đội ngũ kỹ thuật Zorio

Trên một tổng đài IP hiện đại, một cuộc gọi từ khách hàng gần như không bao giờ chỉ có "ring → answer → hangup" đơn giản. Nó đi qua rất nhiều tầng logic:

Với chuỗi 5–6 khâu như vậy, khi supervisor mở báo cáo và thấy một cuộc gọi có duration = 47s, result = no_answer, câu hỏi đầu tiên luôn là: cuộc gọi mất ở khâu nào?

Hai cuộc gọi cùng no_answer + 47s có thể mang ý nghĩa hoàn toàn khác nhau. Nếu tổng đài chỉ ghi lại kết quả cuối cùng, supervisor sẽ chẩn đoán sai và ra quyết định vận hành sai.

Bài viết này mổ xẻ cách nền tảng Zorio Contact Center Platform giải bài toán trên: từ mô hình dữ liệu, cách gán nhãn từng chặng cuộc gọi, đến cách dựng báo cáo waterfall để supervisor thấy rõ hành trình.

Kiến trúc leg — nền móng để truy dấu

Trong bất kỳ tổng đài IP hiện đại nào, mỗi cuộc gọi được chia thành các leg — mỗi kết nối một chiều là một leg riêng biệt:

Khi có transfer, một B-leg mới sẽ được tạo — tổng đài giữ A-leg (khách hàng không phải quay số lại), chỉ thay thế B-leg cũ. Điều này có nghĩa: một cuộc gọi có thể có nhiều B-leg nối tiếp nhau, nhưng chỉ có một A-leg duy nhất từ đầu đến cuối.

Zorio khai thác chính xác đặc tính này. Mỗi hàng trong bảng cdrs (Call Detail Record) ứng với một A-leg — một cuộc gọi trọn vẹn từ góc nhìn của khách. Bên cạnh đó, một bảng phụ tên cdr_legs ghi lại từng B-leg riêng biệt, mỗi lần chuyển tiếp là một dòng mới.

Sơ đồ hành trình một cuộc gọi có transfer
Khách → PBX
INVITE — gọi vào 1900xxxx A-leg khởi tạo
PBX
Router match DID → Time Condition (trong giờ)
PBX → Khách
IVR "Chào mừng, bấm phím 1 gặp Kinh doanh…"
Khách → PBX
DTMF 1 → định tuyến vào Queue "sales_hn"
PBX → Agent 2001
Bridge B-leg #1 — ring 8s → 200 OK talk 2 phút
Agent 2001
REFER — transfer đến máy nhánh 2015
PBX → Agent 2015
Bridge B-leg #2 — ring 4s → 200 OK talk 5 phút
Khách → PBX
BYE — khách kết thúc, đóng A-leg
Một A-leg duy nhất (khách ↔ tổng đài) tồn tại xuyên suốt; hai B-leg nối tiếp nhau khi transfer.

Kết quả trong database:

Nhờ tách biệt A-leg và B-leg trong hai bảng, ta trả lời được cả hai câu hỏi:

Biến ngữ cảnh cuộc gọi — "gắn thẻ" cuộc gọi khi nó đang chạy

Nhân xử lý cuộc gọi cho phép gán các biến metadata (call context variable) vào cuộc gọi khi nó đi qua từng khâu. Zorio định nghĩa một bộ biến tiền tố zorio_* để làm nhật ký thời gian thực. Đây là những biến quan trọng nhất:

BiếnÝ nghĩaVí dụ giá trị
zorio_routing_stageChặng cuộc gọi đang ở đâu trong flow routingivr, queue, ring_group, extension
zorio_routing_pathChuỗi các chặng đã đi qua, ngăn cách >ivr>queue>extension
zorio_exit_reasonLý do cuộc gọi rời tổng đàianswered, caller_abandoned_queue
zorio_ivr_id / _nameID + tên IVR cuộc gọi đang ở12 / Chào mừng buổi sáng
zorio_queue_id / _nameID + tên hàng đợi4 / sales_hn
zorio_ring_group_id / _nameID + tên nhóm đổ chuông2 / Nhóm Kinh doanh
zorio_agent_nameTên agent nhấc máyNguyễn Văn A (2001)
zorio_leg_stageStage của B-leg đang được tạoextension, queue, external
zorio_leg_entity_id / _nameEntity tương ứng với leg đó2001 / Extension 2001
zorio_inbound_didSố DID khách gọi vào1900xxxx
zorio_displayed_cidCaller ID hiển thị cho agent0912345678
zorio_a_leg_eventsJSON các event trên A-leg (audit log)[{ev:"ivr_dtmf",val:"1"},…]

Nguyên tắc thiết kế:

  1. Gán ngay tại chặng, không đợi đến cuối — mỗi handler routing khi tiếp nhận cuộc gọi phải cập nhật ngay zorio_routing_stage, append vào zorio_routing_path, và (nếu là stage cuối) set zorio_exit_reason. Nếu để cuối cuộc gọi mới tính, khi có sự cố nhân xử lý dừng đột ngột giữa chừng, sẽ mất thông tin không hồi phục được.
  2. Không overwrite zorio_inbound_did — DID gốc phải giữ nguyên qua mọi transfer để phân tích đúng "khách gọi số nào". Đây là một pitfall phổ biến khi viết handler mới.
  3. zorio_a_leg_events là audit log dạng JSON append-only — mỗi event (DTMF khách bấm, timeout queue, agent join/leave) được push vào cuối array. Khi có tranh chấp với khách ("tôi bấm phím 2 nhưng vào sai bộ phận"), supervisor có bằng chứng thời gian thực.

Từ biến ngữ cảnh đến database — pipeline hoạt động ra sao

Cuộc gọi kết thúc, nhân xử lý cuộc gọi sinh sự kiện hangup_complete với toàn bộ metadata snapshot. Zorio nhận sự kiện này qua một service realtime nội bộ, sau đó forward sang tầng ứng dụng để ghi vào MySQL:

Pipeline xử lý sự kiện cuộc gọi
Sự kiện
hangup_complete
Realtime worker
event push
Backend worker
ProcessCdrJob
computeResult
+ hangup map
cdrs + cdr_legs
A-leg + N B-leg
Sự kiện realtime → hàng đợi → worker chuẩn hoá → ghi 1 row A-leg và N row B-leg, sẵn sàng cho dashboard.

Điểm mấu chốt là computeResult — function tổng hợp tất cả tín hiệu (hangup cause, billsec, exit_reason, transfer_count, agent extension có được set không) để đưa ra 7 giá trị kết quả cuối cùng:

resultÝ nghĩaĐiều kiện điển hình
answeredAgent nhấc máy, có nói chuyệnbillsec > 0, NORMAL_CLEARING, có agent
no_answerKhông ai nhấc máyNORMAL_CLEARING + billsec = 0, hoặc timeout
busyMáy bậnUSER_BUSY / SIP 486
cancelledKhách bỏ máy giữa chừngORIGINATOR_CANCEL trước khi answer
rejectedBị từ chối chủ độngSIP 603, agent reject, out of hours
voicemailChuyển vào hộp thưvm_recorded, vm_too_short
failedLỗi kỹ thuậtNORMAL_TEMPORARY_FAILURE, network…

Việc chuẩn hoá 7 kết quả này rất quan trọng vì nhân xử lý cuộc gọi sinh ra tới hơn 40 hangup cause khác nhau (theo chuẩn Q.850 telco). Nếu để supervisor tự phân tích cause code, không thể ra được báo cáo có ý nghĩa. Zorio đóng gói toàn bộ logic đó vào một helper class duy nhất, dùng chung cho cả CDR inbound và outbound, đảm bảo nhất quán số liệu.

Đặc biệt lưu ý case "NORMAL_CLEARING + billsec = 0" — nếu chỉ nhìn hangup cause, ai cũng nghĩ cuộc gọi bình thường. Nhưng billsec = 0 nghĩa là chưa từng answer. Zorio map case này thành no_answer, không phải answered. Bỏ qua chi tiết này, tỷ lệ Answer Rate toàn hệ thống sẽ sai lệch 5–15%.

Trường routing_stageexit_reason — hai cột đá tảng cho báo cáo

Trong bảng cdrs, hai enum sau được "chưng cất" từ chuỗi zorio_routing_pathzorio_exit_reason lúc INSERT:

routing_stage ENUM(
    'not_routed', 'out_of_hours', 'ivr', 'queue',
    'ring_group', 'extension', 'voicemail', 'external'
)

exit_reason ENUM(
    'answered',
    'caller_abandoned_ivr', 'caller_abandoned_queue', 'caller_abandoned_ringing',
    'timeout_ivr', 'timeout_queue', 'timeout_ring',
    'no_route', 'out_of_hours',
    'agent_busy', 'agent_reject', 'technical_failure',
    'vm_recorded', 'vm_too_short', 'vm_record_failed'
)

Sự khác biệt:

Ví dụ với cuộc gọi khách bỏ máy giữa lúc chờ trong hàng đợi 30 giây: routing_stage = 'queue', exit_reason = 'caller_abandoned_queue', queue_wait_time = 30, result = 'cancelled'.

Nhờ tách hai chiều thông tin này, supervisor lọc trên dashboard theo bất kỳ tổ hợp nào:

final_destination_type — "đích đến cuối cùng là gì?"

Một chi tiết dễ bỏ sót: nếu cuộc gọi kết thúc ở IVR (khách bấm 0 thoát), thì "đích đến" là gì? Nếu kết thúc ở voicemail thì là hộp thư của extension nào? Zorio dùng thêm cột final_destination_type với 10 giá trị enum để phân loại:

final_destination_typeNghĩaVí dụ label
agent_extBắt trực tiếp máy nhánh, có agent nhấc máyNguyễn Văn A (2001)
agent_queueVào queue rồi được agent bắt máyQueue Sales · Nguyễn Văn A (2001)
agent_rgVào ring group rồi có agent bắt máyNhóm Kinh doanh · Trần B (2015)
ivrKết thúc trong IVR (khách bỏ/bấm exit)IVR: Chào mừng buổi sáng
queue_unansweredVào queue nhưng không có agent bắt máyQueue Sales
rg_unansweredVào ring group nhưng không có agentNhóm Kinh doanh
voicemailChuyển vào hộp thưVoicemail 2001
externalChuyển ra số ngoại tuyến (forward, mobile)Forward 0912345678
not_routedKhông match route nào
hangupKết thúc bất thường

Đi kèm là final_destination_label — chuỗi human-readable đã dựng sẵn, hiển thị ngay trên báo cáo mà không cần join thêm bảng nào. Khi supervisor mở màn hình chi tiết một cuộc gọi trong Zorio, họ thấy ngay:

1900xxxx → 0912345678 · Kết quả: answered · Thời lượng: 8 phút
Đường đi: IVR "Chào mừng buổi sáng" → Queue "sales_hn" (chờ 12s) → Nguyễn Văn A (2001) → transfer → Trần B (2015)
Đích đến cuối: agent_ext — Trần B (2015)

Không cần chạy query SQL, không cần join, không cần đoán.

Waterfall report — nhìn thấy hành trình bằng mắt

Cấu trúc dữ liệu bên trên là nền móng cho một kiểu báo cáo cực kỳ hữu ích trong contact center: Waterfall report — biểu diễn hành trình cuộc gọi dưới dạng thanh ngang phân đoạn màu.

Waterfall — cuộc gọi khoẻ mạnh (tổng ~7,5 phút)
Router + Time
0,7s
IVR chào mừng
8s
Queue sales_hn
chờ 12s
Ring Agent 2001
4s
Talk 2001
2 phút
Transfer + Ring 2015
4s
Talk 2015
5 phút
Định tuyến IVR Chờ hàng đợi Đổ chuông Đàm thoại
Không có "khe hở" chết — khách chờ queue 12s (bình thường), 2 agent nói chuyện liền mạch. Hệ thống routing hoạt động tốt.
Waterfall — cuộc gọi có vấn đề (khách bỏ ngay khi vừa được nối)
IVR
3s
Queue (chờ lâu)
chờ 47s
Ring Agent
12s
Talk
5s ↯
IVR Chờ quá lâu Đổ chuông Đàm thoại
Khách bỏ máy chỉ 5s sau khi agent nhấc — do đã phải chờ 47s trong queue. Đây là customer experience issue: không phải agent lỗi, không phải kỹ thuật lỗi, mà cần tăng agent trực queue hoặc đàm phán lại SLA.

Best practices khi thiết kế flow multi-hop

  1. Set zorio_exit_reason càng sớm càng tốt — mỗi handler khi có tình huống "cuộc gọi rời khỏi handler này" (timeout, khách hangup, chuyển tiếp) phải set ngay tại đó. Không gộp về xử lý ở cuối vì nếu chuỗi có 5 handler, khó truy được ai quyết định exit.
  2. Không lồng transfer quá 3 tầng — mỗi lần transfer tạo thêm 1 B-leg, tăng độ phức tạp phân tích. Nếu flow cần 4+ tầng, nên thiết kế lại thành queue với overflow policy thay vì transfer chuỗi.
  3. Ràng buộc data model theo enumrouting_stage, exit_reason, result, final_destination_type đều là ENUM MySQL, không phải string tự do. Query nhanh hơn (index tốt), báo cáo nhất quán (không có "Timeout Queue" vs "timeout_queue"), ngăn typo khi viết handler mới.
  4. Tận dụng a_leg_events JSON — mọi event nhỏ (DTMF, agent join/leave, transfer request) đều nên push vào audit log. Chi phí rẻ (JSON append trong RAM), giá trị cao khi debug/dispute với khách.
  5. Không dùng CDR làm nguồn cho real-time monitoring — CDR chỉ ghi khi cuộc gọi kết thúc. Muốn xem "hiện có bao nhiêu cuộc đang trong queue" phải dùng luồng event realtime + cache in-memory. CDR là kho phân tích lịch sử, không phải dashboard real-time.

Kết luận — trace cuộc gọi là chi tiết nhỏ tạo khác biệt lớn

Trong 15 năm ngành tổng đài IP, phần lớn hệ thống chỉ ghi lại "khách gọi số A, kéo dài B giây, ai bắt máy". Đó là CDR flat — đủ dùng cho tính cước, không đủ cho vận hành contact center chuyên nghiệp.

Zorio thiết kế mô hình CDR theo hướng stage-aware: mỗi chặng được gắn thẻ ngay khi xảy ra, mỗi B-leg lưu độc lập, hangup cause chuẩn hoá về 7 kết quả cuối, và mỗi cuộc gọi có final_destination rõ ràng. Kết quả:

Với doanh nghiệp đang tìm giải pháp contact center có khả năng truy dấu chi tiết từng cuộc gọi — từ IVR đến agent đến transfer — Zorio là nền tảng được thiết kế từ ngày đầu cho bài toán này, không phải một CDR flat sau đó "cố ép" thêm cột.

Đọc thêm trong loạt bài kỹ thuật

Auto-dialer 4 chế độ (Manual · Preview · Progressive · Predictive) — chọn cho từng chiến dịch Sắp ra
Caller-ID rotation & auto-cooldown khi bị nhà mạng nghi ngờ spam Sắp ra
Kiến trúc multi-tenant: mỗi khách hàng một domain, một cấu hình, một dashboard Sắp ra

Xem báo cáo hành trình cuộc gọi trên dữ liệu thật

Đặt lịch demo để trải nghiệm waterfall report, truy vết multi-hop và toàn bộ nền tảng contact center Zorio.