# Phone

> Connecting phone calls to voicast with Twilio Media Streams: TwiML, cloud PBX and contact-center systems, and handing calls off to a person.

Source: https://voicast.jp/en/docs/phone

You own the phone number and the carrier (Twilio, for example). Partway through an incoming call, return `<Connect><Stream>` TwiML and the call audio streams to the voicast voice server, where the AI takes the call. When the AI finishes, the audio stream closes and Twilio requests your `action` URL; there you look at the outcome and decide whether to transfer the call to a person or hang up.

```
Incoming call → your TwiML (<Connect><Stream>) → the voicast AI talks
             → the AI finishes → Twilio requests action → you return <Dial> or <Hangup/>
```

## TwiML

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Connect action="https://saas.example.com/twilio/after">
    <Stream url="wss://voice.voicast.jp/ws">
      <Parameter name="tenant" value="ten_…" />
      <Parameter name="secret" value="vts_…" />
      <Parameter name="from" value="+819012345678" />
    </Stream>
  </Connect>
</Response>
```

| Parameter | Required | Description |
| --- | --- | --- |
| `tenant` | yes | Tenant ID (`ten_…`) |
| `secret` | yes | The tenant's connection secret (`vts_…`, returned when the tenant was created) |
| `agent` | | Agent ID (`agt_…`). Defaults to the tenant's agent |
| `from` | | The caller's number. Stored as the call's `from` and available to [tools](https://voicast.jp/en/docs/tools.md) and connections to look up the customer |

Twilio sends `<Parameter>` values as they are, so fill in `from` and the rest when your server builds the TwiML.

## Node.js example

Set the number's "A call comes in" webhook to `https://saas.example.com/twilio/voice`.

```js
import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: false }));

const xml = (s) => String(s).replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);

// Incoming call: find your customer and its voicast tenant from the dialed number (To)
app.post('/twilio/voice', async (req, res) => {
  const t = await findTenantByNumber(req.body.To); // { voicastTenantId, voicastSecret }
  res.type('text/xml').send(`<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Connect action="https://saas.example.com/twilio/after">
    <Stream url="wss://voice.voicast.jp/ws">
      <Parameter name="tenant" value="${xml(t.voicastTenantId)}" />
      <Parameter name="secret" value="${xml(t.voicastSecret)}" />
      <Parameter name="from" value="${xml(req.body.From)}" />
    </Stream>
  </Connect>
</Response>`);
});
```

## Handoff

When the AI ends the conversation, voicast writes the outcome before it closes the audio stream. Twilio then requests the `<Connect>` `action` with the `CallSid`, so look the call up with `GET /v1/calls/by-provider/{CallSid}` and return the next TwiML.

```js
// After the AI: transfer to a person or hang up, depending on the outcome
app.post('/twilio/after', async (req, res) => {
  const r = await fetch(`https://api.voicast.jp/v1/calls/by-provider/${req.body.CallSid}`, {
    headers: { Authorization: `Bearer ${process.env.VOICAST_API_KEY}` },
  });
  const call = r.ok ? await r.json() : null;
  // No record (the stream was refused), a handoff, or a failure: connect a person
  const toHuman = !call || call.outcome === 'handoff' || call.outcome === 'error';
  res.type('text/xml').send(
    toHuman
      ? '<Response><Say language="ja-JP">担当者におつなぎします。</Say><Dial>+81312345678</Dial></Response>'
      : '<Response><Hangup/></Response>',
  );
});
```

| `outcome` | What to do next, for example |
| --- | --- |
| `handoff` | `<Dial>` staff or a ring group |
| `completed`, `answered` | `<Hangup/>` (call back using the call's `fields` and summary) |
| `error` | Transfer to a person (the AI was cut off) |
| `hangup` | Nothing (the caller has already hung up) |
| No record (404) | Transfer to a person (see "Refused calls" below) |

The reason for the handoff is in `reason`, and whatever was collected so far is in `fields`. Show them to the person taking over so the caller is not asked again. The `call.handoff_requested` webhook carries the same information, but webhook delivery order is not guaranteed, so base the transfer on the call you look up in `action`.

## Refused calls

In these cases the voice server closes the stream immediately and no call record is created. Twilio moves on to `action`, where the example above transfers to a person.

- `tenant` or `secret` is wrong, or the tenant is disabled (`status: disabled`)
- New calls are being refused for billing reasons: zero balance, overdue payment or the spending cap ([Pricing and billing](https://voicast.jp/en/docs/pricing.md))

A call that has started is never cut off because the balance runs out.

## Rotating the secret

`POST /v1/tenants/{id}/secret` issues a new connection secret. The old one keeps working for 24 hours, so you have time to update your side.

## Audio and behavior

- Audio is Twilio Media Streams (8 kHz μ-law). voicast never hangs up calls through Twilio's REST API; call control stays with your Twilio account.
- When the caller starts talking, the AI stops mid-sentence and listens.
- If the caller is silent for a while, the AI asks again; if there is still no answer, it ends the call as `hangup`.
- If the audio stream reconnects with the same `CallSid` (for example, after a voice server restart), the same call record is reused.

## Cloud PBX and contact-center systems

A cloud phone system, contact-center system or IVR built on Twilio can hand calls to voicast by returning the same `<Connect><Stream>` at the point where the AI should take over. A convenient pattern is to treat "AI reception" as one IVR step and use `action` to return to your own flow (ring group, voicemail and so on) when it ends.

- You can switch tenants or agents (`agent`) per number or by time of day, for example to let the AI answer only after hours.
- If you cannot use `action`, put the verbs to run next (such as `<Dial>`) after `</Connect>`. When the stream closes, Twilio continues with the next verb.
- To connect by other means than Twilio Media Streams (SIP and so on), contact us.

## Trying it out

Use a tenant in the test environment with a Twilio trial number. Test calls are counted too. To check an agent without placing a call, talking to it from the [Web](https://voicast.jp/en/docs/web.md) is quicker.
