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

# Twilio Integration

> Connect Twilio phone calls to Kallglot

# Twilio Integration

This guide walks you through integrating Kallglot with Twilio for real-time voice translation on phone calls.

## Prerequisites

* A Twilio account with a phone number
* A Kallglot API key
* A web server to receive webhooks

## Architecture

```mermaid theme={null}
graph LR
    subgraph Twilio
        P[Phone Number]
        M[Media Streams]
    end

    subgraph Your App
        W[Webhook Handler]
    end

    subgraph Kallglot
        S[Session]
        WS[WebSocket]
    end

    P -->|Incoming Call| W
    W -->|Create Session| S
    W -->|TwiML Response| M
    M -->|Audio Stream| WS
    WS -->|Translated Audio| M
```

## Setup Steps

### 1. Configure Your Twilio Number

In the Twilio Console:

1. Go to **Phone Numbers** > **Manage** > **Active Numbers**
2. Select your number
3. Under **Voice & Fax**, set:
   * **A Call Comes In**: Webhook
   * **URL**: `https://your-app.com/webhooks/twilio/voice`
   * **Method**: POST

### 2. Create the Webhook Handler

<Note>
  Kallglot does not currently provide official SDKs. The helper functions in these examples, such as `createKallglotSession(...)`, represent ordinary HTTPS calls to the API.
</Note>

```javascript theme={null}
import express from 'express';
import twilio from 'twilio';

const app = express();
const VoiceResponse = twilio.twiml.VoiceResponse;

app.post('/webhooks/twilio/voice', express.urlencoded({ extended: false }), async (req, res) => {
  const { CallSid, From, To, Direction } = req.body;

  try {
    // Create a Kallglot session (telephony selection uses `routing`, not `provider`)
    const session = await createKallglotSession({
      mode: 'bidirectional_translation',
      source_language: 'de',  // Customer's language
      target_language: 'en',  // Agent's language
      routing: {
        phone_number: To
      },
      metadata: {
        twilio_call_sid: CallSid,
        from: From,
        to: To,
        direction: Direction
      }
    });

    // Generate TwiML to connect the call to Kallglot
    const response = new VoiceResponse();

    // Optional: Play a greeting
    response.say({
      language: 'de-DE'
    }, 'Willkommen. Ihr Anruf wird übersetzt.');

    // Connect to Kallglot media stream
    const connect = response.connect();
    connect.stream({
      url: session.stream.url,
      track: 'both_tracks'  // Send both inbound and outbound audio
    }).parameter({
      name: 'token',
      value: session.stream.token
    }).parameter({
      name: 'session_id',
      value: session.id
    });

    res.type('text/xml').send(response.toString());

  } catch (error) {
    console.error('Failed to create session:', error);

    // Fallback: connect to agent without translation
    const response = new VoiceResponse();
    response.say('Translation is temporarily unavailable. Connecting you now.');
    response.dial('+14155550123'); // Your agent's number

    res.type('text/xml').send(response.toString());
  }
});

app.listen(3000);
```

### 3. Handle Session Events

Set up a webhook to receive session events:

```javascript theme={null}
app.post('/webhooks/kallglot', express.json(), async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'session.ended':
      // Session ended - save transcript, trigger analysis
      const { session_id, duration } = event.data;
      console.log(`Session ${session_id} ended after ${duration}s`);

      // Request analysis
      await requestKallglotAnalysis(session_id, {
        analyses: ['sentiment', 'summary', 'action_items']
      });
      break;

    case 'analysis.complete':
      // Analysis ready - store results
      const { results } = event.data;
      await db.calls.update({
        session_id: event.data.session_id,
        sentiment: results.sentiment.label,
        summary: results.summary.brief
      });
      break;
  }

  res.status(200).send('OK');
});
```

## Advanced Configurations

### Outbound Calls

For outbound calls, create the session first, then initiate the call:

```javascript theme={null}
async function makeOutboundCall(customerPhone, agentPhone) {
  // 1. Create Kallglot session
  const session = await createKallglotSession({
    mode: 'bidirectional_translation',
    source_language: 'de',
    target_language: 'en'
  });

  // 2. Initiate outbound call via Twilio
  const call = await twilioClient.calls.create({
    url: `https://your-app.com/webhooks/twilio/outbound?session_id=${session.id}`,
    to: customerPhone,
    from: agentPhone
  });

  return { session, call };
}

