curl --request POST \
--url https://ingest.zelto.ai/webhooks/calls \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"call": {
"externalId": "<string>",
"startedAt": "2023-11-07T05:31:56Z",
"endedAt": "2023-11-07T05:31:56Z",
"durationSeconds": 1,
"endedReason": "<string>",
"cost": 1,
"recordingUrl": "<string>",
"recordingUploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customer": {
"number": "<string>",
"name": "<string>"
}
},
"agent": {
"externalId": "<string>",
"name": "<string>"
},
"agentId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"version": "<string>",
"companyExternalId": "<string>",
"systemPrompt": "<string>",
"metadata": {},
"versionConfig": {}
}
'import requests
url = "https://ingest.zelto.ai/webhooks/calls"
payload = {
"call": {
"externalId": "<string>",
"startedAt": "2023-11-07T05:31:56Z",
"endedAt": "2023-11-07T05:31:56Z",
"durationSeconds": 1,
"endedReason": "<string>",
"cost": 1,
"recordingUrl": "<string>",
"recordingUploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customer": {
"number": "<string>",
"name": "<string>"
}
},
"agent": {
"externalId": "<string>",
"name": "<string>"
},
"agentId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"version": "<string>",
"companyExternalId": "<string>",
"systemPrompt": "<string>",
"metadata": {},
"versionConfig": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
call: {
externalId: '<string>',
startedAt: '2023-11-07T05:31:56Z',
endedAt: '2023-11-07T05:31:56Z',
durationSeconds: 1,
endedReason: '<string>',
cost: 1,
recordingUrl: '<string>',
recordingUploadId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
customer: {number: '<string>', name: '<string>'}
},
agent: {externalId: '<string>', name: '<string>'},
agentId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
version: '<string>',
companyExternalId: '<string>',
systemPrompt: '<string>',
metadata: {},
versionConfig: {}
})
};
fetch('https://ingest.zelto.ai/webhooks/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://ingest.zelto.ai/webhooks/calls",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'call' => [
'externalId' => '<string>',
'startedAt' => '2023-11-07T05:31:56Z',
'endedAt' => '2023-11-07T05:31:56Z',
'durationSeconds' => 1,
'endedReason' => '<string>',
'cost' => 1,
'recordingUrl' => '<string>',
'recordingUploadId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'customer' => [
'number' => '<string>',
'name' => '<string>'
]
],
'agent' => [
'externalId' => '<string>',
'name' => '<string>'
],
'agentId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'version' => '<string>',
'companyExternalId' => '<string>',
'systemPrompt' => '<string>',
'metadata' => [
],
'versionConfig' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://ingest.zelto.ai/webhooks/calls"
payload := strings.NewReader("{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://ingest.zelto.ai/webhooks/calls")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://ingest.zelto.ai/webhooks/calls")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}"
response = http.request(request)
puts response.read_body{
"received": true
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}Upload a call
Provider-agnostic call ingestion. POST a call in Zelto’s canonical shape (the default), or a native Vapi/Retell webhook payload selected with the X-Zelto-Provider header. The delivery is acknowledged immediately and processed asynchronously into a conversation + transcript. Deduplicated by call id: re-uploading the same call updates it in place and never double-charges. Non-terminal native events (e.g. Vapi status-update) are accepted and ignored.
curl --request POST \
--url https://ingest.zelto.ai/webhooks/calls \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"call": {
"externalId": "<string>",
"startedAt": "2023-11-07T05:31:56Z",
"endedAt": "2023-11-07T05:31:56Z",
"durationSeconds": 1,
"endedReason": "<string>",
"cost": 1,
"recordingUrl": "<string>",
"recordingUploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customer": {
"number": "<string>",
"name": "<string>"
}
},
"agent": {
"externalId": "<string>",
"name": "<string>"
},
"agentId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"version": "<string>",
"companyExternalId": "<string>",
"systemPrompt": "<string>",
"metadata": {},
"versionConfig": {}
}
'import requests
url = "https://ingest.zelto.ai/webhooks/calls"
payload = {
"call": {
"externalId": "<string>",
"startedAt": "2023-11-07T05:31:56Z",
"endedAt": "2023-11-07T05:31:56Z",
"durationSeconds": 1,
"endedReason": "<string>",
"cost": 1,
"recordingUrl": "<string>",
"recordingUploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customer": {
"number": "<string>",
"name": "<string>"
}
},
"agent": {
"externalId": "<string>",
"name": "<string>"
},
"agentId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"version": "<string>",
"companyExternalId": "<string>",
"systemPrompt": "<string>",
"metadata": {},
"versionConfig": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
call: {
externalId: '<string>',
startedAt: '2023-11-07T05:31:56Z',
endedAt: '2023-11-07T05:31:56Z',
durationSeconds: 1,
endedReason: '<string>',
cost: 1,
recordingUrl: '<string>',
recordingUploadId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
customer: {number: '<string>', name: '<string>'}
},
agent: {externalId: '<string>', name: '<string>'},
agentId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
version: '<string>',
companyExternalId: '<string>',
systemPrompt: '<string>',
metadata: {},
versionConfig: {}
})
};
fetch('https://ingest.zelto.ai/webhooks/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://ingest.zelto.ai/webhooks/calls",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'call' => [
'externalId' => '<string>',
'startedAt' => '2023-11-07T05:31:56Z',
'endedAt' => '2023-11-07T05:31:56Z',
'durationSeconds' => 1,
'endedReason' => '<string>',
'cost' => 1,
'recordingUrl' => '<string>',
'recordingUploadId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'customer' => [
'number' => '<string>',
'name' => '<string>'
]
],
'agent' => [
'externalId' => '<string>',
'name' => '<string>'
],
'agentId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'version' => '<string>',
'companyExternalId' => '<string>',
'systemPrompt' => '<string>',
'metadata' => [
],
'versionConfig' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://ingest.zelto.ai/webhooks/calls"
payload := strings.NewReader("{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://ingest.zelto.ai/webhooks/calls")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://ingest.zelto.ai/webhooks/calls")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"call\": {\n \"externalId\": \"<string>\",\n \"startedAt\": \"2023-11-07T05:31:56Z\",\n \"endedAt\": \"2023-11-07T05:31:56Z\",\n \"durationSeconds\": 1,\n \"endedReason\": \"<string>\",\n \"cost\": 1,\n \"recordingUrl\": \"<string>\",\n \"recordingUploadId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"customer\": {\n \"number\": \"<string>\",\n \"name\": \"<string>\"\n }\n },\n \"agent\": {\n \"externalId\": \"<string>\",\n \"name\": \"<string>\"\n },\n \"agentId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"version\": \"<string>\",\n \"companyExternalId\": \"<string>\",\n \"systemPrompt\": \"<string>\",\n \"metadata\": {},\n \"versionConfig\": {}\n}"
response = http.request(request)
puts response.read_body{
"received": true
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}Authorizations
Send Authorization: Bearer YOUR_API_KEY. The key resolves to a single organization.
Headers
Which schema the request body uses. Defaults to zelto (the canonical CallIngest shape, also used by livekit). Native vapi / retell webhook payloads are also accepted.
zelto, vapi, retell, livekit Body
When X-Zelto-Provider is zelto (or omitted), the canonical CallIngest body. When vapi / retell, that provider's native webhook payload.
Zelto's canonical call upload shape (X-Zelto-Provider: zelto). Provide either agent.externalId (find-or-create) or agentId (attach to an existing agent).
Show child attributes
Show child attributes
Find-or-create an agent by your own id. Required unless agentId is given.
Show child attributes
Show child attributes
Attach to an existing Zelto agent. Takes precedence over agent.externalId.
Release label of the agent that actually handled this call. Optional for ingestion; send it on every call for reliable experiments. Case-sensitive, 1–255 characters after trimming. The same agent and label reuse a version; a new label creates one on the first processed call. Keep the agent ID stable and change this label for prompt or pipeline changes. Preserve the original label on retries. This is a top-level field, not metadata.version. See /docs/agent-versions.
1 - 255Your stable per-call id for the client or brand this call belongs to. When Companies is enabled, Zelto maps it to a company; otherwise the agent's default company is used when available.
1 - 255Show child attributes
Show child attributes
Actual system prompt of the version that handled this call. A leading system transcript turn is used if omitted. Available text is captured when a version is first observed, or fills a missing prompt later; an existing prompt snapshot is not overwritten. A version label alone does not supply a prompt.
Free-form metadata stored on the conversation.
Context for the version that handled this call. Requires the version field. Captured when the version is observed; does not overwrite an existing configuration snapshot. Use config when registering through POST /v1/agents/{id}/versions.
Response
Accepted for processing. Also returned for duplicate deliveries and dropped (non-terminal) events.

