Skip to content

Chiến dịch (Campaigns)

Nhóm endpoint quản trị chiến dịch outbound: tạo mới, điều khiển vòng đời (launch/pause/resume/archive/clone), thống kê hiệu suất + chuỗi thời gian, và quản lý đội ngũ agent gán vào từng chiến dịch. Vòng đời từng cuộc gọi (initiate / status / hangup / disposition) cũng được mô tả ở cuối trang để tiện đối tác tích hợp.

3.1 Tạo chiến dịch

http
POST /api/telesales/campaigns

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
namestringTên chiến dịch, unique trong tài khoảnmax 150 ký tự
descriptionstringoptionalMô tả ngắnmax 500 ký tự
dialer_modestringChế độ quay sốmanual / preview / progressive / predictive
caller_id_group_idintegerID nhóm xoay vòng caller-ID (xem §Caller-ID rotation groups)Phải tồn tại + status=active
script_idintegeroptionalID kịch bản hội thoại gán cho chiến dịchPhải tồn tại
ring_timeout_secintegeroptionalThời gian đổ chuông tối đa (giây)5-120 (default 25)
preview_window_secintegeroptionalThời gian agent xem trước lead khi dialer_mode=preview0-60 (default 10)
priorityintegeroptionalMức ưu tiên — số càng lớn càng ưu tiên0-1000 (default 100)
max_attemptsintegeroptionalSố lần thử lại tối đa cho 1 lead1-20 (default 5)
pdpl_enforcebooloptionalBật khung giờ pháp lý (PDPL/TCPA)true / false (default true)
abandon_rate_limit_pctfloatoptionalNgưỡng abandon rate tối đa (% — predictive mode)0.0-10.0 (default 3.0)
time_windowobjectoptionalKhung giờ gọi cho phép theo từng ngày trong tuầnKey: mon/tue/wed/thu/fri/sat/sun → mảng {from,to} HH:MM

Giá trị dialer_mode:

  • manual — agent tự bấm số gọi từng cuộc.
  • preview — hệ thống đẩy lead cho agent xem trước rồi mới quay số.
  • progressive — hệ thống tự quay số khi có agent rảnh.
  • predictive — hệ thống quay số trước cả khi agent rảnh (giới hạn tuân thủ qua abandon_rate_limit_pct).

Body:

json
{
  "name": "Outbound Sales Q3-2026",
  "description": "Q3 outbound campaign",
  "dialer_mode": "progressive",
  "caller_id_group_id": 3,
  "script_id": 12,
  "ring_timeout_sec": 25,
  "preview_window_sec": 10,
  "priority": 100,
  "max_attempts": 5,
  "pdpl_enforce": true,
  "abandon_rate_limit_pct": 3.0,
  "time_window": {
    "mon": [{"from": "08:00", "to": "17:30"}],
    "tue": [{"from": "08:00", "to": "17:30"}],
    "wed": [{"from": "08:00", "to": "17:30"}],
    "thu": [{"from": "08:00", "to": "17:30"}],
    "fri": [{"from": "08:00", "to": "17:30"}],
    "sat": [{"from": "08:00", "to": "12:00"}]
  }
}

Response body fields

FieldTypeMô tảGiá trị hợp lệ
idintegerID chiến dịch
namestringTên chiến dịch
descriptionstring|nullMô tả ngắn
statusstringTrạng thái vòng đờidraft / active / paused / archived
dialer_modestringChế độ quay sốmanual / preview / progressive / predictive
caller_id_group_idintegerID nhóm caller-ID rotation
script_idinteger|nullID script
ring_timeout_secintegerThời gian đổ chuông tối đa5-120
preview_window_secintegerCửa sổ preview0-60
priorityintegerMức ưu tiên0-1000
max_attemptsintegerSố lần thử lại tối đa1-20
pdpl_enforceboolBật khung giờ PDPL
abandon_rate_limit_pctfloatNgưỡng abandon rate (predictive)0.0-10.0
time_windowobjectKhung giờ gọi theo ngày
total_leadsintegerSố lead đã import vào chiến dịch
created_byintegerID user tạo chiến dịch
created_atdatetimeThời điểm tạo (ISO 8601 UTC)
updated_atdatetimeThời điểm cập nhật cuối

