Skip to main content

Send call records to your tools

Proxium can send a record of each call of your project to your own tools: a webhook, or an OpenTelemetry endpoint that takes OTLP/HTTP traces. You then keep, search and alert on your traffic where you keep the rest of your data.

What Proxium sends​

Three kinds of record. The request_id joins the records of one call.

RecordOne for eachHolds
attemptVendor call, including each failoverThe vendor, the model, the try number, the outcome, the reason of a failure, the duration, the app
callCall that a vendor answeredThe model that you asked for and the model that answered, the tokens, the cost in dollars, the app and the key prefix
bodyVendor call, only if you ask for bodiesThe whole request that went to the vendor, and its answer

A body record leaves only when Stored prompts and answers in Settings keeps that call. With errors, only the bodies of failed calls leave.

1. Add a destination​

  1. Open Settings › Send call records.
  2. In Kind, select webhook or OTLP/HTTP traces.
  3. Type the URL. It must start with https://. For OTLP, it is the traces endpoint, such as https://otlp.example.com/v1/traces.
  4. For OTLP, type the headers that your backend needs in Headers, one Name: value on each line.
  5. To send the prompts and the answers, select the box below the form.
  6. Select Add destination.

A project keeps at most 5 destinations. A new destination starts to get records within 10 seconds.

warning

For a webhook, copy the signing key when the console shows it. It is the only time it is shown.

2. Test it​

Select Test on the row. Proxium sends one test record now, and the row shows sent, or the reason that it failed.

Check a webhook signature​

Each webhook request has the header x-proxium-signature: t=<unix seconds>,v1=<hex>. The hex is HMAC-SHA256, with your signing key, over the time, a dot, and the body as it arrived. Refuse a request whose time is old, so nobody can send a captured request again.

verify.py
import hashlib, hmac, time

def verify(header: str, body: bytes, key: str, max_age: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, sig = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > max_age:
return False
want = hmac.new(key.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, sig)

The webhook body​

Proxium sends the records in batches: at most 100 records, or every 2 seconds.

Webhook body
{
"records": [
{
"type": "attempt",
"request_id": "0192f0a0-5f3c-7c1e-9a51-1d2e3f4a5b6c",
"at": "2026-10-03T14:05:00.120Z",
"project": "acme",
"app": "support-bot",
"endpoint": "chat",
"model_requested": "standard",
"provider": "groq",
"model": "llama-3.3-70b-versatile",
"try": 1,
"outcome": "success",
"reason": null,
"duration_ms": 412
},
{
"type": "call",
"request_id": "0192f0a0-5f3c-7c1e-9a51-1d2e3f4a5b6c",
"at": "2026-10-03T14:05:00.121Z",
"project": "acme",
"app": "support-bot",
"key_prefix": "mbk_AbCdEfGh",
"endpoint": "chat",
"model_requested": "standard",
"provider": "groq",
"model": "llama-3.3-70b-versatile",
"input_tokens": 812,
"output_tokens": 96,
"cache_read_tokens": 0,
"cache_write_tokens": 0,
"cost_usd": 0.00055,
"price_missing": false
}
]
}

A body record has request (the JSON that went to the vendor), response (the answer as text), request_truncated and response_truncated.

The OTLP spans​

Each record is one span, with the OpenTelemetry gen_ai.* names where one exists:

SpanAttributes
proxium.attemptgen_ai.system, gen_ai.request.model, gen_ai.response.model, proxium.try, proxium.outcome, proxium.reason, proxium.app
proxium.callgen_ai.usage.input_tokens, gen_ai.usage.output_tokens, proxium.cost_usd, proxium.app, proxium.key_prefix
proxium.bodygen_ai.prompt, gen_ai.completion

The trace id is the request_id without dashes, so the spans of one call are in one trace. The service name is proxium, and proxium.project names the project.

Delivery​

  • A batch is sent at most once. A batch that fails counts in failed, and it is not sent again.
  • When records come faster than they can leave, Proxium drops the extra records and counts them in dropped. A call is never slowed down or refused for a record.
  • Each destination passes the address check when you add it and before each batch. A URL that resolves to a private address is refused.
  • Pause stops a destination and keeps it. Remove deletes it.