> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voicebip.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voicebip.com/_mcp/server.

# Inbound Support Agent + Knowledge Base

> Provision a Nigerian number bound to a hosted-AI agent grounded in your own docs, and it answers callers 24/7 — no call centre, no code for the answering itself.

**What you build:** a `+234` phone line that, when called, is answered by an AI agent that speaks your greeting, understands the caller, and answers from *your* documents (FAQ, refund policy, price list) using retrieval-augmented generation (RAG). Unknown or sensitive requests get transferred to a human. You receive a webhook with the transcript when the call ends.

```
Caller dials +234… ─▶ Voicebip answers ─▶ hosted AI + your KB ─▶ spoken answer
                                             │
                                             └─(can't help)─▶ transfer_call to your team
On hangup ─▶ POST https://you.example.com/webhooks  (call.completed + transcript)
```

**Who it's for:** the SME or fintech founder who wants their +234 line answered without staffing it. This is the platform's "hello world" — it touches numbers, hosted AI, KB/RAG, and webhooks in one flow.

## Prerequisites

* A live API key (`pk_live_…`) or sandbox key (`pk_test_…`) from [your dashboard](https://app.voicebip.com/settings).
* A webhook receiver (any HTTPS endpoint) if you want the end-of-call transcript. See [Webhooks](/guides/webhooks) for signature verification. You can skip this and add it later.
* One or more documents (PDF/DOCX/TXT/URL) to ground the agent.

## Step 1 — Build the knowledge base

Create the KB, then add a document. Full detail (file uploads, status polling) is in the [Knowledge Base deep-dive](/use-cases/knowledge-base-deep-dive); here's the short path with a URL source (no upload step):

```bash
# Create the KB
curl -X POST https://api.voicebip.com/v1/knowledge-bases \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d '{ "name": "Support FAQ", "description": "Billing, refunds, delivery" }'
# → { "id": "kb_8sd2k1fapvq3", ... }

# Add a URL source (ingestion starts immediately)
curl -X POST https://api.voicebip.com/v1/knowledge-bases/kb_8sd2k1fapvq3/documents \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d '{ "name": "Help centre", "source_type": "url", "source_url": "https://acme.example/help" }'
```

Poll the document until `status: "ready"` before your first call — only `ready` documents are searched:

```bash
curl https://api.voicebip.com/v1/knowledge-bases/kb_8sd2k1fapvq3/documents/doc_4kq9zr1m7w0c \
  -H "Authorization: Bearer pk_xxx"
# status: pending → processing → ready
```

## Step 2 — Create the agent

Create a hosted-AI agent with a greeting, a voice, and the KB attached. Because it's hosted, retrieval is automatic on every turn — you write no RAG code.

```bash
curl -X POST https://api.voicebip.com/v1/agents \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d @agent.json
```

```json
{
  "display_name": "Acme Support Line",
  "language": "en",
  "ai_provider": "hosted",
  "ai_model": "<model_id>",
  "system_prompt": "You are Acme's friendly support agent. Answer only from the provided context. If you can't help or the caller asks for a human, use the transfer_call tool. Keep answers under 3 sentences.",
  "tts_voice": "<voice_id>",
  "greeting_text": "Thanks for calling Acme. How can I help you today?",
  "knowledge_base_id": "kb_8sd2k1fapvq3",
  "recording_enabled": true,
  "consent_required": true,
  "call_window_start": "08:00",
  "call_window_end": "20:00",
  "call_window_timezone": "Africa/Lagos"
}
```

```json
{
  "id": "agt_PAEZ_njcfm2kycpjs",
  "display_name": "Acme Support Line",
  "status": "active",
  ...
}
```

> **Note**
>
> `recording_enabled` requires workspace-level recording to be on, and `consent_required: true` plays a DTMF consent prompt before recording — the [NDPR](/guides/contact-erasure) default. Set `consent_required: false` only if you collect consent independently.

> **Tip**
>
> `<model_id>` and `<voice_id>` are placeholders. List the models available to your workspace with `GET /v1/ai/models`, and the TTS voices with `GET /v1/voice/models`, then use one of those ids. Omit either field to use the platform default.

## Step 3 — Provision a number bound to the agent

`POST /v1/numbers/auto` claims the first available number of the requested type **and binds it to the agent in one call** — so the number is answered by the agent immediately. (This is why the agent is created first.)

```bash
curl -X POST https://api.voicebip.com/v1/numbers/auto \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_PAEZ_njcfm2kycpjs",
    "type": "geo_did",
    "channels": ["voice"],
    "country_code": "NG"
  }'
```

```json
{
  "number_id": "num_7hq2k9wz1p4c",
  "e164": "+2341002223344",
  "type": "geo_did",
  "status": "provisioned",
  "agent_id": "agt_PAEZ_njcfm2kycpjs",
  "channels": ["voice"]
}
```

That's the whole build. Anyone who dials `+2341002223344` now reaches the agent.

> **Note**
>
> Number `type` is one of `geo_did`, `mobile_virtual`, `intl_did`, `shortcode`. To pick a specific number instead of auto-claiming, list candidates with `GET /v1/numbers/available` and provision one via `POST /v1/numbers` with `{number_id, agent_id, channels}`. In the sandbox, numbers are drawn from the test range and there's no charge.

## Step 4 — Handle the end-of-call webhook (optional)

When a call ends, Voicebip POSTs `call.completed` to your workspace webhook with the transcript. Verify the signature, then log it:

```js
// Express — end-of-call handler
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.raw({ type: "application/json" })); // raw body for HMAC

app.post("/webhooks", (req, res) => {
  const sig = req.header("X-Voicebip-Signature");        // "sha256=<hex>"
  const ts  = req.header("X-Voicebip-Timestamp");
  const mac = crypto.createHmac("sha256", process.env.VOICEBIP_SIGNING_SECRET)
    .update(`${ts}.${req.body}`).digest("hex");
  if (sig !== `sha256=${mac}`) return res.sendStatus(401);

  const evt = JSON.parse(req.body);
  if (evt.event_type === "call.completed") {
    console.log(evt.payload.status, evt.payload.transcript);
  }
  res.sendStatus(200); // ack fast; do slow work async
});
app.listen(3000);
```

The `call.completed` payload (inside the webhook envelope's `payload`):

```json
{
  "event_id": "evt_2m9…",
  "event_type": "call.completed",
  "channel": "voice",
  "agent_id": "agt_PAEZ_njcfm2kycpjs",
  "timestamp": "2026-07-02T09:14:22Z",
  "payload": {
    "call_id": "call_5xk…",
    "workspace_id": "ws_xyz123",
    "status": "completed",
    "direction": "inbound",
    "duration_seconds": 92,
    "mno_used": "mtn",
    "ai_mode": "hosted",
    "recording_url": "/v1/calls/call_5xk…/recording",
    "quality_score": 4.1,
    "transcript": [
      { "role": "caller", "text": "How long do refunds take?", "timestamp": "2026-07-02T09:13:10Z" },
      { "role": "agent", "text": "Refunds are processed within 5 to 7 business days.", "timestamp": "2026-07-02T09:13:14Z" }
    ]
  }
}
```

> **Note**
>
> `status` is `completed` for any call that ended; there is no per-call "confirmed/declined" field. Voicebip separately AI-classifies the call outcome as `resolution: resolved | unresolved` on the [Call object](/api) once classification runs.

## Config: the whole thing

| Object | Field               | Value                                                                                     |
| ------ | ------------------- | ----------------------------------------------------------------------------------------- |
| Number | `agent_id`          | bound at provision time (`/v1/numbers/auto`)                                              |
| Agent  | `ai_provider`       | `hosted`                                                                                  |
| Agent  | `knowledge_base_id` | `kb_8sd2k1fapvq3`                                                                         |
| Agent  | `tts_voice`         | a `voice_id` from `GET /v1/voice/models` (omit for the platform-default voice)            |
| Agent  | `system_prompt`     | instructs "answer from context, else `transfer_call`"                                     |
| Agent  | built-in tool       | `transfer_call` is available automatically — no `tool_definitions` needed for the handoff |

## Test it (sandbox, no real call)

With a `pk_test_` key, fire a synthetic `call.completed` at your webhook and watch it arrive with a real HMAC signature — no SIP, no billing:

```bash
curl -X POST https://api.voicebip.com/v1/webhooks/test \
  -H "Authorization: Bearer pk_test_xxx" -H "Content-Type: application/json" \
  -d '{ "webhook_url": "https://you.example.com/webhooks", "event_type": "call.completed" }'
```

You can also chat with the agent's brain directly (no telephony) via the playground to sanity-check that the KB is grounding answers:

```bash
curl -X POST https://api.voicebip.com/v1/agents/agt_PAEZ_njcfm2kycpjs/playground \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d '{ "message": "How long do refunds take?" }'
```

See [Sandbox Mode](/reference/sandbox-mode) for the full synthetic-event catalogue.

## Troubleshooting

| Symptom                                      | Likely cause                                                                                                                |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Agent answers generically, ignores your docs | Document not `ready` yet, or the question isn't similar enough to any chunk (relevance gate). Check `GET …/documents/{id}`. |
| Caller reaches a busy tone / no answer       | No number bound to the agent (Step 3), or call is outside the call window (08:00–20:00 `Africa/Lagos`).                     |
| No end-of-call webhook                       | Workspace webhook URL not configured, or your endpoint returned non-2xx (check [delivery history](/guides/webhooks)).       |

## Next steps

* Let the agent **do things**, not just answer → [Voice agent with tool-calling](/use-cases/tool-calling).
* Answer on **WhatsApp** too, from the same KB → [WhatsApp AI agent](/use-cases/whatsapp-agent).
* Serve callers in **Hausa, Yoruba, Igbo, or Pidgin** → set `language` on the agent.
* Understand retrieval tuning (chunking, `top_k`, relevance gate) → [Knowledge Base deep-dive](/use-cases/knowledge-base-deep-dive).