Callback Request
One time. Any matching skill. Nobody dials it early.
A callback is a time-gated pool lead. CRM sends callback_scheduled_at. When due, any idle agent whose skills include form_type and whose priority_ids allow the lead’s priority_id (empty = all) may claim it. Master has no agent_id pin. Field shapes are in the API reference.
End-to-end flow
The row sits as Callback until the time is due. Claim starts when any matching-skill agent is panel-active, ready, and AMI idle.
- POST callback_scheduled_at on Callback Schedule, or send-callback-request on an existing row
- Store Callback (skill pool, no employee pin)
- Wait until the time is due (and inside working hours when
outbound_obey_work_hours = 1) - Any idle agent with matching form_type claims it
- Progressive: AMI to that desk. Predictive: customer first, then skill bridge
Push
callback_scheduled_at is only for priority_id 1 (Callback Schedule) on POST. The other priorities are 2 Fresh Assign and 3 Final Disposal (set by the dialer, rejected on push). Extra keys such as agent_id are rejected. An existing row of any priority can get a time with POST /api/send-callback-request.
| Body | Result |
|---|---|
| Time on priority 1 (Callback Schedule) | Stored. dial_status = Callback. |
| Time on any other priority | 400 callback_scheduled_at is only allowed for callback priorities |
agent_id present | 400 extra field rejected |
Accepted times: 2026-08-20 15:30:00 or ISO-8601, including past datetimes (due immediately). Rejected: date-only. Other priority_id values cannot send a time on this POST.
curl -sS -X POST https://YOUR_HOST/api/leads \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"oli_id": "OLI124",
"phone_number": "9876543211",
"client_name": "B",
"form_type": "GST",
"priority_id": 3,
"callback_scheduled_at": "2026-08-20 15:30:00"
}'
Wait until due
Both engines skip a lead while callback_scheduled_at is in the future. Until then the row stays unreserved. When the time is null or <= NOW(), it can be claimed as soon as a matching agent is ready. Only when sales_dialer_config.outbound_obey_work_hours = 1 is it also held to work_start–work_end. After a later Not Connected, the pattern-bucket scheduler resumes.
Any matching skill
Claim requires
form_type in that agent’s skills and priority_id in their allowlist (empty = all).Master has no
agent_id. Any matching idle agent may take it.Panel active, presence ready, AMI idle, wrap finished. If nobody matching is free, the lead waits.
Progressive vs Predictive
| Progressive | Predictive | |
|---|---|---|
| When claimed | Next in that agent’s skill claimNextLead queue | When any matching agent is idle, via claimNextForPool |
| Overdial pool | Skill-matched like other leads | In the pool when due. Bridged to a free skilled agent |
| Originate | AMI to the claiming extension | Customer first, then Bridge to a skilled extension |
| If agent busy after answer | N/A (agent-first) | Hold until a skilled agent is free, or Abandoned if the customer hangs up |
Hangup and retry
| Outcome | Status | Next |
|---|---|---|
Talked (ANSWER) | Received | Never |
| No answer / busy / congestion | Not Connected | retry_pattern + retry_seconds |
call_count reaches max_tries | Disposed | Final Disposal until redial, a new CRM push, or a CRM pending callback |
| Reject / customer cancel | Rejected | Never |
| Customer left while waiting | Abandoned | retry_seconds (or keep existing next_retry_at) |
Failed originate restores the previous status and clears reserve without incrementing call_count or touching first_attempt_at / last_attempt_at (those are stamped only at a real dial), and retries after RELEASE_RETRY_MS (default 60s). Received / Rejected / Disposed are not claimed by the engines; socket redial can reopen them (call_type = redial). Not Connected uses sales_dialer_config.retry_pattern (currently [4,4,3,2,1,1,1,1,1,1,1]), same-day retry_seconds (3600), window work_start–work_end, until max_tries (20). Leftover in the current bucket carries to the next working day. Abandoned is not on that cycle — it keeps a future next_retry_at, otherwise waits retry_seconds. A due callback_scheduled_at waits for working hours only when outbound_obey_work_hours = 1. Redial NC does not bump call_count. Claim order: due Callback, then Pending, then Abandoned, then Not Connected.
Pending callbacks from CRM
An agent usually schedules the callback while still on the call. send-callback-request returns 409 lead_in_flight then, and the hangup settle would overwrite the status anyway. POST /api/pending-callbacks accepts it at any time and holds it until the call is settled.
- The CRM agent page saves the callback in the CRM (
scheduleCallBack_new), then posts straight to the dialer (BearerWS_SECRET). - The dialer inserts a
pendingrow insales_dialer_pending_callbacks. Older pending rows for the sameoli_idbecomesuperseded(latest wins). - Lead not on a live call → applied now (
200,status: applied). Otherwise202,status: pending. settleLead(every engine and inbound) applies the pending row right after the hangup settle.- The 60s stale sweep retries pending rows older than 30s whose lead is not active. Rows whose
oli_idno longer exists becomefailed.
Applying sets dial_status = Callback, callback_scheduled_at, priority_id = 1 (Callback Schedule), clears next_retry_at / call_count / attempt timestamps / last_dial_result / hangup_by / hangup_cause / disposeRemark / reserve, and rotates data_id. It never touches a Calling + reserved row. From there the normal pool flow above applies.
Final disposition from CRM
POST /api/pending-dispositions uses the same table (action = dispose) and the same latest-wins / apply-after-settle flow, so a callback followed by a disposition (or the reverse) ends with whichever the agent submitted last. The CRM page calls it after ChangeWorkStatus succeeds (Not Intrested, Invalid Lead, Language Barrier, DND). Applying sets dial_status = Disposed, disposeRemark = CRM: <reason>, priority_id = disposed_priority_id (Final Disposal), clears callback_scheduled_at / next_retry_at / reserve, and rotates data_id.
Clears missed activities
Storing either a pending callback or a pending disposition clears every open sales_dialer_missed_activities row for that OLI (any agent), with cleared_by = callback | disposition. A blocked agent gets outbound back straight away. See Missed activities.