Response 201:

json
{
  "data": {
    "id": 36,
    "name": "Outbound Sales Q3-2026",
    "description": "Q3 outbound campaign",
    "status": "draft",
    "dialer_mode": "progressive",
    "caller_id_group_id": 3,
    "script_id": 12,
    "ring_timeout_sec": 25,
    "preview_window_sec": 10,
    "priority": 100,
    "max_attempts": 5,
    "pdpl_enforce": true,
    "abandon_rate_limit_pct": 3.0,
    "time_window": { "mon": [{"from":"08:00","to":"17:30"}], "...": "..." },
    "total_leads": 0,
    "created_by": 7,
    "created_at": "2026-06-06T06:00:00Z",
    "updated_at": "2026-06-06T06:00:00Z"
  }
}

Response 422 (validate sai):

json
{
  "message": "The given data was invalid.",
  "errors": {
    "dialer_mode": ["The selected dialer mode is invalid."],
    "caller_id_group_id": ["The selected caller id group id is invalid."]
  }
}

3.2 Điều khiển vòng đời chiến dịch

EndpointHành độngResponse
POST /api/telesales/campaigns/{id}/launchdraft → active{ "data": { "id":36, "status":"active", "launched_at":"...Z" } }
POST /api/telesales/campaigns/{id}/pauseactive → paused{ "data": { "id":36, "status":"paused" } }
POST /api/telesales/campaigns/{id}/resumepaused → active{ "data": { "id":36, "status":"active" } }
POST /api/telesales/campaigns/{id}/archive→ archived{ "data": { "id":36, "status":"archived" } }
POST /api/telesales/campaigns/{id}/clone→ bản draft mới{ "data": { "id":42, "status":"draft", "name":"... (clone)" } }
PUT /api/telesales/campaigns/{id}Cập nhật fieldtrả về toàn bộ object chiến dịch
DELETE /api/telesales/campaigns/{id}Xoá mềm204 No Content

3.3 Danh sách / chi tiết / thống kê

GET /api/telesales/campaigns — danh sách

Query parameters

FieldTypeRequiredMô tảGiá trị hợp lệ
statusstringoptionalLọc theo trạng thái vòng đờidraft / active / paused / archived
dialer_modestringoptionalLọc theo chế độ quay sốmanual / preview / progressive / predictive
qstringoptionalTìm theo name (LIKE %keyword%)
pageintegeroptionalSố trang≥ 1 (default 1)
per_pageintegeroptionalBản ghi mỗi trang1-200 (default 50)

Response fields (mỗi item trong data[])

FieldTypeMô tảGiá trị hợp lệ
idintegerID chiến dịch
namestringTên chiến dịch
statusstringTrạng thái vòng đờidraft / active / paused / archived
dialer_modestringChế độ quay sốmanual / preview / progressive / predictive
total_leadsintegerTổng số lead đã import
dialledintegerSố lần đã quay số
answeredintegerSố cuộc bắt máy
convertedintegerSố cuộc chuyển đổi thành công
asr_pctfloatAnswer-Seizure Ratio (%) = answered/dialled*1000-100

Response 200:

json
{
  "data": [
    { "id": 36, "name": "Outbound Sales Q3-2026", "status": "active",
      "dialer_mode": "progressive", "total_leads": 487, "dialled": 312,
      "answered": 198, "converted": 47, "asr_pct": 63.5 }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 }
}

GET /api/telesales/campaigns/{id}/stats — thống kê tổng hợp

Response body fields

FieldTypeMô tảGiá trị hợp lệ
dialledintegerTổng số lần quay số
answeredintegerTổng số cuộc bắt máy
talked_over_30sintegerSố cuộc có billsec ≥ 30
convertedintegerSố cuộc chuyển đổi (disposition is_final=true + outcome thành công)
answer_rate_pctfloatanswered/dialled*1000-100
conversion_rate_pctfloatconverted/answered*1000-100
asr_pctfloatAnswer-Seizure Ratio (%)0-100
aht_secondsintegerAverage Handle Time (giây) — trung bình thời gian xử lý mỗi cuộc
abandon_rate_pctfloatTỷ lệ abandoned (% — chỉ tính trong predictive mode)0-100
callbacks_pendingintegerSố callback đang chờ thực hiện

