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>",
"systemPrompt": "<string>",
"metadata": {}
}
'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>",
"systemPrompt": "<string>",
"metadata": {}
}
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>',
systemPrompt: '<string>',
metadata: {}
})
};
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>',
'systemPrompt' => '<string>',
'metadata' => [
]
]),
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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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>",
"systemPrompt": "<string>",
"metadata": {}
}
'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>",
"systemPrompt": "<string>",
"metadata": {}
}
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>',
systemPrompt: '<string>',
metadata: {}
})
};
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>',
'systemPrompt' => '<string>',
'metadata' => [
]
]),
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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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 \"systemPrompt\": \"<string>\",\n \"metadata\": {}\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.
Version label for the agent build that handled this call (e.g. v3 or a prompt hash). Recorded as the call's agent version so analytics can segment by it. Optional; wins over any rotating-id derivation when both apply.
1 - 255Show child attributes
Show child attributes
Agent system prompt. A leading system transcript turn is used if this is omitted.
Free-form metadata stored on the conversation.
Response
Accepted for processing. Also returned for duplicate deliveries and dropped (non-terminal) events.

