Skip to main content

Things you’ll need

  • An active Daily developer account with API key
  • At least one phone number purchased through Daily (covered below)
  • For local development: ngrok to expose your bot to the Internet

Phone Number Management

Before setting up dial-in or dial-out, you’ll need to purchase phone numbers through Daily.

Daily Phone Numbers Guide

Complete guide to searching, purchasing, and managing phone numbers with Daily’s REST API

Dial-in

Dial-in allows users to call your phone number and connect directly to your Pipecat bot.

How It Works

Here’s the sequence of events when someone calls your Daily phone number:
  1. Daily receives an incoming call to your phone number
  2. Daily calls your webhook endpoint
  3. The webhook creates a Daily room with SIP configuration
  4. The webhook starts your bot with the room details and caller information
  5. The caller is put on hold with music
  6. The bot joins the Daily room and signals readiness
  7. Daily forwards the call to the Daily room
  8. The caller and bot are connected for the conversation

Local Development

The Pipecat development runner provides built-in webhook handling for Daily PSTN dial-in, eliminating the need for a separate webhook server.

1. Run your bot with dial-in support

uv run bot.py -t daily --dialin
This starts a FastAPI server on port 7860 with a /daily-dialin-webhook endpoint.

2. Expose your bot to the internet

ngrok http 7860
Copy the ngrok URL (e.g., https://abc123.ngrok.io).
Use ngrok http 7860 --subdomain your-subdomain for a reusable URL.

3. Configure your Daily phone number

Set your phone number’s room_creation_api webhook to:
https://your-ngrok-url.ngrok.io/daily-dialin-webhook

Daily Dial-in Config API

Instructions for configuring the webhook URL for your phone number
Create pinless dial-in config
curl --location 'https://api.daily.co/v1' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_DAILY_API_KEY' \
--data '{
    "properties": {
        "pinless_dialin": [
            {
                "phone_number": "DAILY_PROVISIONED_NUMBER_HERE",
                "room_creation_api": "https://your-ngrok-url.ngrok.io/daily-dialin-webhook"
            }
        ]
    }
}'
Ensure your DAILY_API_KEY has the phone number associated with it. This is required for the pinlessCallUpdate API call that connects the caller to the room.

Configure your Pipecat bot for dial-in

The bot receives dial-in information through RunnerArguments containing:
  • room_url: Daily room URL for the call
  • token: Daily room token for authentication
  • body: Contains DailyDialinRequest with call details
Bot Entry Point:
bot.py
from pipecat.runner.types import DailyDialinRequest, RunnerArguments
from pipecat.transports.daily import DailyTransport, DailyParams, DailyDialinSettings

async def bot(runner_args: RunnerArguments):
    # Parse dial-in request from runner
    request = DailyDialinRequest.model_validate(runner_args.body)

    # Configure dial-in settings with webhook data
    daily_dialin_settings = DailyDialinSettings(
        call_id=request.dialin_settings.call_id,
        call_domain=request.dialin_settings.call_domain
    )

    # Create transport with dial-in configuration
    transport = DailyTransport(
        runner_args.room_url,
        runner_args.token,
        "Voice Bot",
        DailyParams(
            api_key=request.daily_api_key,
            api_url=request.daily_api_url,
            dialin_settings=daily_dialin_settings,
            audio_in_enabled=True,
            audio_out_enabled=True,
            vad_analyzer=SileroVADAnalyzer(),
        )
    )

    # Your bot setup and pipeline creation here...

if __name__ == "__main__":
    from pipecat.runner.run import main
    main()

Complete Bot Implementation

See the full bot.py with argument handling, transport setup, and pipeline configuration

Customize Your Bot with Caller Information

Use the caller’s phone number to personalize the conversation:
async def bot(runner_args: RunnerArguments):
    # Parse dial-in request
    request = DailyDialinRequest.model_validate(runner_args.body)

    # Get caller's phone number
    caller_phone = request.dialin_settings.From

    # Look up customer information from your database
    customer = await get_customer_by_phone(caller_phone)

    # Customize the system prompt
    messages = [
        {
            "role": "system",
            "content": f"You are a helpful assistant for {customer.name}. "
                      f"Their account status is {customer.status}. "
                      "Keep responses concise and conversational."
        }
    ]

    # Use the customized context in your bot...

Run the Example

Complete Setup Instructions

See the full README with step-by-step setup, environment variables, and troubleshooting tips

Dial-out

Dial-out allows your bot to initiate calls to phone numbers. Unlike dial-in, your bot starts the call rather than waiting for incoming calls.
You must contact Daily to enable dial-out for your account. Submit a support request here.

How It Works

Here’s the sequence of events for dial-out calls:
  1. Your application triggers a dial-out (via API call or user action)
  2. Server creates a Daily room with dial-out capabilities enabled
  3. Bot joins the Daily room and sets up the pipeline
  4. Bot initiates the dial-out call to the target phone number
  5. Daily connects the call to the phone number
  6. The recipient answers and is connected to your bot
  7. The bot handles the conversation with the called party

Set up your server for dial-out

The dial-out server is simpler than dial-in since you’re initiating calls rather than receiving webhooks. Your server needs to:
  1. Create Daily rooms with dial-out enabled
  2. Start bot processes with the target phone number
  3. Handle API requests to trigger outbound calls

Complete Server Implementation

See the full FastAPI server code with room creation and dial-out triggering

Configure your Pipecat bot for dial-out

The dial-out bot receives the target phone number and creates a transport without dial-in settings: Bot Entry Point: The bot() method receives RunnerArguments containing:
  • room_url: Daily room URL for the call
  • token: Daily room token for authentication
  • phone_number: Target phone number to call
Transport Creation: These arguments are used to configure the DailyTransport:
bot.py
from pipecat.transports.daily import DailyTransport, DailyParams

async def bot(args: RunnerArguments):
    # Create transport for dial-out (no dial-in settings needed)
    transport = DailyTransport(
        args.room_url,
        args.token,
        "Voice Bot",
        DailyParams(
            api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
            api_key=os.getenv("DAILY_API_KEY"),
            audio_in_enabled=True,
            audio_out_enabled=True,
            video_out_enabled=False,
            vad_analyzer=SileroVADAnalyzer(),
            transcription_enabled=True,
        )
    )

    # Start the dial-out call
    await transport.start_dialout(args.phone_number)

    # Pass transport to your bot pipeline
    # Your bot setup and pipeline creation here...

Complete Bot Implementation

See the full bot.py with dial-out configuration and pipeline setup

Run the Example

To test dial-out functionality:
  1. Start your server: Run your FastAPI server
  2. Trigger a call: Make an API request to start a dial-out call
  3. Answer your phone: The bot will call the specified number
  4. Talk to your bot: Have a conversation with your AI agent

Complete Setup Instructions

See the full README with step-by-step setup, API usage, and configuration details

Call Transfers

Daily supports cold transfers, allowing you to transfer an active call to another phone number. The bot can initiate a transfer and then leave the call, connecting the caller directly to the transfer destination.

How Call Transfers Work

  1. Bot receives transfer request (via function call or user input)
  2. Bot informs the caller about the transfer
  3. Bot initiates SIP call transfer to the destination number
  4. Daily connects the transfer to the destination
  5. Bot leaves the call (cold transfer)
  6. Caller and destination continue the conversation

Implementing Call Transfers

Call transfers are typically implemented as LLM function calls that your bot can invoke:
bot.py
async def dial_operator(transport: BaseTransport, params: FunctionCallParams):
    """Function the bot can call to transfer to an operator."""
    operator_number = os.getenv("OPERATOR_NUMBER", "+1234567890")

    # Inform the user about the transfer
    content = "I'm transferring you to a supervisor now. Please hold while I connect you."
    message = {"role": "system", "content": content}
    await params.llm.push_frame(LLMMessagesAppendFrame([message], run_llm=True))

    # Execute the SIP call transfer
    transfer_params = {"toEndPoint": operator_number}
    await transport.sip_call_transfer(transfer_params)

Handling Transfer Events

Your bot should handle transfer-related events to manage the call flow:
bot.py
@transport.event_handler("on_dialout_answered")
async def on_dialout_answered(transport, data):
    logger.info(f"Transfer successful: {data}")
    # Cold transfer: bot leaves, caller and operator continue
    await task.queue_frames([EndFrame()])

Complete Transfer Implementation

See the full call transfer example with LLM function calls, event handling, and error management

Deployment

Pipecat Cloud

For production deployment without managing your own infrastructure, use Pipecat Cloud. Pipecat Cloud handles all webhook infrastructure, room creation, and bot scaling automatically.

Self-Hosted Deployment

For self-hosted production deployment, ensure your servers are:
  • Publicly accessible with HTTPS
  • Able to handle concurrent requests
  • Properly configured with your Daily API credentials

Next Steps