Response 200:

json
{
  "data": {
    "dialled": 312,
    "answered": 198,
    "talked_over_30s": 142,
    "converted": 47,
    "answer_rate_pct": 63.46,
    "conversion_rate_pct": 15.06,
    "asr_pct": 63.46,
    "aht_seconds": 184,
    "abandon_rate_pct": 1.8,
    "callbacks_pending": 23
  }
}

GET /api/telesales/campaigns/{id}/timeseries — chuỗi thời gian

Query parameters

FieldTypeRequiredMô tảGiá trị hợp lệ
group_bystringoptionalĐơn vị grouphour / day / week (default day)
date_fromdatetimeoptionalMốc bắt đầu (UTC ISO 8601)
date_todatetimeoptionalMốc kết thúc (UTC ISO 8601)

Response fields (mỗi item trong data[])

FieldTypeMô tảGiá trị hợp lệ
bucketstringMốc thời gian — định dạng theo group_byYYYY-MM-DD / YYYY-Www / YYYY-MM-DD HH:00:00
dialledintegerSố lần quay số trong bucket
answeredintegerSố cuộc bắt máy trong bucket
convertedintegerSố cuộc chuyển đổi trong bucket

Response 200:

json
{
  "data": [
    { "bucket": "2026-06-01", "dialled": 80, "answered": 51, "converted": 12 },
    { "bucket": "2026-06-02", "dialled": 92, "answered": 60, "converted": 15 },
    { "bucket": "2026-06-03", "dialled": 75, "answered": 47, "converted":  9 }
  ],
  "meta": { "range": "7d" }
}

3.4 Quản lý agent của chiến dịch

EndpointMục đích
GET /api/telesales/campaigns/{id}/agentsDanh sách agent đang gán vào chiến dịch
GET /api/telesales/campaigns/{id}/agents/availableDanh sách user đủ điều kiện gán (chưa thuộc chiến dịch khác xung đột, có role phù hợp)
POST /api/telesales/campaigns/{id}/agentsGán một agent vào chiến dịch
PATCH /api/telesales/campaigns/{id}/agents/{userId}Cập nhật role hoặc trạng thái của agent
DELETE /api/telesales/campaigns/{id}/agents/{userId}Gỡ agent khỏi chiến dịch

GET /api/telesales/agents — danh sách agent của khách hàng

Đối tác tích hợp dùng để chọn user_id cho POST /api/telesales/campaigns/{id}/agents:

http
GET /api/telesales/agents?per_page=50&page=1

Query parameters

FieldTypeRequiredMô tảGiá trị hợp lệ
qstringoptionalTìm theo name / username / email (LIKE %keyword%)
rolestringoptionalLọc theo roleagent / supervisor / manager / ...
team_idintegeroptionalLọc theo team
pageintegeroptionalSố trang≥ 1 (default 1)
per_pageintegeroptionalBản ghi mỗi trang1-200 (default 50)

Response fields (mỗi item trong data[])

FieldTypeMô tảGiá trị hợp lệ
idintegerUser ID
namestringHọ tên
usernamestringTên đăng nhập
emailstringEmail
rolestringVai trò trong hệ thốngagent / supervisor / manager
team_idinteger|nullID team đang thuộc
extensionstring|nullSố máy nhánh SIP đã gán
is_activeboolUser còn hoạt động khôngtrue / false

Response 200:

json
{
  "data": [
    {
      "id": 25,
      "name": "Nguyen Van A",
      "username": "nguyenvana",
      "email": "nguyenvana@company.com",
      "role": "agent",
      "team_id": 3,
      "extension": "1001",
      "is_active": true
    }
  ],
  "meta": { "current_page": 1, "last_page": 3, "per_page": 50, "total": 142 }
}

