You've built an AI agent that holds smart conversations. Then your product manager says: "Great -- now let's use it to call 500 customers about their expiring subscriptions."
You start planning and quickly realize what you're actually signing up for:
- Provisioning a SIP trunk (carrier agreements required)
- Managing RTP media streams for every concurrent call
- Implementing retry logic for busy/no-answer
- Staying within carrier rate limits
- Tracking call dispositions (answered, voicemail, failed)
- Scaling the media server as concurrent calls grow
Before your AI has said a single word, you've built a telephony platform. And that's before you get to the business logic -- what the AI should say.
The Better Split: AI Owns the Conversation, Not the Infrastructure
VoIPBin is built around a simple idea: your AI should own the conversation logic, not the telephony plumbing. You call a REST API, VoIPBin places the call and handles RTP/TTS/STT, and you get a webhook when there's something to respond to.
Here's what a minimal outbound campaign looks like end to end.
Step 1: Get Your API Key
No credit card or OTP required. Signup is a single POST:
curl -s -X POST "https://api.voipbin.net/v1.0/auth/signup" -H "Content-Type: application/json" -d '{"username": "you@example.com", "password": "yourpassword"}'
The response includes accesskey.token -- use it as your Bearer token from here on.
Step 2: Define Your Conversation Flow
A flow tells VoIPBin what to say and what to do with responses. Here is a subscription-renewal reminder:
{
"name": "subscription-renewal-campaign",
"actions": [
{
"type": "talk",
"text": "Hi, this is Acme Corp. Your subscription expires in 3 days. Press 1 to renew now, press 2 for more options, or press 3 to be removed from this list."
},
{
"type": "gather",
"timeout": 5,
"num_digits": 1,
"action": "https://yourdomain.com/webhook/campaign-response"
}
]
}
Create the flow:
curl -s -X POST "https://api.voipbin.net/v1.0/flows" -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" -d @flow.json
Save the returned flow_id -- you will use it when placing calls.
Step 3: Place Outbound Calls
With a flow ID, you can work through a contact list. Here is a Python script with basic rate limiting:
import urllib.request
import time
import json
API_BASE = "https://api.voipbin.net/v1.0"
TOKEN = "YOUR_ACCESS_TOKEN"
FLOW_ID = "YOUR_FLOW_ID"
FROM_NUMBER = "+15551234567" # Your VoIPBin number
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def place_call(to_number: str, contact_name: str) -> dict:
payload = json.dumps({
"flow_id": FLOW_ID,
"source": FROM_NUMBER,
"destinations": [{"type": "tel", "target": to_number}],
"variables": {"contact_name": contact_name}
}).encode()
req = urllib.request.Request(
f"{API_BASE}/calls",
data=payload,
headers=HEADERS,
method="POST"
)
with urllib.request.urlopen(req) as resp:
return json.load(resp)
def run_campaign(contacts: list[dict], calls_per_minute: int = 20):
delay = 60.0 / calls_per_minute
results = []
for i, contact in enumerate(contacts):
print(f"[{i+1}/{len(contacts)}] Calling {contact['name']} at {contact['phone']}")
try:
result = place_call(contact["phone"], contact["name"])
results.append({"contact": contact, "call_id": result.get("id"), "status": "placed"})
except Exception as e:
results.append({"contact": contact, "call_id": None, "status": f"error: {e}"})
time.sleep(delay)
return results
contacts = [
{"name": "Alice", "phone": "+15550001111"},
{"name": "Bob", "phone": "+15550002222"},
{"name": "Carol", "phone": "+15550003333"},
]
results = run_campaign(contacts, calls_per_minute=20)
with open("campaign_results.json", "w") as f:
json.dump(results, f, indent=2)
print(f"Campaign complete. {len(results)} calls placed.")
Step 4: Handle Responses via Webhook
When a customer presses a key, VoIPBin calls your webhook. You reply with what happens next:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook/campaign-response", methods=["POST"])
def handle_response():
data = request.json
digit = data.get("digit")
call_id = data.get("call_id")
if digit == "1":
return jsonify({"actions": [
{"type": "talk", "text": "Great! Transferring you to our renewal team."},
{"type": "connect", "destination": "+15559998888"}
]})
elif digit == "2":
return jsonify({"actions": [
{"type": "talk", "text": "You can also renew at acmecorp.com/renew. Goodbye!"},
{"type": "hangup"}
]})
elif digit == "3":
remove_from_list(call_id)
return jsonify({"actions": [
{"type": "talk", "text": "You have been removed from our call list. Have a great day!"},
{"type": "hangup"}
]})
else:
return jsonify({"actions": [
{"type": "talk", "text": "We did not catch that. Please call 1-800-ACME. Goodbye!"},
{"type": "hangup"}
]})
def remove_from_list(call_id):
print(f"Removing contact for call {call_id}")
if __name__ == "__main__":
app.run(port=3000)
Notice what is missing: no SIP parsing, no audio encoding, no media state machine. Your webhook receives a clean event and returns a simple JSON array of next actions.
Step 5: Check Campaign Results
VoIPBin stores metadata for every call -- duration, disposition, and whether it was answered or went to voicemail:
curl -s "https://api.voipbin.net/v1.0/calls?flow_id=YOUR_FLOW_ID&page_size=100" -H "Authorization: Bearer YOUR_TOKEN"
Each record includes:
-
status:completed,no_answer,busy, orfailed -
duration: in seconds -
recording_id: if recording was enabled on the flow
What You Did Not Have to Build
| Task | DIY approach | With VoIPBin |
|---|---|---|
| SIP trunk provisioning | Your problem | Done |
| RTP media handling | Your problem | Done |
| TTS / STT | Integrate separately | Built-in |
| Call disposition tracking | Build your own schema | Included |
| Scaling concurrent calls | Provision servers | Managed |
| Retry / no-answer logic | Write it yourself | Configurable |
Your codebase stays focused on one thing: what the AI says and what happens in response.
Upgrading to Dynamic AI Conversations
Fixed scripts work well for simple reminders. But if you want your AI to respond dynamically -- handling questions, adapting tone, making real-time decisions -- VoIPBin supports connecting a live call to an AI agent via websocket. The infrastructure stays the same: VoIPBin handles the audio, your agent receives clean text and returns spoken responses.
This means you can start with a deterministic flow (step 2 above) and graduate to a fully conversational AI agent without rebuilding your campaign infrastructure.
Getting Started
- Website: voipbin.net
-
Golang SDK:
go get github.com/voipbin/voipbin-go -
MCP server (Claude Desktop / Cursor):
uvx voipbin-mcp -
API base:
https://api.voipbin.net/v1.0
Build the campaign logic. Let someone else run the phone system.
Top comments (0)