Retell with Asterisk AGI

I wanted to share a working example of integrating Asterisk with Retell AI using AGI + PJSIP + register-phone-call for inbound PSTN calls.

This took some trial and error, so hopefully this saves someone else time.

Final Working Call Flow

Carrier DID (BulkVS / Telnyx / etc)
→ Asterisk inbound trunk
→ Dialplan matches AI DID
→ AGI script calls Retell register-phone-call API
→ Retell returns call_id
→ Asterisk dials dynamic SIP URI
→ Retell agent answers

Important Discovery

For PJSIP dynamic SIP URI dialing, the correct syntax is:

PJSIP/<endpoint>/sip:user@domain

This matches official Asterisk community guidance. (Asterisk Community)

So in this use case:

Dial(PJSIP/retell-direct/sip:${RETELL_CALL_ID}@sip.retellai.com,60)

That was the breakthrough.


Example pjsip.conf

[transport-tcp]
type=transport
protocol=tcp
bind=0.0.0.0:5060

[retell-direct]
type=endpoint
transport=transport-tcp
disallow=all
allow=ulaw
aors=retell-direct
direct_media=no
rewrite_contact=yes
rtp_symmetric=yes
force_rport=yes
from_domain=sip.retellai.com
outbound_proxy=sip:sip.retellai.com\;lr

[retell-direct]
type=aor
max_contacts=1
contact=sip:sip.retellai.com:5060
qualify_frequency=60


Example extensions.conf

[from-carrier]

exten => +1XXXXXXXXXX,1,NoOp(Inbound call to Retell AI DID)
 same => n,Set(CALLER_E164=${IF($["${CALLERID(num):0:1}" = "+"]?${CALLERID(num)}:+1${CALLERID(num)})})
 same => n,Set(DID_E164=+1XXXXXXXXXX)

 same => n,AGI(retell_register.py,${CALLER_E164},${DID_E164})

 same => n,NoOp(RETELL_CALL_ID=${RETELL_CALL_ID})
 same => n,NoOp(RETELL_ERROR=${RETELL_ERROR})

 same => n,GotoIf($["${RETELL_CALL_ID}"=""]?fail)

 same => n,Dial(PJSIP/retell-direct/sip:${RETELL_CALL_ID}@sip.retellai.com,60)
 same => n,Hangup()

 same => n(fail),NoOp(Retell registration failed: ${RETELL_ERROR})
 same => n,Playback(sorry-cant-let-you-do-that)
 same => n,Hangup()


Example AGI Script (retell_register.py)

#!/usr/bin/env python3
import sys
import json
import urllib.request
import urllib.error

RETELL_API_KEY = "rtk_xxxxxxxxxxxxxxxxx"
RETELL_AGENT_ID = "agent_xxxxxxxxxxxxx"

def read_agi():
    while True:
        line = sys.stdin.readline().strip()
        if line == "":
            break

def agi_cmd(cmd):
    sys.stdout.write(cmd + "\n")
    sys.stdout.flush()
    return sys.stdin.readline()

def setvar(k,v):
    safe = str(v).replace('"','\\"')
    agi_cmd(f'SET VARIABLE {k} "{safe}"')

def main():
    read_agi()

    from_number = sys.argv[1]
    to_number   = sys.argv[2]

    payload = {
        "agent_id": RETELL_AGENT_ID,
        "from_number": from_number,
        "to_number": to_number,
        "direction": "inbound"
    }

    req = urllib.request.Request(
        "https://api.retellai.com/v2/register-phone-call",
        data=json.dumps(payload).encode(),
        headers={
            "Authorization": f"Bearer {RETELL_API_KEY}",
            "Content-Type": "application/json"
        },
        method="POST"
    )

    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            data = json.loads(r.read().decode())

        setvar("RETELL_CALL_ID", data["call_id"])
        setvar("RETELL_ERROR", "")

    except Exception as e:
        setvar("RETELL_CALL_ID", "")
        setvar("RETELL_ERROR", str(e))

if __name__ == "__main__":
    main()

Make executable:

chmod +x /var/lib/asterisk/agi-bin/retell_register.py


Notes

1. Use REST API Key

Use the Retell REST API key, not webhook key.

2. Agent ID != Phone Number

Use:

agent_xxxxxxxxx

not the imported DID number.

3. DNS Check

Make sure your server resolves:

getent hosts sip.retellai.com

4. Firewall

Allow outbound SIP/RTP traffic.

5. Codec

I used:

allow=ulaw


Final Result

Inbound PSTN calls now route:

Carrier DID → Asterisk → Retell AI agent

with full control of routing in Asterisk.

This lets you do:

  • route only specific DIDs to AI

  • time-of-day routing

  • failover to queues/extensions

  • blend human + AI call flows

  • use your own carriers

Hope this helps someone.

Thanks to the Asterisk community thread that clarified the correct PJSIP URI format. (Asterisk Community)