
# Phone System Integration

Connect an inbound voice agent to an advanced phone system (PBX) such as 3CX. Your phone system can send caller context — like IVR menu selections — to the agent right before it bridges the call, and retrieve call results (telemetry plus values the agent collected) after the call ends.

## What you'll need

- An inbound voice agent with a phone number assigned — see [Create a Voice Agent](/help/voice-agents/creating-campaigns)
- The `agents:voice:manage` permission
- A phone system that can make HTTP webhook calls before and/or after transferring a call (3CX call flows, for example, support an HTTP request step)

## How it works

```mermaid
sequenceDiagram
    participant C as Caller
    participant P as Phone system (PBX)
    participant K as Karmaflow.ai
    participant A as Voice agent
    C->>P: Dials in, makes IVR selections
    P->>K: POST pre-call webhook (caller, IVR data)
    P->>A: Bridges the call to the agent's number
    K->>A: Injects the pre-call context into the agent
    A->>A: Collects configured values during the call
    A->>C: Ends the call (only after required values are saved)
    P->>K: POST post-call webhook
    K->>P: Call telemetry + collected values in the response
```

## Enable the integration

1. Go to **Agents → Agent Types → Voice Agents** and open your inbound agent for editing.
2. Open the **Phone System** tab.
3. Turn on **Enable Phone System Integration**.
4. Click **Save**. Saving generates the webhook URLs shown in the sections below.

## Send caller context to the agent (pre-call webhook)

1. In the **Pre-call webhook** card, turn the toggle on and click **Save**.
2. Copy the **Webhook URL (POST)** with the **Copy** button and configure your phone system to POST its JSON payload there right before transferring the call.

<!-- SCREENSHOT: voice-agents/phone-system-precall.png — Phone System tab with the Pre-call webhook card enabled, showing the generated URL and the Values sent to the agent table -->

3. Set **Caller number field (dot-path)** to the payload field that holds the caller's number (for example `caller` or `call.from`). Karmaflow.ai matches the webhook to the incoming call using this number.
4. Under **Values sent to the agent**, click **Add field** for each payload value the agent should see:
   - **Payload path** — dot-path into the JSON body, e.g. `ivr.selection`
   - **Label** — how it appears in the agent's context, e.g. `IVR Selection`
   - **Placeholder** — a key you can reference in the agent's instructions as `{{ivr_selection}}`
5. Optionally require authentication with **Header secret** (a shared secret in a request header) or **HMAC signature** (an HMAC-SHA256 signature of the raw body). Each secret field has a **Generate** button that fills in a strong random secret and reveals it — copy it into your phone system's webhook configuration, then save the agent. The show/hide and copy buttons work on any secret you enter.

When the call arrives, the mapped values appear to the agent as a "Phone system context" block, and any `{{placeholder}}` keys in the agent's instructions resolve to the received values. If no webhook arrives before the call, the call proceeds normally without the context.

**Caller-ID mismatch:** some phone systems bridge calls as a three-way call, so the caller ID Karmaflow.ai sees may differ from the number in the webhook payload. Leave **Tolerate caller-ID mismatch** on to accept the context anyway when exactly one pending context exists for the agent.

## Collect values during the call

1. In the **Values the agent must collect** card, click **Add value** for each item:
   - **Label** and **Key** — the key identifies the value in the post-call results
   - **Type** — Text, Number, Yes/No, or Picklist (with comma-separated options)
   - **Guidance for the agent** — how to ask for it or what qualifies
   - **Required** — check to prevent the agent from hanging up before this value is saved
2. Click **Save**.

The agent saves values as the caller provides them. If it tries to end the call while required values are missing, the hang-up is refused and the agent is told what to ask for. If the **caller** hangs up first, the results are still delivered with `valuesComplete: false` and a `missingRequired` list.

## Retrieve results after the call

Two delivery options — use either or both:

**Your phone system pulls (recommended for 3CX-style flows).** Turn on **Post-call results — phone system pulls**, save, and point your phone system's post-call HTTP step at the generated URL. When the PBX POSTs after the hangup, the response contains the results. If the call is still finalizing, the response is `{ "status": "pending" }` — safe to retry the same request. Set **Call ID field** if your system echoes Karmaflow.ai's call ID back; otherwise matching falls back to the caller number.

**Karmaflow.ai pushes.** Turn on **Post-call results — we push** and enter your **Destination URL**. When a call ends, Karmaflow.ai POSTs the results, retrying on failure. Set a **Signing secret** (use **Generate** for a strong one) to receive an `X-Karmaflow-Signature: sha256=<hex>` header — the HMAC-SHA256 of `<X-Karmaflow-Timestamp>.<body>`. Add **Static headers** if your endpoint expects fixed headers such as an API key. Use **Send test** to deliver a sample payload to your endpoint (save the agent first).

The results payload looks like:

```json
{
  "event": "call.ended",
  "callId": "…",
  "direction": "inbound",
  "from": "+15551234567",
  "to": "+15557654321",
  "startedAt": "2026-07-26T18:02:11.000Z",
  "endedAt": "2026-07-26T18:06:40.000Z",
  "durationSeconds": 269,
  "disposition": "ended",
  "collectedValues": { "ticket_number": "T-4821", "callback_ok": true },
  "valuesComplete": true,
  "missingRequired": [],
  "recordingUrl": null
}
```

The AI call summary is produced by post-call analysis a few minutes after the call and is not included in this payload — review it on the [call detail page](/help/voice-agents/call-detail).

## Troubleshooting

- **The agent doesn't mention the pre-call data.** Confirm the webhook returned `200` with a `contextId`, that it fired *before* the call reached the agent's number, and that **Caller number field** points at the right payload path. Context expires after the configured lifetime (default 15 minutes).
- **The post-call response stays `pending`.** The values are written as the call wraps up — retry after a second or two. If it persists, check the call actually ended on the [call detail page](/help/voice-agents/call-detail).
- **`401` from a webhook URL.** The URL token, header secret, or HMAC signature didn't match. Re-copy the URL (regenerating the token invalidates old URLs immediately) and verify the secret configured in your phone system.

## Related

- [Create a Voice Agent](/help/voice-agents/creating-campaigns)
- [Calls, Transcripts and Recordings](/help/voice-agents/call-detail)
- [Phone Numbers and Caller ID](/help/voice-agents/phone-numbers)