// Webhook for outbound calls
app.post('/webhooks/twilio/outbound', async (req, res) => {
  const sessionId = req.query.session_id;
  const session = await retrieveKallglotSession(sessionId);

  const response = new VoiceResponse();
  const connect = response.connect();
  connect.stream({
    url: session.stream.url,
    track: 'both_tracks'
  }).parameter({
    name: 'token',
    value: session.stream.token
  });

  res.type('text/xml').send(response.toString());
});
```

### Conference Calls

For multi-party calls, use Twilio Conferences:

```javascript theme={null}
app.post('/webhooks/twilio/conference', async (req, res) => {
  const { CallSid, ConferenceSid } = req.body;

  // Create session for this participant
  const session = await createKallglotSession({
    mode: 'bidirectional_translation',
    source_language: 'auto',  // Auto-detect each participant's language
    target_language: 'en'
  });

  const response = new VoiceResponse();
  const dial = response.dial();

  // Join conference with Kallglot stream
  dial.conference({
    statusCallback: '/webhooks/twilio/conference-status',
    record: 'record-from-start'
  }, 'TranslatedConference');

  // Start media stream
  response.connect().stream({
    url: session.stream.url
  }).parameter({
    name: 'token',
    value: session.stream.token
  });

  res.type('text/xml').send(response.toString());
});
```

### Dynamic Language Detection

Let Kallglot auto-detect the customer's language:

```javascript theme={null}
const session = await createKallglotSession({
  mode: 'bidirectional_translation',
  source_language: 'auto',  // Auto-detect
  target_language: 'en'
});

// Listen for language detection event
session.on('language_detected', (data) => {
  console.log(`Detected language: ${data.language} (${data.confidence})`);
});
```

## Error Handling

### Handle Session Creation Failures

```javascript theme={null}
app.post('/webhooks/twilio/voice', async (req, res) => {
  try {
    const session = await createKallglotSession({
      // ...
    });
    // Return TwiML with stream
  } catch (error) {
    console.error('Kallglot error:', error);

    const response = new VoiceResponse();

    if (error.code === 'rate_limit_exceeded') {
      response.say('We are experiencing high call volume. Please try again later.');
      response.hangup();
    } else {
      // Fallback to non-translated call
      response.say('Translation service is temporarily unavailable.');
      response.dial('+14155550123');
    }

    res.type('text/xml').send(response.toString());
  }
});
```

### Handle Stream Disconnections

Twilio sends a status callback when the stream disconnects:

```javascript theme={null}
app.post('/webhooks/twilio/stream-status', async (req, res) => {
  const { StreamSid, StreamStatus } = req.body;

  if (StreamStatus === 'stopped') {
    // Stream ended - end the Kallglot session
    const sessionId = await getSessionIdForStream(StreamSid);
    await endKallglotSession(sessionId);
  }

  res.status(200).send('OK');
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use track: 'both_tracks'">
    Always use `track: 'both_tracks'` to send both inbound and outbound audio to Kallglot. This ensures proper speaker separation.
  </Accordion>

  <Accordion title="Handle race conditions">
    The Twilio webhook must respond quickly (within 15 seconds). Create the session and respond immediately:

    ```javascript theme={null}
    // Good: Create session in webhook
    const session = await createKallglotSession({...});
    res.type('text/xml').send(response.toString());

    // Bad: Doing too much in webhook
    const session = await createKallglotSession({...});
    await db.save(session);  // Too slow!
    await analytics.track({...});  // Even slower!
    res.type('text/xml').send(response.toString());
    ```
  </Accordion>

  <Accordion title="Clean up on call end">
    End Kallglot sessions when calls complete to stop billing:

    ```javascript theme={null}
    app.post('/webhooks/twilio/status', async (req, res) => {
      if (req.body.CallStatus === 'completed') {
        const sessionId = await getSessionIdForCall(req.body.CallSid);
        await endKallglotSession(sessionId);
      }
      res.status(200).send('OK');
    });
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

### No audio in translation

1. Verify the stream URL is correct
2. Check that `track: 'both_tracks'` is set
3. Verify the WebSocket connection is established

### High latency

1. Ensure your server is geographically close to Twilio's region
2. Check network latency between your server and Kallglot
3. Consider using Twilio's edge locations

### Echo or feedback

1. Ensure you're not playing translated audio back to the source
2. Check that speaker identification is working correctly
