> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vocobase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Inbound Routing Policies

> Evaluate inbound calls before AI session creation and route each call using webhook decisions

# Inbound Routing Policies

Inbound Routing Policies are webhook-backed pre-answer routing hooks for inbound phone calls. Vocobase resolves the inbound DID, finds the applicable policy, calls your policy webhook, and only then decides whether to create an AI session, select another agent, transfer the call, or reject it.

> [!NOTE]
>
> Policies run for Vobiz, Plivo, and VoiceLink inbound calls. `TRANSFER` is not available on VoiceLink (see [Provider Support](#provider-support)). Runtime scope priority is `PHONE_NUMBER` -> `AGENT` -> `PARTNER`.

## API

### Policy endpoints

* `GET /api/v2/inbound-route-policies`
* `POST /api/v2/inbound-route-policies`
* `GET /api/v2/inbound-route-policies/{id}`
* `PATCH /api/v2/inbound-route-policies/{id}`
* `DELETE /api/v2/inbound-route-policies/{id}`

### Decision-log endpoint

* `GET /api/v2/inbound-route-decisions`

Decision logs are newest-first and scoped to the authenticated account. Query filters:

| Query | Notes |
| - | - |
| `policy_id` | Filter by inbound policy ID. |
| `phone_number_id` | Filter by DID row ID. |
| `action` | `ANSWER_WITH_AI`, `SELECT_AGENT`, `TRANSFER`, or `REJECT`. |
| `provider` | `vobiz`, `plivo`, or `voicelink`. |
| `limit` | Defaults to `50`, maximum `200`. |
| `cursor` | Opaque cursor returned as `next_cursor`. |

## Scope Resolution

Vocobase evaluates enabled policies in this order:

1. `PHONE_NUMBER`: exact policy for the inbound DID row.
2. `AGENT`: fallback policy for the agent assigned to the DID.
3. `PARTNER`: the account-wide default policy.

> [!NOTE]
>
> `PARTNER` is a legacy name kept for backward compatibility — it is the
> **account-wide** scope, and it applies to every account. Existing integrations
> that already send `"scope": "PARTNER"` keep working unchanged.

If no policy matches, Vocobase continues with the normal AI answer path.

### Request fields

| Field | Type | Required | Notes |
| - | - | - | - |
| `scope` | `PHONE_NUMBER` \| `AGENT` \| `PARTNER` | Yes | Selects the policy level. |
| `phone_number_id` | UUID | For `PHONE_NUMBER` | Must reference an active inbound-enabled Vobiz, Plivo, or VoiceLink DID owned by your account. |
| `agent_id` | UUID | For `AGENT` | Must reference an active agent owned by your account. |
| `name` | string (1-200) | Yes (POST) / No (PATCH) | Policy label for your own reference. |
| `enabled` | boolean | No (default: `true`) | Toggle policy evaluation without deleting it. |
| `webhook_url` | HTTPS URI | Yes (POST) / No (PATCH) | Receives the `inbound.route` webhook. |
| `webhook_secret` | string or null | No | Write-only secret used to sign webhook requests. `null` clears it on update. |
| `timeout_ms` | integer | No (default: `2000`) | Must be between `250` and `5000`. |
| `failure_action` | `ANSWER_WITH_AI` \| `REJECT` | No (default: `ANSWER_WITH_AI`) | Action to take on webhook timeout, failure, or invalid response. |
| `allowed_actions` | string\[] | No | Restricts actions returned by your webhook. Omit to allow all actions. |

### Response fields

| Field | Type | Notes |
| - | - | - |
| `id` | string | Opaque policy ID. |
| `scope` | string | `PHONE_NUMBER`, `AGENT`, or `PARTNER`. |
| `phone_number_id` | UUID \| null | DID target for phone-number policies. |
| `agent_id` | UUID \| null | Agent target for `AGENT` policies. Null for `PHONE_NUMBER` and `PARTNER` policies. |
| `name` | string | Policy label. |
| `enabled` | boolean | `false` disables evaluation. |
| `webhook_url` | URI | Target endpoint for `inbound.route`. |
| `has_webhook_secret` | boolean | `true` when a signing secret is configured. |
| `timeout_ms` | integer | Decision timeout in ms. |
| `failure_action` | `ANSWER_WITH_AI` \| `REJECT` | Default action on failure. |
| `allowed_actions` | `array<string>` \| null | Restriction list; `null` means no explicit allowlist. |
| `created_at` | ISO timestamp | Creation time. |
| `updated_at` | ISO timestamp | Last edit time. |

## Examples

### Create a phone-number policy

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/inbound-route-policies \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "PHONE_NUMBER",
    "phone_number_id": "f1a2c3d4-1111-2222-3333-444455556666",
    "name": "Inbound owner routing",
    "enabled": true,
    "webhook_url": "https://app.example.com/webhooks/vocobase/inbound-route",
    "webhook_secret": "optional-secret",
    "timeout_ms": 2000,
    "failure_action": "ANSWER_WITH_AI",
    "allowed_actions": ["ANSWER_WITH_AI","SELECT_AGENT","TRANSFER","REJECT"]
  }'
```

For a VoiceLink DID, omit `TRANSFER` from `allowed_actions`, or omit the field entirely.

### Create an agent fallback policy

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/inbound-route-policies \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "AGENT",
    "agent_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Sales agent fallback",
    "webhook_url": "https://app.example.com/webhooks/vocobase/sales-agent",
    "failure_action": "ANSWER_WITH_AI"
  }'
```

### Create an account-wide default policy

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/inbound-route-policies \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "PARTNER",
    "name": "Account default inbound route",
    "webhook_url": "https://app.example.com/webhooks/vocobase/default-inbound",
    "failure_action": "ANSWER_WITH_AI"
  }'
