Inbound Support Agent + Knowledge Base

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.
  • A webhook receiver (any HTTPS endpoint) if you want the end-of-call transcript. See 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; here’s the short path with a URL source (no upload step):

$# 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:

$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.

$curl -X POST https://api.voicebip.com/v1/agents \
> -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
> -d @agent.json
1{
2 "display_name": "Acme Support Line",
3 "language": "en",
4 "ai_provider": "hosted",
5 "ai_model": "<model_id>",
6 "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.",
7 "tts_voice": "<voice_id>",
8 "greeting_text": "Thanks for calling Acme. How can I help you today?",
9 "knowledge_base_id": "kb_8sd2k1fapvq3",
10 "recording_enabled": true,
11 "consent_required": true,
12 "call_window_start": "08:00",
13 "call_window_end": "20:00",
14 "call_window_timezone": "Africa/Lagos"
15}
1{
2 "id": "agt_PAEZ_njcfm2kycpjs",
3 "display_name": "Acme Support Line",
4 "status": "active",
5 ...
6}

recording_enabled requires workspace-level recording to be on, and consent_required: true plays a DTMF consent prompt before recording — the NDPR default. Set consent_required: false only if you collect consent independently.

<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.)

$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"
> }'
1{
2 "number_id": "num_7hq2k9wz1p4c",
3 "e164": "+2341002223344",
4 "type": "geo_did",
5 "status": "provisioned",
6 "agent_id": "agt_PAEZ_njcfm2kycpjs",
7 "channels": ["voice"]
8}

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

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:

1// Express — end-of-call handler
2import express from "express";
3import crypto from "crypto";
4
5const app = express();
6app.use(express.raw({ type: "application/json" })); // raw body for HMAC
7
8app.post("/webhooks", (req, res) => {
9 const sig = req.header("X-Voicebip-Signature"); // "sha256=<hex>"
10 const ts = req.header("X-Voicebip-Timestamp");
11 const mac = crypto.createHmac("sha256", process.env.VOICEBIP_SIGNING_SECRET)
12 .update(`${ts}.${req.body}`).digest("hex");
13 if (sig !== `sha256=${mac}`) return res.sendStatus(401);
14
15 const evt = JSON.parse(req.body);
16 if (evt.event_type === "call.completed") {
17 console.log(evt.payload.status, evt.payload.transcript);
18 }
19 res.sendStatus(200); // ack fast; do slow work async
20});
21app.listen(3000);

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

1{
2 "event_id": "evt_2m9…",
3 "event_type": "call.completed",
4 "channel": "voice",
5 "agent_id": "agt_PAEZ_njcfm2kycpjs",
6 "timestamp": "2026-07-02T09:14:22Z",
7 "payload": {
8 "call_id": "call_5xk…",
9 "workspace_id": "ws_xyz123",
10 "status": "completed",
11 "direction": "inbound",
12 "duration_seconds": 92,
13 "mno_used": "mtn",
14 "ai_mode": "hosted",
15 "recording_url": "/v1/calls/call_5xk…/recording",
16 "quality_score": 4.1,
17 "transcript": [
18 { "role": "caller", "text": "How long do refunds take?", "timestamp": "2026-07-02T09:13:10Z" },
19 { "role": "agent", "text": "Refunds are processed within 5 to 7 business days.", "timestamp": "2026-07-02T09:13:14Z" }
20 ]
21 }
22}

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 once classification runs.

Config: the whole thing

ObjectFieldValue
Numberagent_idbound at provision time (/v1/numbers/auto)
Agentai_providerhosted
Agentknowledge_base_idkb_8sd2k1fapvq3
Agenttts_voicea voice_id from GET /v1/voice/models (omit for the platform-default voice)
Agentsystem_promptinstructs “answer from context, else transfer_call
Agentbuilt-in tooltransfer_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:

$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:

$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 for the full synthetic-event catalogue.

Troubleshooting

SymptomLikely cause
Agent answers generically, ignores your docsDocument 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 answerNo number bound to the agent (Step 3), or call is outside the call window (08:00–20:00 Africa/Lagos).
No end-of-call webhookWorkspace webhook URL not configured, or your endpoint returned non-2xx (check delivery history).

Next steps