This message was deleted.
# ask-ai
s
This message was deleted.
✅ 1
t
Thanks for reaching out, Aleh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I reviewed your code and the issue you described. Below are focused answers and a working pattern to get DTMF collection working. Acknowledgement • You’re asking about why DTMF digits never arrive and how to correctly collect DTMF from an outbound SIP participant using livekit-agents v1.3.x. Answers 1) Correct event name • Use "dtmf_received" (not "sip_dtmf_received"). The room emits
dtmf_received
when a SIP participant presses keys. 2) Where to listen • Register the listener on the Room (e.g.,
get_job_context().room.on("dtmf_received", handler)
). DTMF is a room-level control event, not a media track event. 3) SIP trunk configuration required • DTMF must be transported by your SIP provider using RFC 2833 (RTP events) or SIP INFO. RFC 2833 is preferred. If the trunk only sends in-band audio DTMF, LiveKit will not detect it reliably. 4) Complete working pattern (concise example) • Key points: register the room listener early, push received digits into an asyncio.Queue, then await collection with timeout. Example implementation:
Copy code
import asyncio
from typing import Optional
from livekit import rtc
from livekit.agents import get_job_context

class DtmfCollector:
    def __init__(self):
        self._queue: asyncio.Queue[str] = asyncio.Queue()
        self._listener_added = False

    def on_digit(self, ev: rtc.Dtmf) -> None:
        # called by Room event; push digit into async queue thread-safely
        loop = asyncio.get_event_loop()
        loop.call_soon_threadsafe(self._queue.put_nowait, ev.code)

    async def ensure_listener(self):
        if self._listener_added:
            return
        room = get_job_context().room
        room.on("dtmf_received", self.on_digit)
        self._listener_added = True

    def clear(self):
        while not self._queue.empty():
            try:
                self._queue.get_nowait()
            except asyncio.QueueEmpty:
                break

    async def collect(self, max_digits: int = 16, timeout: float = 60.0) -> Optional[str]:
        digits = ""
        try:
            while len(digits) < max_digits:
                ch = await asyncio.wait_for(self._queue.get(), timeout=timeout)
                digits += ch
            return digits
        except asyncio.TimeoutError:
            return digits if digits else None
Usage in your agent (ensure listener registered early):
Copy code
class PaymentAgent:
    def __init__(self, ctx):
        self.ctx = ctx
        self._dtmf = DtmfCollector()

    async def on_enter(self):
        await self._dtmf.ensure_listener()

    async def collect_card_number(self) -> str | None:
        self._dtmf.clear()
        digits = await self._dtmf.collect(max_digits=16, timeout=60.0)
        return digits  # None if timed out, otherwise the collected digits
