Skip to main content
Send a test trace, find it in Zelto, then apply the same setup to your agent. A trace represents one call or session; its spans represent timed steps such as speech recognition, an LLM request, a tool call, or speech synthesis. Exporting traces does not upload a call transcript or recording: those arrive through your call integration. You need a Zelto organization, an owner or admin to create its ingest key, and a server or worker that can make HTTPS requests to ingest.zelto.ai. The first example uses Python 3.10+ and synthetic data; it does not place a call.

1. Create a telemetry key

In the organization you want to send to, open Traces → Connect agent or Settings → Integrations → Observability. Create an observability ingest key and copy it when shown. It is a write-only credential for that organization. Store it in your worker’s secrets or environment as ZELTO_INGEST_KEY. This is separate from the REST/MCP API key used to upload conversations. Telemetry uses Authorization: Bearer <ingest-key> and needs no X-Zelto-Provider header. Keep the key in server-side code.

2. Export a first trace

Create an isolated Python environment and install the SDK and HTTP exporter:
Save this as send_trace.py, replacing the placeholder key in your environment before running it. This example represents one agent, so its stable identity is on the resource, the attributes shared by all spans from this provider:
You should see a trace ID and a unique session ID. The child span shares the root’s trace ID because it is created inside the active root span. Attributes such as session.id are not automatically inherited by child spans, so set them on every relevant span or through your framework’s metadata mechanism. The script flushes ended spans before exiting; inspect exporter errors too—a printed ID or a completed flush alone does not prove the server accepted it.

3. Find it in Zelto

Open Traces in the same organization, clear agent/company filters, and choose a range that includes now. Look for demo.call and compare its trace ID with the script’s output. Open it to see demo.tool beneath the root and inspect its attributes. Zelto reuses or creates the agent named support-agent. A successful HTTP response acknowledges acceptance. For organizations using buffered ingestion, data becomes queryable asynchronously; the target is within 30 seconds after acceptance under normal load, not a guaranteed maximum. Exporter batching adds time before acceptance, and processing can take longer during an outage. Refresh before diagnosing a missing trace. Linked-call panels also require the conversation to have arrived. This synthetic example has no linked conversation until a call with the same external ID is uploaded. That does not mean the trace export failed.

Identify agents and calls consistently

Zelto matches external agent IDs within your organization. Unknown IDs create agents automatically, initially named after the external ID. If you omit the provider, Zelto reuses a unique existing match or creates a new agent under zelto. Send livekit when calls use X-Zelto-Provider: livekit to prevent separate identities when telemetry arrives first. Multiple matches across providers remain unassigned; specify the provider to disambiguate them. External IDs are case-sensitive strings, at most 255 characters; provider names support up to 50. Merged identities resolve to their surviving agent. If an external identity cannot be resolved, Zelto does not substitute an unrelated agent from a matching session. See agent identity rules. For one agent per process, resource attributes are convenient. For a process serving multiple agents, set the identity on each span and log instead; record attributes override resource defaults. Do not use a room ID or version label as the agent ID. zelto.agent_id and native agentId mean a Zelto UUID and take precedence; your own UUID-shaped ID still belongs in zelto.agent_external_id. Send call data through your provider integration or call upload API, using the same organization, agent identity, provider, and external call ID. Call and trace can arrive in either order. The trace header shows Open conversation when one matching call is found; that enables the available recording, transcript, and monitor results. See conversation linking.

Connect your existing exporter

If your application already initializes OpenTelemetry, add the Zelto exporter to its existing provider instead of registering a second global provider. Configure an OTLP/HTTP exporter; gRPC on port 4317 is not supported. For an exporter that reads standard OpenTelemetry environment variables:
The shared base endpoint has no /v1/traces suffix: the SDK appends the signal path. A signal-specific endpoint or an explicit exporter URL is the full URL, as in the Python example: Use Content-Type: application/x-protobuf for protobuf and application/json for JSON. Keep Python’s HTTP exporter on http/protobuf. There is no telemetry metrics endpoint. See the OpenTelemetry Python exporter documentation for SDK configuration. Environment variables alone do not instrument your code: initialize the SDK and exporter, create spans (or enable your framework’s instrumentation), and end spans so they can be exported. Use a batch processor, keep it alive for the worker’s lifetime, and flush at graceful shutdown rather than after every span. Keep trace context when work crosses asynchronous tasks or service boundaries.

LiveKit

Use the LiveKit tracing setup at the start of the agent entrypoint, before session.start(). It registers LiveKit’s provider, sets your external agent identity and room reference, and flushes pending spans on shutdown. Use the same stable agent ID and room name as your finished-session upload. LiveKit’s session instrumentation supplies the spans; forwarding the transcript remains a separate step.

Logs

A trace exporter does not automatically export application logs. Configure an OTLP/HTTP log exporter and logging bridge for your SDK, pointed at https://ingest.zelto.ai/v1/logs, with the same bearer key. Emit logs while the relevant span is active so the bridge can attach its trace and span IDs. Include the agent identity on the log or its resource as well. Zelto shows correlated logs in the span’s Logs tab and trace-wide logs in Trace overview → Logs. You can also send logs with the native example below.

Native JSON without an OpenTelemetry SDK

Use /webhooks/otel when your stack cannot emit OTLP. This is a different JSON shape from OTLP JSON. Generate current timestamps and fresh identities for a new session; save the resulting file unchanged if you need to retry:
Expected response: HTTP 200 with {"received":true}. OTLP endpoints return HTTP 200 with {}. Native span timestamps use Unix milliseconds; OTLP timestamps use Unix nanoseconds. Use parentSpanId for native child spans and the same traceId to place them in one tree. Native attributes accept string, number, and boolean values.

Limits, retries, and troubleshooting

Every telemetry request is limited to 10 MB (10,000,000 bytes) after decompression. Tune batch size for your span sizes; a row count alone does not bound request bytes. Keep the original trace/span IDs, timestamps, and payload when retrying an export. Sending the same call as new trace IDs produces separate traces. SDK retries and in-memory queues are finite: a process crash, full queue, or extended outage can lose telemetry before Zelto accepts it. Monitor exporter errors and queue drops; use a Collector with persistent buffering if you need durable delivery from your infrastructure. The OTLP retry specification describes retryable status codes. You now have an exporter, stable agent identity, and a way to verify delivery. Use Traces to explore the data and the LiveKit integration to attach finished calls.