Skip to main content

Carrier

The carrier delivers the phone call to Patter. Patter supports Twilio and Telnyx. Both share DTMF, call transfer, AMD, status callbacks, and cost tracking; recording is Twilio-only. You configure one carrier per Patter instance by passing an instance to the carrier= keyword argument. Each carrier class falls back to environment variables when constructor arguments are omitted. Each carrier ships as both a flat alias (from getpatter import Twilio) and a namespaced class (from getpatter.carriers import twiliotwilio.Carrier()). They are equivalent.

Twilio

from getpatter import Patter, Twilio

phone = Patter(carrier=Twilio(), phone_number="+15550001234")   # reads env
# Or explicitly:
phone = Patter(
    carrier=Twilio(account_sid="AC...", auth_token="..."),
    phone_number="+15550001234",
)
ParameterTypeDefaultDescription
account_sidstr""Twilio Account SID (starts with AC). Reads from TWILIO_ACCOUNT_SID when empty.
auth_tokenstr""Twilio Auth Token. Reads from TWILIO_AUTH_TOKEN when empty.
Namespaced form:
from getpatter.carriers import twilio

carrier = twilio.Carrier()                            # reads env
carrier = twilio.Carrier(account_sid="AC...", auth_token="...")
On serve(), Patter automatically sets the voice_url on the Twilio number to https://<webhook_url>/webhooks/twilio/voice via the Twilio REST API — no manual Console configuration needed.

Twilio trial account limitations

Twilio trial accounts apply a few platform-level restrictions that affect first-time testing. None of these are Patter limitations — they’re Twilio platform rules:
  1. Verified Caller IDs required for outbound — trial accounts can only call numbers you’ve added under Phone Numbers › Verified Caller IDs in the Twilio Console. Verifying via the CLI is also restricted; do it from the Console.
  2. Trial announcement prepended on outbound calls — Twilio plays an English trial-account message before connecting the call to your agent; the callee has to press a key to continue.
  3. Trial caller-ID restrictions — the caller-ID shown to the recipient may be masked or labelled differently than your purchased number until the account is upgraded.
Upgrading the account in the Twilio Console clears all three. Refer to the Twilio docs (search: “trial account”) for the current exact behaviour — Twilio may change these over time.

Signature verification

The Auth Token is also used to verify every Twilio webhook with HMAC-SHA1 against the X-Twilio-Signature header. Requests with invalid signatures are rejected with HTTP 403.

Telnyx

from getpatter import Patter, Telnyx

phone = Patter(carrier=Telnyx(), phone_number="+15550001234")    # reads env
# Or explicitly:
phone = Patter(
    carrier=Telnyx(
        api_key="KEY...",
        connection_id="2000000000000000000",
        public_key="...",  # optional — enables Ed25519 signature verification
    ),
    phone_number="+15550001234",
)
ParameterTypeDefaultDescription
api_keystr""Telnyx API v2 key. Reads from TELNYX_API_KEY when empty.
connection_idstr""Call Control Application ID. Reads from TELNYX_CONNECTION_ID when empty.
public_keystr""Optional. Ed25519 public key for webhook signature verification. Reads from TELNYX_PUBLIC_KEY when empty.
Namespaced form:
from getpatter.carriers import telnyx

carrier = telnyx.Carrier()                            # reads env

Signature verification

When public_key is set (or TELNYX_PUBLIC_KEY is present), every Telnyx webhook is verified with Ed25519. Requests older than 5 minutes are rejected (replay protection).

Webhook Endpoints

The embedded server exposes these endpoints regardless of carrier choice:
EndpointPurpose
POST /webhooks/twilio/voiceIncoming Twilio call → returns TwiML to start streaming.
POST /webhooks/twilio/statusCall lifecycle status callbacks (initiated, ringing, answered, completed).
POST /webhooks/twilio/recordingRecording completion callbacks.
POST /webhooks/twilio/amdAsync AMD (answering machine detection) results.
POST /webhooks/telnyx/voiceIncoming Telnyx call → returns Call Control commands.

Outbound calls

Use phone.call(...) to place an outbound call on either carrier. Every keyword argument is snake_case:
import asyncio
from getpatter import Patter, Twilio, OpenAIRealtime

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(engine=OpenAIRealtime(), system_prompt="You are a friendly receptionist.")

async def main():
    server = asyncio.create_task(phone.serve(agent, tunnel=True))
    await phone.ready                                   # wait until tunnel + listener are up

    await phone.call(
        to="+15550009876",
        agent=agent,
        first_message="Hi! This is a courtesy call from Acme.",
        machine_detection=True,                          # default since 0.6.2
        ring_timeout=25,                                 # default since 0.6.2
        voicemail_message="Please call us back at +15550001234.",
    )

asyncio.run(main())
Key defaults changed in 0.6.2:
  • machine_detection defaults to True. On Twilio Patter sends MachineDetection=DetectMessageEnd + Async AMD so there is no answer-latency penalty on human pickups. Pass False to skip per-call AMD billing.
  • ring_timeout defaults to 25 seconds. Pass 60 for legacy carrier-default parity, or None to omit the parameter entirely.
  • The AMD callback was renamed on_machineon_machine_detection and now receives a MachineDetectionResult (not a raw dict).
See Local Mode › call() Parameters for the full parameter table.

What’s Next

STT

Speech-to-text providers.

LLM

Language model providers.

TTS

Text-to-speech providers.

Tunneling

Expose your local server publicly.