Chỉ trả về user có is_active=true, sắp xếp theo name.

Workflow gán agent vào chiến dịch:

http
# Bước 1: lấy danh sách agent có sẵn
GET /api/telesales/agents?role=agent

# Bước 2: gán user_id=25 vào chiến dịch
POST /api/telesales/campaigns/36/agents
{ "user_id": 25, "role": "agent" }

GET /api/telesales/campaigns/{id}/agents

Response fields (mỗi item trong data[])

FieldTypeMô tảGiá trị hợp lệ
user_idintegerID user agent
usernamestringTên đăng nhập
display_namestringTên hiển thị
campaign_idintegerID chiến dịch
rolestringVai trò trong chiến dịchagent / supervisor
extensionstring|nullSố máy nhánh SIP
team_idinteger|nullID team
team_namestring|nullTên team
is_activeboolUser còn hoạt động khôngtrue / false
assigned_atdatetimeThời điểm gán vào chiến dịch (ISO 8601 UTC)

Response 200:

json
{
  "data": [
    {
      "user_id": 25,
      "username": "agent01",
      "display_name": "Nguyen Van A",
      "campaign_id": 36,
      "role": "agent",
      "extension": "2001",
      "team_id": 3,
      "team_name": "Sales A",
      "is_active": true,
      "assigned_at": "2026-06-06T06:10:00Z"
    }
  ]
}

GET /api/telesales/campaigns/{id}/agents/available

Response fields (mỗi item trong data[])

FieldTypeMô tảGiá trị hợp lệ
user_idintegerID user agent có thể gán
usernamestringTên đăng nhập
display_namestringTên hiển thị
extensionstring|nullSố máy nhánh SIP
team_idinteger|nullID team
team_namestring|nullTên team
rolestringVai trò hệ thốngagent / supervisor
is_activeboolUser còn hoạt động khôngtrue / false

Response 200:

json
{
  "data": [
    {
      "user_id": 30,
      "username": "agent06",
      "display_name": "Tran Thi B",
      "extension": "2006",
      "team_id": 3,
      "team_name": "Sales A",
      "role": "agent",
      "is_active": true
    }
  ]
}

POST /api/telesales/campaigns/{id}/agents — gán agent

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
user_idintegerID user cần gánPhải tồn tại + is_active=true + có extension SIP
rolestringoptionalVai trò trong chiến dịchagent (default) / supervisor

Body:

json
{ "user_id": 25, "role": "agent" }

Response body fields

FieldTypeMô tảGiá trị hợp lệ
user_idintegerID user vừa gán
campaign_idintegerID chiến dịch
rolestringVai trò trong chiến dịchagent / supervisor
assigned_atdatetimeThời điểm gán (ISO 8601 UTC)

Response 201:

json
{
  "data": {
    "user_id": 25,
    "campaign_id": 36,
    "role": "agent",
    "assigned_at": "2026-06-06T06:10:00Z"
  }
}

Response 422 — các trường hợp validate / xung đột:

json
// User không tồn tại hoặc đã ngừng hoạt động
{
  "message": "The given data was invalid.",
  "errors": { "user_id": ["The selected user id is invalid."] }
}

// User đã được gán vào chiến dịch khác (mỗi user chỉ thuộc 1 chiến dịch tại 1 thời điểm)
{
  "message": "Agent is already attached to another active campaign.",
  "errors": { "user_id": ["User 25 already assigned to campaign 38."] }
}

// User chưa được cấp extension SIP (không thể nhận cuộc gọi)
{
  "message": "User has no SIP extension assigned — cannot be assigned as an agent.",
  "errors": { "user_id": ["User 25 has no extension assigned."] }
}

// Role không hợp lệ (chỉ chấp nhận agent / supervisor)
{
  "message": "The given data was invalid.",
  "errors": { "role": ["The selected role is invalid."] }
}

PATCH /api/telesales/campaigns/{id}/agents/{userId} — cập nhật role

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
rolestringVai trò mới trong chiến dịchagent / supervisor

Body:

json
{ "role": "supervisor" }

Response 200:

