> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroruntime.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# DTMF Events

> Listen for caller key presses during a call to capture input and drive IVR flows.

DTMF (Dual-Tone Multi-Frequency) events occur when a caller presses keys (0–9, \*, #) during a call. Agents can listen for these events to capture input and respond immediately.

<Note>
  This page covers handling DTMF from your **agent code**. For the SIP gateway setup and the raw `DTMF_EVENT` payload, see [DTMF Events](/telephony/managing-calls/dtmf-events) in the Telephony platform docs.
</Note>

## Features

* Detect key presses during a call session.
* Deliver events in real time to the agent.
* Handle events with a user-defined callback.
* Trigger actions or IVR flows based on the input.

## Activation

DTMF detection is enabled on the Inbound SIP gateway, in one of two ways.

<Tabs>
  <Tab title="Via Dashboard">
    When creating an Inbound SIP gateway in the Zero Runtime dashboard, enable the `DTMF` option.

    <Frame>
      <img src="https://assets.videosdk.live/images/DTMF-events.png" alt="DTMF events" />
    </Frame>
  </Tab>

  <Tab title="Via API">
    Set `enableDtmf` to `true` when creating or updating a SIP gateway.

    ```bash theme={null}
    curl -H 'Authorization: $YOUR_TOKEN' \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "Twilio Inbound Gateway",
        "enableDtmf": "true",
        "numbers": ["+0123456789"]
      }' \
      -XPOST https://api.videosdk.live/v2/sip/inbound-gateways
    ```
  </Tab>
</Tabs>

Once the gateway has DTMF enabled, implement the handler as shown below.

## Setup

Put a `DTMFHandler` on the `Pipeline` so keypad tones are delivered rather than dropped, then define an `on_dtmf` method on the agent. Once the session is started with `zeroruntime.invoke()`, the runtime calls it for each keypress.

<CodeGroup>
  ```python title="Python" Python theme={null}
  from zeroruntime import Agent, DTMFHandler, Pipeline
  from zeroruntime.plugins import DeepgramSTT, GoogleLLM, CartesiaTTS
  from zeroruntime.inference import AICousticsDenoise, TurnDetector

  pipeline = Pipeline(
      stt=DeepgramSTT(),
      llm=GoogleLLM(),
      tts=CartesiaTTS(),
      turn_detector=TurnDetector(model="echo-large"),
      denoise=AICousticsDenoise(model_id="quail-vf-2.2-l-16khz"),
      dtmf_handler=DTMFHandler(),   # deliver keypad tones instead of dropping them
  )


  class KeypadAgent(Agent):
      def __init__(self):
          super().__init__(
              agent_id="keypad",
              instructions="You are a phone menu. Ask the caller to press 1 or 2.",
              pipeline=pipeline,
          )
          self.pressed = ""

      async def on_dtmf(self, key: str, payload: dict) -> None:
          """Called once per keypress. A one-argument `on_dtmf(self, key)` works too."""
          if key == "1":
              await self.session.say("Routing you to Sales. How can I help?")
          elif key == "2":
              await self.session.say("Routing you to Support. What issue are you facing?")

          # Multi-digit sequences (a PIN, say) are accumulated by you -- the runtime
          # delivers one key at a time.
          self.pressed = (self.pressed + key)[-4:]
          if self.pressed == "1234":
              await self.session.say("PIN accepted.")
  ```

  ```typescript title="Node JS" Node JS theme={null}
  import { Agent, DTMFHandler, Pipeline } from '@zeroruntime/js-sdk';
  import { DeepgramSTT, GoogleLLM, CartesiaTTS } from '@zeroruntime/js-sdk/plugins';
  import { AICousticsDenoise, TurnDetector } from '@zeroruntime/js-sdk/inference';

  const pipeline = Pipeline({
    stt: DeepgramSTT(),
    llm: GoogleLLM(),
    tts: CartesiaTTS(),
    turn_detector: TurnDetector({ model: 'echo-large' }),
    denoise: AICousticsDenoise({ model_id: 'quail-vf-2.2-l-16khz' }),
    dtmf_handler: DTMFHandler(),  // deliver keypad tones instead of dropping them
  });


  class KeypadAgent extends Agent {
    pressed: string;

    constructor() {
      super({
        agent_id: 'keypad',
        instructions: 'You are a phone menu. Ask the caller to press 1 or 2.',
        pipeline,
      });
      this.pressed = '';
    }

    async on_dtmf(key: string, payload: Record<string, any>) {
      // Called once per keypress. A one-argument `on_dtmf(self, key)` works too.
      if (key === '1') {
        await this.session!.say('Routing you to Sales. How can I help?');
      } else if (key === '2') {
        await this.session!.say('Routing you to Support. What issue are you facing?');
      }

      // Multi-digit sequences (a PIN, say) are accumulated by you -- the runtime
      // delivers one key at a time.
      this.pressed = (this.pressed + key).slice(-4);
      if (this.pressed === '1234') {
        await this.session!.say('PIN accepted.');
      }
    }
  }
  ```
</CodeGroup>

<Note>
  A `DTMFHandler` on the pipeline subscribes the agent to the room's DTMF events—no manual subscription is required. `on_dtmf` is looked up on your agent by name and may take either `(key)` or `(key, payload)`; `DTMFHandler(callback)` routes to a plain function instead. The runtime delivers one key per call, so multi-digit input such as a PIN is accumulated in your own agent state.
</Note>

## What's Next

<CardGroup cols={2}>
  <Card title="Call Transfer" icon="phone-arrow-right" href="/build/telephony/call-transfer">
    Move a live call to another number.
  </Card>

  <Card title="Voice Mail Detection" icon="voicemail" href="/build/telephony/voicemail-detection">
    Handle voicemail on outbound calls.
  </Card>
</CardGroup>
