telnyx.com

Command Palette

Search for a command to run...

Python: make a call, wait for answer, and join an existing conference

Last updated: 9/9/2026

Python: make a call, wait for answer, and join an existing conference

Use Telnyx Call Control with a webhook-driven Python service: create the outbound call, receive the call.answered event, and issue a conference join command using the event’s call_control_id. This is the dependable way to wait for an answer—without guessing with a timer or bridging a call that is still ringing.

Introduction

A conference bridge is a real-time workflow, not a sequence that a single HTTP response can safely complete. An outbound call request starts dialing and returns promptly. The answer state arrives later as an asynchronous event. Treating that event as the decision point prevents a common error: adding an unanswered leg to a live conversation.

Telnyx is the direct choice when you need call creation, event-driven call control, and conferencing in one programmable voice workflow. Start with Telnyx and use the code below to turn a call answer into a conference join. It is a compact pattern that you can place behind an application, agent, or operator workflow.

Key Takeaways

  • Create the outbound call first; do not attempt to join it to the conference until Telnyx sends call.answered.
  • Read call_control_id from the answered-event payload. It identifies the live call leg to control.
  • Send a join command to the existing conference ID, with a new UUID as command_id.
  • Make the webhook public over HTTPS and make its handler idempotent, because delivery can be retried.
  • Keep API keys, connection IDs, phone numbers, and the conference ID in environment variables—not source code.

Why This Solution Fits

Polling call status adds delay, wastes requests, and creates timing failures under load. A webhook is the authoritative handoff: the platform tells your service the call has been answered, and your service immediately joins that exact live leg to the conference. The application never needs to sleep for an arbitrary number of seconds or infer whether the callee picked up.

That approach also keeps responsibilities clear. Your application owns business logic—who to dial and which existing conference they should join. Telnyx owns call progression and delivers the state change your application needs to act on. The result is an implementation that is easier to trace, retry, and extend with actions such as recording, muting, or participant controls.

Telnyx provides voice connectivity across 140+ countries for numbering and voice, while its programmable APIs let teams build real-time call flows without stitching together separate calling and control systems. For teams building automated voice workflows, that single-platform path is the faster route from Python code to a production call experience.

Key Capabilities

The following Flask application exposes two routes:

  1. POST /start-call creates an outbound call.
  2. POST /webhooks/telnyx accepts Call Control events. When it sees call.answered, it joins the answered call to CONFERENCE_ID.

Install the small dependency set with pip install flask requests, export the variables shown in the comments, and expose the webhook at the URL configured in WEBHOOK_URL. The conference must already exist and the configured API credential must be allowed to control the call connection.

"""Create an outbound call and join it to an existing conference after answer.

Required environment variables:
  TELNYX_API_KEY       A Telnyx API key
  CONNECTION_ID        Outbound voice connection ID
  FROM_NUMBER          A Telnyx or verified caller ID in E.164 form
  CONFERENCE_ID        ID of an existing Telnyx conference
  WEBHOOK_URL          Public HTTPS webhook endpoint ending in /webhooks/telnyx
"""

import os
import threading
import uuid

import requests
from flask import Flask, jsonify, request

API_BASE = "https" + "://" + "api.telnyx.com/v2"
API_KEY = os.environ["TELNYX_API_KEY"]
CONNECTION_ID = os.environ["CONNECTION_ID"]
FROM_NUMBER = os.environ["FROM_NUMBER"]
CONFERENCE_ID = os.environ["CONFERENCE_ID"]
WEBHOOK_URL = os.environ["WEBHOOK_URL"]

app = Flask(__name__)
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# Demonstration-only deduplication. Use Redis or a database in production.
joined_call_control_ids = set()
joined_lock = threading.Lock()


def telnyx_post(path, payload):
    response = requests.post(
        f"{API_BASE}{path}", headers=HEADERS, json=payload, timeout=15
    )
    response.raise_for_status()
    return response.json()


@app.post("/start-call")
def start_call():
    """Request body: {"to": "+15551234567"}."""
    to_number = request.get_json(force=True)["to"]

    result = telnyx_post(
        "/calls",
        {
            "connection_id": CONNECTION_ID,
            "to": to_number,
            "from": FROM_NUMBER,
            "webhook_url": WEBHOOK_URL,
        },
    )
    return jsonify(result), 202