json
{ "data": { "user_id": 25, "campaign_id": 36, "role": "supervisor", "assigned_at": "2026-06-06T06:10:00Z" } }

Response 404 — agent chưa được gán vào chiến dịch này:

json
{ "message": "Agent is not attached to this campaign." }

DELETE /api/telesales/campaigns/{id}/agents/{userId}

Response 204 No Content (thành công).

Response 422 — agent đang ở trên cuộc gọi:

json
{
  "message": "Cannot unassign an agent who is currently on a call.",
  "errors": { "user_id": ["Agent 25 is currently on call UUID xxx — wait for it to end or force-hangup first."] }
}

Lưu ý

Sau khi DELETE thành công, agent sẽ không nhận thêm cuộc gọi mới từ chiến dịch, nhưng cuộc gọi đang quay sẽ chạy đến lúc kết thúc. Trạng thái presence realtime tự đồng bộ trong vòng 5 giây.


7. Vòng đời cuộc gọi (Call Lifecycle)

Một cuộc gọi đi qua 4 bước chính do agent (hoặc hệ thống) khởi tạo trong phạm vi chiến dịch.

Mẹo

Nếu đối tác dùng Webphone SDK, toàn bộ 4 bước được đóng gói trong phone.call(number, options) — không cần gọi trực tiếp các endpoint dưới. Xem tài liệu Webphone SDK.

7.1 Khởi tạo cuộc gọi

http
POST /api/telesales/calls/initiate

Extension của agent được lấy từ profile của user đã xác thực — không cần truyền tay.

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
campaign_idintegerID chiến dịch agent đang chạyPhải tồn tại + status=active + agent đã được gán
lead_idintegerID lead cần quay sốPhải thuộc campaign_id + status không phải dnc/exhausted/converted

Body:

json
{
  "campaign_id": 36,
  "lead_id": 78912
}

Response body fields

FieldTypeMô tảGiá trị hợp lệ
uuidstringUUID cuộc gọi — dùng cho mọi endpoint con (/status, /hangup, /disposition)UUID v4
statusstringTrạng thái khởi tạooriginating
caller_id_usedstringSố gọi ra (caller-ID) được nhóm rotation chọnE.164
lead_idintegerID lead
campaign_idintegerID chiến dịch

Response 200:

json
{
  "uuid": "093e1024-82c6-49b3-8775-99edeb221898",
  "status": "originating",
  "caller_id_used": "0900000010",
  "lead_id": 78912,
  "campaign_id": 36
}

Response 422 (vi phạm PDPL window):

json
{
  "error": "pdpl_blocked",
  "message": "Outside the campaign calling window (08:00-17:30, Mon-Sat).",
  "next_eligible_at": "2026-06-07T01:00:00Z"
}

Response 422 (các lý do khác):

json
{
  "error": "lead_dnc",
  "message": "Lead phone is on the DNC list."
}

Các mã lỗi khác: lead_blocked, no_caller_id_available, max_attempts_reached.

7.2 Theo dõi trạng thái cuộc gọi

http
GET /api/telesales/calls/{uuid}/status

Response body fields

FieldTypeMô tảGiá trị hợp lệ
uuidstringUUID cuộc gọiUUID v4
statestringTrạng thái hiện tạioriginating / ringing / answered / wrap_up / closed
start_timedatetimeThời điểm khởi tạo (ISO 8601 UTC)
answer_timedatetime|nullThời điểm khách bắt máynull nếu chưa bắt máy
end_timedatetime|nullThời điểm kết thúcnull nếu chưa kết thúc
durationinteger|nullTổng thời gian từ originate đến hangup (giây)
billsecinteger|nullThời gian đàm thoại (giây) — từ answer_time đến end_time
resultstring|nullKết quả cuối — mapping từ hangup causeanswered / no_answer / busy / failed / cancelled
hangup_causestring|nullLý do hangup từ Zorio PBXvd NORMAL_CLEARING / USER_BUSY
caller_id_usedstringSố gọi raE.164
agent_extensionstringSố máy nhánh của agent
destination_numberstringSố điện thoại đích (lead)

7.3 Cúp máy thủ công

