Skip to main content
After an Omni call ends, PyAI can run an extraction pass over the transcript and deliver a structured JSON object — captured fields, intent, outcome — to your webhook. No client code and no engine integration required: you declare a JSON Schema on the agent and point it at an HTTPS endpoint.
This is post-call capture (after the call, fully PyAI-run). For in-call function calling where your code executes mid-conversation, see Function calling. For the difference between the tool systems, see Tools overview.

Configure

Set two fields on the agent — a JSON Schema of the fields to capture and a signed delivery URL:
curl -sS -X PATCH "https://api.pyai.com/v1/agents/agent_…" \
  -H "Authorization: Bearer $PYAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "extraction_schema": {
      "type": "object",
      "properties": {
        "caller_name": { "type": "string" },
        "intent": { "type": "string" },
        "order_id": { "type": "string" },
        "follow_up_needed": { "type": "boolean" }
      }
    },
    "extraction_webhook_url": "https://your-app.example.com/pyai/extraction"
  }'
Or use the console: Agents → your agent → Data capture. Both fields are required to enable extraction. Set either to null to turn it off. extraction_webhook_url must be https.

How a call is matched to your agent

Omni is zero-state — a call is authorized by your key’s org, not a stored agent. To attribute a call to a specific agent’s extraction config, set the connect URL’s session_label to that agent’s id:
wss://api.pyai.com/v1/omni?format=pcm16&rate=24000&session_label=agent_7f3a0b12
PyAI resolves the agent only when the session_label equals an agent id in the same org — so another tenant’s id can never trigger your extraction.

The delivery (engine → your webhook)

When the call completes, PyAI POSTs the extracted object, signed with X-PyAI-Signature (same scheme as transcription webhooks):
POST <extraction_webhook_url>
Content-Type: application/json
X-PyAI-Signature: t=1718900000,v1=<hex hmac-sha256>

{
  "type": "omni.call.extracted",
  "created": 1718900000,
  "data": {
    "object": "omni.call.extraction",
    "call_id": "call_abc",
    "org_id": "org_…",
    "agent_id": "agent_7f3a0b12",
    "session_label": "agent_7f3a0b12",
    "extracted": {
      "caller_name": "Alex",
      "intent": "track_order",
      "order_id": "ORD-7782",
      "follow_up_needed": false
    }
  }
}

Verify the signature

The signature is t=<unix_seconds>,v1=<hmac> where the HMAC-SHA256 is computed over `${t}.${rawBody}` with your webhook signing secret:
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

Behavior & limits

  • Best-effort / fail-open. Extraction never blocks or fails a call. If the transcript is empty, the model can’t produce valid JSON, or your webhook is down, the call is unaffected and nothing is delivered.
  • Fields you can’t determine come back null — values are never invented.
  • The transcript is truncated for very long calls before the extraction pass.
  • Delivery is a single signed POST (no retry today). Return 2xx quickly.

See also

Tools overview

Knowledge vs function-calling tools.

Function calling

In-call tools your client executes.

Omni protocol

Connect URL, session_label, frames.

Conversation intelligence

Transcripts, summaries, recordings.