```

### Query recent decisions

```bash theme={null}
curl -sS "https://api.vocobase.com/api/v2/inbound-route-decisions?phone_number_id=f1a2c3d4-1111-2222-3333-444455556666&limit=25" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Decision rows include `provider`, `provider_call_id`, `from_number`, `to_number`, `action`, `decision_source`, `failure_reason`, `transfer_to_number`, `webhook_status`, `webhook_latency_ms`, `created_session_id`, and `created_phone_call_id`.

## Webhook Event Sent By Vocobase

Vocobase sends this event to `webhook_url` before creating any session:

```json theme={null}
{
  "event": "inbound.route",
  "version": "2026-07-04",
  "provider": "plivo",
  "call": {
    "provider_call_id": "a1b2c3d4-5e6f-7890-1234-56789abcde01",
    "from_number": "+919999999999",
    "to_number": "+918888888888"
  },
  "phone_number": {
    "id": "f1a2c3d4-1111-2222-3333-444455556666",
    "number": "+918888888888"
  },
  "agent": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
}
```

### Headers

| Header | Description |
| - | - |
| `X-Vocobase-Event` | Always `inbound.route`. |
| `X-Vocobase-Timestamp` | Unix milliseconds at send time. |
| `X-Vocobase-Signature` | `v1=<hmac_sha256(timestamp.raw_body)>` when webhook secret is set. |
| `Content-Type` | `application/json`. |

Vocobase computes HMAC over:

```
${timestamp}.${raw_json_body}
```

## Supported Webhook Actions

| Action | Meaning | Payload |
| - | - | - |
| `ANSWER_WITH_AI` | Route call to AI. Optional `variables` are merged into session variables. | `{ "action": "ANSWER_WITH_AI", "variables": { "customer_status": "unknown", "language": "en" } }` |
| `SELECT_AGENT` | Route call to a different agent owned by the same account. | `{ "action": "SELECT_AGENT", "agent_id": "agent_id", "variables": { "owner_level": "vip" } }` |
| `TRANSFER` | Transfer to a destination number without creating an AI session. Not supported on VoiceLink — see below. | `{ "action": "TRANSFER", "to_number": "+919999999999", "reason": "known_customer_owner" }` |
| `REJECT` | End call without AI. | `{ "action": "REJECT", "reason": "blocked_number" }` |

### Provider Support

Policies are evaluated on inbound calls for Vobiz, Plivo, and VoiceLink.

`TRANSFER` is **not supported on VoiceLink**. A VoiceLink inbound call reaches the
policy after the media session has already connected, so there is no point at which
Vocobase can hand the call to a carrier. Behaviour:

* A policy scoped to a VoiceLink DID is rejected at write time (`400 VALIDATION_ERROR`,
  `allowed_actions must not include TRANSFER for a VoiceLink phone number`) if
  `allowed_actions` names `TRANSFER` explicitly. Omitting `allowed_actions` (all
  actions permitted) stays valid.
* If a webhook returns `TRANSFER` on a VoiceLink call anyway, Vocobase answers with
  AI instead of dropping the call, and records the decision with
  `failure_reason: "transfer_unsupported_on_voicelink"`. This overrides
  `failure_action`, including `REJECT` — a transfer that cannot be performed is not
  a reason to hang up on a caller.

`ANSWER_WITH_AI`, `SELECT_AGENT`, and `REJECT` produce the same outcome across all
three providers. One difference in caller experience: on Vobiz and Plivo a `REJECT`
refuses the call before it is answered, while on VoiceLink the call has already been
answered by the time the policy runs, so `REJECT` ends a connected call.

## Response Handling And Failure Behavior

* HTTP `202` or `204` from webhook: continue as `ANSWER_WITH_AI`.
* HTTP 2xx with JSON body: use the returned action.
* Non-2xx response: apply `failure_action`.
* Timeout, network failure, invalid JSON, unknown action, disallowed action, or unauthorized selected agent: apply `failure_action`.
* Default `failure_action` is `ANSWER_WITH_AI` unless explicitly set to `REJECT`.

If `SELECT_AGENT` returns an agent outside your account, Vocobase uses the configured `failure_action`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.