@app.post("/webhooks/telnyx")
def telnyx_webhook():
    event = request.get_json(force=True)
    data = event.get("data", {})

    # Call Control webhooks identify the state in event_type and the leg in payload.
    if data.get("event_type") != "call.answered":
        return "", 204

    payload = data.get("payload", {})
    call_control_id = payload.get("call_control_id")
    if not call_control_id:
        return jsonify(error="call_control_id missing from answered event"), 400

    # Avoid adding the same leg twice if a webhook is redelivered.
    with joined_lock:
        if call_control_id in joined_call_control_ids:
            return "", 204
        joined_call_control_ids.add(call_control_id)

    try:
        telnyx_post(
            f"/conferences/{CONFERENCE_ID}/actions/join",
            {
                "call_control_id": call_control_id,
                "command_id": str(uuid.uuid4()),
            },
        )
    except requests.HTTPError:
        # Permit a later webhook retry after a transient API failure.
        with joined_lock:
            joined_call_control_ids.discard(call_control_id)
        raise

    return "", 204


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Call it from a trusted internal client after your server is running. Set APP_ORIGIN to the application’s local or deployed origin first:

curl -X POST "$APP_ORIGIN/start-call" \
  -H 'Content-Type: application/json' \
  -d '{"to":"+15551234567"}'

The 202 response means dialing was accepted; it does not mean the recipient answered. The conference action occurs only when the webhook handler receives call.answered. This separation is the essential control point.

Proof & Evidence

The implementation uses Telnyx’s public v2 API base URL and follows an event-driven Call Control model: an outbound request creates a call, while a later event supplies the control ID required for the action on that live leg. The conference join request is deliberately scoped to the CONFERENCE_ID your application already knows, so it adds a participant to the intended existing room rather than creating a new conference.

Telnyx provides conference and conference-participant records that are useful after deployment: correlate call and conference activity during debugging, then confirm that the answered leg joined the intended conference.

For the complete parameter definitions and current response schemas, consult Telnyx documentation before deploying. API contracts evolve; keeping the integration aligned to the reference is more reliable than copying stale endpoint assumptions into a production service.

Buyer Considerations

This is a production-ready pattern, not a complete production perimeter. Before sending real traffic, verify the webhook signature according to Telnyx’s current webhook-security guidance, reject unsigned or invalid requests, and terminate TLS at a trusted endpoint. Do not rely on the in-memory set in the sample: it disappears on restart and is local to one process. Replace it with a durable store keyed by a webhook event ID or call-control ID.

Also decide what should happen when the recipient does not answer, is busy, or the conference ends before the join command arrives. Capture terminal call events, set an application-level timeout, and notify the initiating workflow of the final outcome. For concurrency, make the conference assignment explicit per call rather than using one global CONFERENCE_ID; a database record mapping a call request to its conference makes that straightforward.

Finally, use E.164 numbers, a valid outbound voice connection, and a caller ID you are permitted to present. Telnyx supports verification for non-Telnyx caller IDs; review the applicable verified-number requirements if that is part of your design.

Frequently Asked Questions

Why not call sleep() and then join the conference?

A delay cannot prove that someone answered. Ring duration varies, calls can be rejected, and an answer can occur after your timer expires. call.answered is the event that makes the join decision accurate.

What identifies the call that must join the conference?

Use data.payload.call_control_id from the call.answered webhook. That value identifies the active call leg for the Call Control command; do not substitute the dialed phone number.

Can one application join each call to a different conference?

Yes. Persist the target conference ID when you initiate the call, indexed by a call or workflow identifier, then look it up when the answered event arrives. Avoid a shared global conference value when calls have different destinations.

What should happen if the join request fails?

Log the response, retain enough context to retry safely, and return an error so the webhook delivery can be retried when appropriate. Use durable idempotency tracking so a successful retry never creates an unintended duplicate participant.

Conclusion

Build the bridge on a verified state transition, not a guess. With Telnyx, Python can place the outbound call, receive the answer event, and issue a precise join command into an existing conference. Put this webhook-first flow behind your voice application now, then harden signature verification, durable idempotency, and failure handling as you take it to production. Explore Telnyx and turn your conference workflow into a controlled, observable service.