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

# Exercise a call intent lifecycle

> A complete local example of idempotent dispatch, authorization, and confirmed hangup status.

Run the call service without a carrier account or phone network. The provider fixture lets you inspect the exact host integration flow before replacing it with a [real adapter](/voice/telephony#adapters).

[Download the complete project](/downloads/telephony.zip), extract it, then run `npm install` and `npm run check`. Use `npm start` to execute the recipe with the requirements below.

## Install and run

Use a supported [Node version](/learn/installation), then:

```bash theme={null}
npm init -y
npm pkg set type=module
npm install @agentium/core@4.0.0
npm install --save-dev typescript tsx @types/node
```

Save this as `telephony.ts`:

```typescript telephony.ts theme={null}
import assert from "node:assert/strict";
import {
  InMemoryCallIntentStore, OutboundCallService, TelephonyError,
  type OutboundCallProvider, type CallStatus,
} from "@agentium/core/telephony";

// A local provider fixture. No HTTP client, credentials, or phone network is used.
let creates = 0;
let status: CallStatus = "queued";
const provider: OutboundCallProvider = {
  id: "fixture", routeId: "demo-v1",
  capabilities: {
    transport: "http", automaticCreateRetry: false,
    callbackVerification: "host", hangup: "call",
  },
  async create() {
    creates++;
    return { ref: { providerId: "fixture", routeId: "demo-v1", callId: "call-1" }, status, providerStatus: status };
  },
  async get(ref) { return { ref, status, providerStatus: status }; },
  async hangup(ref) { status = "cancelled"; return { ref, acknowledged: true }; },
  normalizeVerifiedEvent() { throw new Error("This fixture does not accept webhooks"); },
};
const identity = { tenantId: "demo", userId: "alice" };
const calls = new OutboundCallService({
  providers: [provider], store: new InMemoryCallIntentStore(),
  authorize: ({ identity: caller, request }) => caller.tenantId === "demo" && caller.userId === "alice"
    && request.routeId === "demo-v1" && request.from === "+14155550102" && request.to === "+14155550103",
});
const request = {
  identity, intentId: "appointment-42", routeId: "demo-v1",
  from: "+14155550102", to: "+14155550103",
};
const first = await calls.create(request);
const duplicate = await calls.create(request);
assert.equal(creates, 1);
assert.deepEqual(first.snapshot?.ref, duplicate.snapshot?.ref);
console.log("duplicate intent: one provider create");
await assert.rejects(
  calls.get({ tenantId: "demo", userId: "bob" }, request.intentId),
  (error: unknown) => error instanceof TelephonyError && error.code === "unauthorized",
);
console.log("another user: denied");
await calls.hangup(identity, request.intentId);
const ended = await calls.get(identity, request.intentId);
assert.equal(ended.snapshot?.status, "cancelled");
console.log("confirmed status:", ended.snapshot?.status);
```

```bash theme={null}
npx tsx telephony.ts
```

Expected output:

```text theme={null}
duplicate intent: one provider create
another user: denied
confirmed status: cancelled
```

## What this verifies

The same intent and payload reach the provider's `create` once. A different user cannot read Alice's intent. The application requests hangup and then reads status to establish cancellation; acknowledgement alone is not enough.

This fixture exercises local contracts. It does not establish live carrier interoperability, callback verification, or media delivery.

## Connect a real carrier

Replace `provider` with [Twilio](/telephony/twilio), [Telnyx](/telephony/telnyx), [SignalWire](/telephony/signalwire), [Vonage](/telephony/vonage), [Exotel](/telephony/exotel), or [LiveKit SIP](/telephony/livekit). Use a durable intent store, authenticated identity, and application authorization before exposing an endpoint. With a real provider, `create()` places a real call.

Preserve the business intent ID across retries. An uncertain create must be [reconciled](/telephony/callbacks); generating a new intent can dial twice. The media path is a separate [voice integration](/voice/adapters).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.