Skip to main content

Overview

The Voice service enables you to make outbound calls, provide interactive voice instructions using XML, and handle call events through webhooks.

Service Status

Check Voice Service Status

curl http://localhost:8000/voice/
service
string
Service name (“voice”)
status
string
Service status (“ready”)
Response Example
{
  "service": "voice",
  "status": "ready"
}

Making Calls

Initiate Outbound Call

Make a voice call to a phone number.
curl "http://localhost:8000/voice/invoke-call?phone=254711XXXYYY"
phone
string
required
Phone number to call (without + prefix)Example: 254711XXXYYY
Implementation
# From routes/voice.py:12-29
@voice_bp.route("/invoke-call", methods=["GET"])
def make_voice_call():
    phone = "+" + request.args.get("phone", "").strip()

    print(f"📞 Request to call: {phone}")
    if not phone:
        return {"error": "Missing 'phone' query parameter"}, 400

    try:
        response = make_call(phone)
        return {"message": f"Call initiated to {phone}", "response": response}
    except Exception as e:
        return {"error": str(e)}, 500
Response Example
{
  "message": "Call initiated to +254711XXXYYY",
  "response": {
    "sessionId": "ATVId_...",
    "status": "Queued"
  }
}
Error Response
{
  "error": "Missing 'phone' query parameter"
}

Call Instructions

Provide Voice Instructions

This endpoint is called by Africa’s Talking when a call is answered. Return XML instructions to control the call flow.
curl -X POST http://localhost:8000/voice/instruct \
  -d "sessionId=ATVId_123" \
  -d "callerNumber=%2B254711000111" \
  -d "destinationNumber=%2B254711XXXYYY"
sessionId
string
required
Unique identifier for the call session
callerNumber
string
required
Phone number of the person who initiated the call
destinationNumber
string
required
Phone number being called
Implementation
# From routes/voice.py:32-56
@voice_bp.route("/instruct", methods=["POST"])
def voice_instruct():
    session_id = request.values.get("sessionId")
    caller_number = request.values.get("callerNumber")
    destination_number = request.values.get("destinationNumber")

    print(
        f"📞 Call answered. Session: {session_id}, Caller: {caller_number}, "
        f"Destination: {destination_number}"
    )

    # Example instructions: Greet the caller and end the call
    response = '<?xml version="1.0" encoding="UTF-8"?>'
    response += "<Response>"
    response += (
        "<Say>Welcome to the service. This is a demo voice application. Goodbye.</Say>"
    )
    response += "</Response>"

    return Response(response, mimetype="text/plain")
Response Example
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Welcome to the service. This is a demo voice application. Goodbye.</Say>
</Response>

XML Instructions Reference

You can use various XML elements to control the call:
Convert text to speech and play it to the caller.
<Say voice="woman" playBeep="true">
  Welcome to our service. Please hold.
</Say>
Attributes:
  • voice: “man” or “woman” (default: “woman”)
  • playBeep: Play a beep before speaking (default: false)
Play an audio file from a URL.
<Play url="https://example.com/audio.mp3" />
Collect DTMF input from the caller.
<GetDigits timeout="30" finishOnKey="#" callbackUrl="https://example.com/callback">
  <Say>Press 1 for sales, 2 for support</Say>
</GetDigits>
Attributes:
  • timeout: Seconds to wait for input
  • finishOnKey: Key that terminates input (e.g., ”#”)
  • callbackUrl: URL to POST the collected digits
Connect the caller to another phone number.
<Dial phoneNumbers="+254711XXXYYY" record="true" sequential="true" />
Attributes:
  • phoneNumbers: Comma-separated list of numbers to dial
  • record: Record the conversation (default: false)
  • sequential: Try numbers in order vs simultaneously
Record the caller’s voice.
<Record finishOnKey="#" maxLength="60" playBeep="true" 
        callbackUrl="https://example.com/recording" />
Attributes:
  • finishOnKey: Key to stop recording
  • maxLength: Maximum recording duration in seconds
  • playBeep: Play beep before recording
  • callbackUrl: URL to receive the recording URL
Reject an incoming call.
<Reject />

Call Events

Handle Voice Events

Receive real-time notifications about call events (started, answered, ended, failed, etc.).
curl -X POST http://localhost:8000/voice/events \
  -d "sessionId=ATVId_123" \
  -d "eventType=SessionEnded" \
  -d "callerNumber=%2B254711000111" \
  -d "destinationNumber=%2B254711XXXYYY" \
  -d "hangupCause=NORMAL_CLEARING" \
  -d "durationInSeconds=45"
sessionId
string
required
Unique call session identifier
eventType
string
required
Type of event (e.g., “SessionInitiated”, “SessionAnswered”, “SessionEnded”)
callerNumber
string
Caller’s phone number
destinationNumber
string
Destination phone number
hangupCause
string
Reason for call termination (e.g., “NORMAL_CLEARING”, “USER_BUSY”, “NO_ANSWER”)
durationInSeconds
string
Call duration in seconds
amount
string
Cost of the call
currencyCode
string
Currency code (e.g., “KES”)
Implementation
# From routes/voice.py:59-87
@voice_bp.route("/events", methods=["POST"])
def voice_events():
    # Collect all request parameters
    payload = {key: request.values.get(key) for key in request.values.keys()}

    # Log the event
    print("📢 Voice Event Received:")
    for key, value in payload.items():
        print(f"   {key}: {value}")

    # Extract key fields
    session_id = payload.get("sessionId")
    event_type = payload.get("eventType")
    caller = payload.get("callerNumber")
    dest = payload.get("destinationNumber")
    hangup_cause = payload.get("hangupCause")

    print(
        f"➡️ Session {session_id} | EventType={event_type} | Caller={caller} | "
        f"Dest={dest} | HangupCause={hangup_cause}"
    )

    # Always respond with "OK" (200) so AT knows we received it
    return Response("OK", status=200)
Event Types

SessionInitiated

Call has been initiated

SessionAnswered

Call has been answered

SessionEnded

Call has ended

SessionFailed

Call failed to connect
Response
  • Status: 200 OK
  • Body: "OK"

Complete Call Flow Example

1

Initiate Call

Make a GET request to /voice/invoke-call with the phone number
2

Receive Session ID

Africa’s Talking returns a session ID and queues the call
3

Call Answered

When answered, AT calls your /voice/instruct endpoint
4

Provide Instructions

Your endpoint returns XML with voice instructions
5

Track Events

AT sends real-time events to your /voice/events endpoint
6

Call Ends

Receive SessionEnded event with call details and cost

Next Steps

SMS Service

Send SMS messages and handle delivery reports

USSD Service

Build interactive USSD menus

Build docs developers (and LLMs) love