http
POST /api/telesales/calls/{uuid}/hangup

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
causestringoptionalLý do hangup tự đặt — lưu vào CDR để báo cáofree text max 64 ký tự (default MANUAL_HANGUP)

Body (tuỳ chọn):

json
{ "cause": "MANUAL_HANGUP" }

Response body fields

FieldTypeMô tảGiá trị hợp lệ
uuidstringUUID cuộc gọiUUID v4
statestringTrạng thái sau hangupwrap_up (sang chờ agent disposition)
end_timedatetimeThời điểm hangup (ISO 8601 UTC)
durationintegerTổng thời gian cuộc gọi (giây)
billsecintegerThời gian đàm thoại (giây)
hangup_causestringLý do hangup (echo từ request hoặc tự sinh)

Response 200:

json
{
  "data": {
    "uuid": "093e1024-82c6-49b3-8775-99edeb221898",
    "state": "wrap_up",
    "end_time": "2026-06-06T06:35:42Z",
    "duration": 222,
    "billsec": 213,
    "hangup_cause": "MANUAL_HANGUP"
  }
}

Response 404 — UUID không tồn tại hoặc không thuộc tài khoản:

json
{ "message": "Call not found." }

Response 422 — cuộc gọi đã kết thúc:

json
{
  "message": "Call has already ended, hangup is not possible.",
  "errors": { "uuid": ["Call already in state 'closed' since 2026-06-06T06:34:00Z."] }
}

Response 403 — agent thường cố cúp máy cuộc gọi của người khác mà chưa có quyền supervisor:

json
{ "message": "You are not allowed to hang up this call. Supervisor role is required to force hangup." }

7.4 Gửi kết quả cuộc gọi (Disposition)

http
POST /api/telesales/calls/{uuid}/disposition

Request body fields

FieldTypeRequiredMô tảGiá trị hợp lệ
disposition_idintegerID disposition (map từ code, vd SALE-OK)Phải tồn tại + is_active=true
notestringoptionalGhi chú text của agentmax 1000 ký tự
callback_atdatetimeoptionalLịch gọi lại (ISO 8601 UTC) — chỉ dùng khi disposition có trigger_callback=truePhải lớn hơn thời điểm hiện tại
callback_agent_idintegeroptionalSticky agent cho cuộc callbackUser ID phải là agent của chiến dịch

Body:

json
{
  "disposition_id": 5,
  "note": "Customer committed, follow-up email scheduled",
  "callback_at": null,
  "callback_agent_id": null
}

Response body fields

FieldTypeMô tảGiá trị hợp lệ
uuidstringUUID cuộc gọiUUID v4
statestringTrạng thái sau khi gửi dispositionclosed
dispositionobjectChi tiết disposition vừa áp{id, code, label, category}
disposition.idintegerID disposition
disposition.codestringCode immutable (vd SALE-OK)
disposition.labelstringNhãn hiển thị
disposition.categorystringPhân loạicontact / no_contact / callback / remove
lead_idintegerID lead
lead_status_afterstringTrạng thái lead sau khi áp dispositionpending / contacted / converted / dnc / exhausted
callback_scheduledobject|nullThông tin callback (nếu có){callback_at, agent_id} hoặc null

Response 200:

json
{
  "data": {
    "uuid": "093e1024-82c6-49b3-8775-99edeb221898",
    "state": "closed",
    "disposition": { "id": 5, "code": "SALE-OK", "label": "Sale Success", "category": "contact" },
    "lead_id": 78912,
    "lead_status_after": "converted",
    "callback_scheduled": null
  }
}

Các trường outcome theo ngành nghề

Các trường outcome chuyên biệt (Payment Method, Appointment Date, Survey Result, Lead Score, Notes, ...) được khai báo qua custom field ở từng disposition do admin của khách hàng cấu hình. Schema chi tiết cho từng ngành (chăm sóc khách hàng, lịch hẹn dịch vụ, khảo sát mức độ hài lòng, ...) nằm trong gói cấu hình riêng riêng cho khách hàng.

Cấp phép theo điều khoản sử dụng của Zorio.