Webhooks
Receive real-time call data when a call ends.
Webhooks
Tabbly sends a real-time webhook to your endpoint whenever a call concludes — both answered and not answered. The payload includes transcript, summary, sentiment, QA pairs, recording URL, and cost details so you can sync call data into your CRM or backend without polling Get Call Logs.
Setup
- Log in to the Tabbly dashboard.
- Open Developer API from the sidebar.
- Scroll to the Webhook URL field at the bottom of the page.
- Paste your publicly reachable HTTPS endpoint (for example
https://your-app.com/webhooks/tabbly). - Click Update to store the URL for your organization.
After updating, Tabbly will POST JSON to that URL as soon as each call ends. Leave the field empty to disable webhooks.
Endpoint requirements
- Accept HTTP POST with
Content-Type: application/json. - Return HTTP 2xx quickly to acknowledge receipt (ideally within a few seconds).
- Tabbly does not retry failed deliveries — process or queue the payload before responding if your work is slow.
Delivery
POST https://your-webhook-url.comTabbly calls the URL you configured in Developer API. There is no Tabbly-hosted webhook path to call yourself.
Headers
| Header | Value |
|---|---|
Content-Type |
application/json |
Example request
What Tabbly sends to your endpoint after a completed answered call:
POST /webhooks/tabbly HTTP/1.1
Host: your-app.com
Content-Type: application/json{
"participant_identity": "user-a0dfacddba6e58ff0ccc223239f919",
"use_agent_id": "854",
"called_to": "Web Call",
"called_time": "2025-05-30 05:37:09",
"custom_identifiers": "",
"call_status": "Call Answered",
"call_duration": "137",
"start_time": "2025-05-30 05:37:12",
"end_time": "2025-05-30 05:39:29",
"call_transcript": "[0 - 6] Agent: Hello, this is rohan from tabbly.\n[6 - 13] User: Yes?\n...",
"call_summary": "The call was a screening interview for a PHP Developer role. The candidate expressed positive interest and provided solid answers about their experience with PHP and willingness to work on-site.",
"call_sentiment": 8,
"call_json_output": "{\"summary\":\"The call was a screening interview for a PHP Developer role.\",\"sentiment\":8,\"timestamps\":{\"questions\":{\"1\":[\"0:26\",\"0:48\"]},\"answers\":{\"1\":[\"0:48\",\"0:55\"]},\"qa_pairs\":[{\"question_1\":\"Do you have experience working with PHP in a professional setting?\",\"answer_1\":\"Yes, I worked for 5 years. In PHD.\"}],\"output_status\":\"Review\"}",
"call_recording_url": null,
"total_call_cost": "0.1783",
"telco_pricing": "0.0092",
"agent_cost": 0.1507,
"organization_id": "234",
"call_direction": "outbound"
}Not answered calls
Not answered calls use the same field set. Transcript, summary, sentiment, JSON output, and recording are typically null, and call_duration is 0:
{
"participant_identity": "camp-2904-4199969-c31493bea9",
"use_agent_id": "6355",
"called_to": "+912269858803",
"called_time": "2026-07-09 03:41:11",
"custom_identifiers": "",
"campaign_id": "2904",
"call_status": "Not Answered",
"call_duration": 0,
"start_time": "2026-07-09 03:41:11",
"end_time": "2026-07-09 03:41:41",
"call_transcript": null,
"call_summary": null,
"call_sentiment": null,
"call_json_output": null,
"call_recording_url": null,
"total_call_cost": 0,
"telco_pricing": "0.0092",
"agent_cost": 0,
"organization_id": "3282",
"call_direction": "outbound"
}Payload fields
| Field | Type | Description |
|---|---|---|
participant_identity |
string | Unique call / participant ID |
use_agent_id |
string | Voice agent ID that handled the call |
called_to |
string | Destination number, or Web Call for browser calls |
called_time |
string | When the call was placed (YYYY-MM-DD HH:MM:SS) |
custom_identifiers |
string | Optional contact tag from Add Contacts; may be empty |
campaign_id |
string | Campaign ID when the call belongs to a campaign; may be empty |
call_status |
string | e.g. Call Answered, Not Answered |
call_duration |
string | number | Duration in seconds |
start_time |
string | Call start timestamp |
end_time |
string | Call end timestamp |
call_transcript |
string | null | Full timed conversation transcript |
call_summary |
string | null | AI-generated summary of the call |
call_sentiment |
integer | null | Sentiment score from 1–10 |
call_json_output |
string | null | JSON string with structured data (summary, sentiment, QA pairs, timestamps, output_status) |
call_recording_url |
string | null | URL to the call recording when available |
total_call_cost |
string | number | Total cost of the call |
telco_pricing |
string | Telco / carrier cost component |
agent_cost |
number | Agent / AI cost component |
organization_id |
string | Your organization ID |
call_direction |
string | outbound or inbound |
call_json_output (parsed)
When present, parse the string as JSON. Typical shape:
| Field | Type | Description |
|---|---|---|
summary |
string | Call summary |
sentiment |
integer | Sentiment score (1–10) |
timestamps |
object | Question/answer time ranges |
qa_pairs |
array | Extracted question and answer pairs |
output_status |
string | Processing status (e.g. Review) |
Expected response
Return any 2xx status as soon as you have accepted the payload:
HTTP/1.1 200 OK
Content-Type: application/json
{"ok": true}The response body is ignored. Non-2xx responses are treated as delivery failures and are not retried.
Notes
- Webhooks fire for both answered and not answered calls after the call concludes.
- Configure one webhook URL per organization in Developer API; it applies to all agents and campaigns under that org.
- Prefer queueing heavy work (CRM sync, analytics) after returning 200 so Tabbly’s request does not time out.
- For historical or filtered retrieval, use Get Call Logs in addition to webhooks.