Inbound calls and callbacks
Every number you call from takes calls back. When a lead calls one of your numbers, the call rings the agent who last spoke with them, then your agent pool, then voicemail.
How a callback is routed
We match the caller's number to a lead in the account. When it matches, the call is routed in this order:
- The agent who last spoke with the lead, if they are connected and free. Their console shows the lead and the last call.
- The next free agent in the agent pool, if the last agent is busy, offline or does not answer within
ring_agent_seconds. - Hold, for up to 2 minutes, while every agent in the pool is busy. The first agent to come free takes the call.
- Voicemail, if no agent is free by then or no agent is connected.
A caller that matches no lead goes straight to the pool. Inbound calls ring at any hour. Calling hours apply only to calls you place.
Routing settings
Routing is set on the caller ID pool and applies to every number in it. Set the same fields on one number with PATCH /v1/numbers/{id} to override the pool.
PATCH/v1/caller_id_pools/{id}
| Parameters | Type | Description |
|---|---|---|
inbound.agent_pool_id | string | The agent pool that takes callbacks when the lead's last agent is busy or offline, and calls from numbers that match no lead. |
inbound.ring_agent_seconds | integer | How long the last agent rings before the pool is tried. 5 to 30, default 15. |
inbound.max_hold_seconds | integer | How long a caller waits on hold for a free agent in the pool. 0 to 120, default 120. |
inbound.hold_audio_id | string | Your hold music or message. A standard one is used if you leave it out. |
inbound.voicemail_greeting_id | string | Your voicemail greeting. It must name your business. |
curl -X PATCH https://api.liveleg.dev/v1/caller_id_pools/pool_local_presence \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbound": {
"agent_pool_id": "pool_closers",
"ring_agent_seconds": 15,
"max_hold_seconds": 120,
"voicemail_greeting_id": "ann_ridgeline_voicemail"
}
}'const res = await fetch('https://api.liveleg.dev/v1/caller_id_pools/pool_local_presence', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inbound: {
agent_pool_id: "pool_closers",
ring_agent_seconds: 15,
max_hold_seconds: 120,
voicemail_greeting_id: "ann_ridgeline_voicemail"
}
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/caller_id_pools/pool_local_presence",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"inbound": {
"agent_pool_id": "pool_closers",
"ring_agent_seconds": 15,
"max_hold_seconds": 120,
"voicemail_greeting_id": "ann_ridgeline_voicemail",
},
},
).json() Voicemail
The caller hears your greeting and can leave a message up to 3 minutes long. The message is saved as a recording on the inbound call and recording.ready arrives with its URL. A callback is scheduled for the lead's last agent at the next time inside the lead's calling hours, and callback.scheduled arrives with source voicemail.
Opt-out menu
Callers who are put on hold or sent to voicemail first hear one line with your business name: "To stop calls from Ridgeline Roofing, press 9." Pressing 9 adds the number to your do-not-call list, plays a short confirmation and ends the call. dnc.added arrives with source keypress. When a caller asks an agent not to be called again, the number is added from the call the same way.
Resting and retired numbers
- Resting numbers and numbers in spam cleaning take callbacks as usual.
- Retired numbers keep taking callbacks for 90 days. The call is routed to the lead's last agent the same way.
number_stateoninbound.routedshows the state of the number that was called.
Scheduled callbacks
A disposition with callback_at schedules a callback to the lead. At that time the lead is called back by the same agent. If that agent is busy or offline, the call goes to the agent pool. A time outside the lead's calling hours moves to the next open window. In a dial session, due callbacks are dialed before new leads. The usual pre-dial checks run before each callback.
POST/v1/calls/{id}/disposition
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "callback_requested",
"notes": "Owner on a job. Call Thursday after 2 pm.",
"callback_at": "2026-11-06T14:00:00-06:00"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: "callback_requested",
notes: "Owner on a job. Call Thursday after 2 pm.",
callback_at: "2026-11-06T14:00:00-06:00"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"code": "callback_requested",
"notes": "Owner on a job. Call Thursday after 2 pm.",
"callback_at": "2026-11-06T14:00:00-06:00",
},
).json() List scheduled callbacks with GET /v1/callbacks, filtered by agent, lead or status. Cancel one with DELETE /v1/callbacks/{id}.
curl "https://api.liveleg.dev/v1/callbacks?agent_id=agt_114&status=scheduled" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/callbacks?agent_id=agt_114&status=scheduled', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/callbacks",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"agent_id": "agt_114",
"status": "scheduled",
},
).json() {
"object": "list",
"data": [
{
"id": "cbk_2Rt81Xn",
"object": "callback",
"lead_id": "led_44Hc2",
"agent_id": "agt_114",
"callback_at": "2026-11-06T20:00:00Z",
"source": "disposition",
"call_id": "cal_8Jq2mT4x",
"status": "scheduled"
}
],
"has_more": false
} Events
inbound.routed arrives when a call to one of your numbers is routed. routed_to is an agent ID, or voicemail. reason is one of these:
| reason | Meaning |
|---|---|
last_agent_spoke_to_lead | The agent who last spoke with the lead. |
agent_pool | The next free agent in the pool, after the last agent was busy, offline or did not answer. |
no_lead_match | The caller matched no lead and went to the pool. |
voicemail_no_agent | No agent was free within the hold time, or no agent was connected. |
opted_out | The caller pressed 9 to stop calls. |
{
"id": "evt_7Hn20Qa1",
"type": "inbound.routed",
"created_at": "2026-11-03T19:12:44.020Z",
"livemode": true,
"data": {
"call_id": "cal_In44Wd2",
"from": "+15125550147",
"to": "+15125550199",
"number_state": "active",
"lead_id": "led_44Hc2",
"routed_to": "agt_114",
"reason": "last_agent_spoke_to_lead",
"hold_seconds": 0
}
} callback.scheduled arrives when a callback is set, from a disposition or a voicemail.
{
"id": "evt_7Hn27Lp4",
"type": "callback.scheduled",
"created_at": "2026-11-03T21:48:10.331Z",
"livemode": true,
"data": {
"callback_id": "cbk_9Wm30Ft",
"lead_id": "led_44Hc2",
"agent_id": "agt_114",
"callback_at": "2026-11-04T14:00:00Z",
"source": "voicemail",
"call_id": "cal_In51Qb8"
}
} Prices
Inbound calls are billed per minute in 6-second increments from the moment the call is answered, including hold and voicemail time. A call to a local number is $0.06 / min. A call to a toll-free number is $0.08 / min. The owner of a toll-free number pays for each call to it. Routing, the agent phone line and Smart Recording are included in both.
| Inbound call to a local numberRouting to the right agent, agent phone line and Smart Recording included. | $0.06/ min |
| Inbound call to a toll-free numberRouting to the right agent, agent phone line and Smart Recording included. | $0.08/ min |