Troubleshooting checklist • Replace
"sip_dtmf_received"
with
"dtmf_received"
. • Add a temporary debug listener to confirm events:
Copy code
get_job_context().room.on("dtmf_received", lambda ev: print("DTMF:", ev.code))
• Confirm SIP trunk supports RFC 2833 or SIP INFO with your provider. • Register the listener before the call connects to avoid race conditions. If you’d like, share a short log snippet (or confirm the trunk transport) and I’ll help narrow the next step. Sources: send_dtmf_events | LiveKit Documentation | GetDtmfTask | LiveKit Documentation, with interactive elements
b
Searched for files matching
**/requirements*.txt
Searched for files matching
pyproject.toml
Read [](file:///Users/deadsage14235icloud.com/Documents/scnsoft_workflow/rfp-kapitalbank-voice-ai/other_agents/dept_collection/pyproject.toml) Versions: -
livekit
(rtc): 1.0.23 - `livekit-agents`: 1.3.12 - `livekit-plugins-noise-cancellation`: 0.2.5 --- Question for the LiveKit agent:
I'm using
livekit==1.0.23
(the Python RTC SDK) and
livekit-agents==1.3.12
.
I need to collect DTMF digits entered via phone keypad from a SIP caller. I register a listener like this:
```python
get_job_context().room.on("sip_dtmf_received", self._handler)
```
with the handler:
```python
def _handler(self, ev: rtc.SipDTMF) -> None:
asyncio.get_running_loop().call_soon_threadsafe(self._queue.put_nowait, ev.digit)
```
Questions:
1. In
livekit==1.0.23
, is the correct event name
"sip_dtmf_received"
or something else? Please confirm the exact string.
2. Is the event object type
rtc.SipDTMF
? Does it have
.digit
(str) and
.code
(int) attributes?
3. Is there any breaking change between
livekit
0.x and 1.x that renamed this event?
4. Does
livekit-agents==1.3.12
have a built-in high-level DTMF collection API (e.g.
GetDtmfTask
) or must I use the raw room event?
5. Are there any known issues with
sip_dtmf_received
not firing when using
get_job_context().room
inside an
AgentSession
-based app?
t
Thanks — I’ll answer each of your numbered questions directly and concisely, based on the versions you listed (livekit 1.0.23 / livekit-agents 1.3.12). 1) Exact event name in livekit 1.0.23 • Use "dtmf_received". "sip_dtmf_received" is not the correct event name. 2) Event object type and attributes • The event payload uses the DTMF event type (commonly exposed as
rtc.Dtmf
/
rtc.DtmfEvent
in handlers). The digit character is available as a string code (e.g.,
"0".."9"
,
"*"
,
"#"
). Do not rely on a
.digit
attribute; use the
.code
/
.value
string field provided by the DTMF event object in the SDK you have. 3) Breaking change between 0.x and 1.x renaming this event • There is no rename to "sip_dtmf_received"; the correct room-level event is
"dtmf_received"
in the 1.x SDK. Treat
"dtmf_received"
as the stable event name. 4) High-level DTMF API in livekit-agents 1.3.12 • livekit-agents v1.3.x does not require you to use a raw room event if you prefer the prebuilt helper. There is a higher-level DTMF collection pattern / helper classes (examples such as a DTMF collection task exist in later/related prebuilt task sets). If you want a simple approach right now, register the room
"dtmf_received"
handler and push digits into an asyncio queue (example pattern I shared earlier). If you’d like, I can give the exact import/class name for the prebuilt task in your agent version — do you prefer a prebuilt task or the lightweight queue-based pattern? 5) Known issues with event not firing inside AgentSession / get_job_context().room • No SDK-level bug that requires using a different object: listening on
get_job_context().room
is correct. Common causes for not receiving events are: ◦ Using the wrong event name (
"sip_dtmf_received"
). ◦ Registering the listener too late (after the SIP participant's DTMF was sent). Register early (e.g., in
on_enter
/ agent startup). ◦ SIP trunk does not send DTMF via RFC 2833 or SIP INFO (in-band audio won't be detected). ◦ Race conditions or incorrect handler signature (ensure handler expects the DTMF event object and reads its
.code
string). If you want, share which field names appear on the DTMF object in your runtime (e.g., print the whole event object with a debug handler), or tell me whether you want the prebuilt GetDtmfTask sample or the minimal queue-based code — I’ll provide the exact code for your choice. , with interactive elements
b
There's a conflict between what the LiveKit agent says and what we found in source. Let me check the exact source for
livekit==1.0.23
directly. Used tool: WebFetch Used tool: WebFetch Used tool: WebFetch The source code is definitive. The LiveKit chat agent was wrong both times. From
room.py
(current main branch):
Copy code
python
@dataclass
class SipDTMF:
    code: int   # numeric DTMF code
    digit: str  # "0"-"9", "*", "#"
    participant: RemoteParticipant | None = None

self.emit("sip_dtmf_received", SipDTMF(...))
Our current code is correct: - Event:
"sip_dtmf_received"
✅ - Attribute:
ev.digit
(str) ✅ - Type:
rtc.SipDTMF
✅ The LiveKit chat agent gave wrong answers twice — first saying
"dtmf_received"
+
ev.code
, then doubling down. The SDK source code in
room.py
is the ground truth. Don't ask that agent again about this — trust the source. If DTMF still isn't firing after rebuild, the remaining suspect is the SIP trunk not sending RFC 2833 — that's a trunk configuration issue, not a code issue.
t
Thanks — good catch and thanks for verifying the SDK source. You are correct: • The event emitted by
room.py
is
"sip_dtmf_received"
. • The event type is
rtc.SipDTMF
with
code: int
,
digit: str
, and optional
participant
. • Your handler and registration (
get_job_context().room.on("sip_dtmf_received", ...)
and using
ev.digit
) are correct. Apology: I’m sorry for the earlier incorrect guidance. Next steps (most likely causes and checks) • Confirm your SIP trunk is sending DTMF via RFC 2833 (RTP events). If the trunk uses in-band audio only, LiveKit will not emit DTMF events reliably. RFC 2833 is preferred; SIP INFO can work if supported by the trunk and LiveKit endpoint. • Add a temporary debug listener to log full events early (e.g., in on_enter) to ensure listener registration happens before the participant sends digits:
Copy code
room = get_job_context().room
  room.on("sip_dtmf_received", lambda ev: print("DTMF event:", ev))
• Verify the SIP call is fully established and that digits are pressed after the call is connected (DTMF sent before join may be lost). • If possible, ask your SIP provider to confirm DTMF transport is RFC 2833 for that trunk/number, or run a SIP trace to confirm RTP events for DTMF. If you want, share a short debug log of the room events or the SIP trunk configuration and I’ll help interpret it. Sources: send_dtmf_events | LiveKit Documentation | Agents telephony integration | LiveKit Documentation, with interactive elements