> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getdial.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server.

# Listen service

> Run the Dial listen daemon to capture account events on your machine.

The listen service is a background daemon that keeps a live connection to your account's [event stream](/documentation/platform/stream-account-events) and appends every event — `message.received`, `call.ended`, and more — to a local log as JSON lines. It manages its own subscription token and renews it automatically.

This is the machine-friendly way to capture events: a long-running agent can tail the log, and `dial wait-for` reads from it directly when the daemon is up.

The stream behind the daemon is **presence-based**, not a durable at-least-once queue. If the daemon stops, it **replays missed events on reconnect only within 2 minutes** — longer downtime can drop events. For guaranteed delivery, register an [at-least-once webhook](/documentation/platform/webhooks) — Dial POSTs each event to your HTTPS endpoint, signed and retried.

## Manage the daemon

```bash
dial listen install     # install and start the daemon
dial listen status      # report state and recent events
dial listen uninstall    # stop and remove it
```

`install` registers a user-level service — a **launchd** agent on macOS or a **systemd** user unit on Linux — and prints the unit path. You must be signed in first (`dial auth login`, then `dial auth verify-otp`).

To run the worker in the foreground instead (for debugging), use:

```bash
dial listen
```

## Staying current after CLI updates

On startup the daemon records the version it is running in `~/.local/state/dial/listen-version.v1.json`. About once a minute it compares that against the installed CLI, and when an [update](/documentation/cli/commands#update) lands — manual or automatic — it drains, exits, and lets the supervisor relaunch it on the new code. You never need to restart it after updating.

## Where events land

The daemon appends events to `~/.local/state/dial/listen.log` (honoring `XDG_STATE_HOME`), one JSON object per line. Tail it like any log:

```bash
tail -f ~/.local/state/dial/listen.log
```

## Event payload shapes

Every event shares one envelope — `id`, `object` (`"event"`), `type`, `version`, `createdAt`, `relatedObject` — and a `data` payload whose shape the `type` selects. All field names are camelCase, matching the REST surface.

### `message.received`

```json
{
  "id": "evt_3f9a…",
  "object": "event",
  "type": "message.received",
  "version": 1,
  "createdAt": "2026-05-28T18:32:11Z",
  "relatedObject": { "id": "clxxx", "type": "message", "url": null },
  "data": {
    "messageId": "clxxx",
    "from": "+14155559876",
    "to": "+14155550123",
    "channel": "sms",
    "body": "Your code is 123456",
    "source": "external"
  }
}
```

`data.source` is `"external"` for real carrier-delivered SMS and `"internal"` for messages synthesized by Dial (e.g. dashboard test tools). Gate on `source === "external"` if you only care about real-world traffic.

### `call.ended`

```json
{
  "id": "evt_7c1b…",
  "object": "event",
  "type": "call.ended",
  "version": 1,
  "createdAt": "2026-05-28T18:32:11Z",
  "relatedObject": { "id": "clxxx", "type": "call", "url": "/api/v1/calls/clxxx" },
  "data": {
    "callId": "clxxx",
    "from": "+14155559876",
    "to": "+14155550123",
    "direction": "outbound",
    "durationSeconds": 42,
    "status": "completed",
    "canceled": false,
    "transcriptAvailable": true
  }
}
```

### `call.transcribed`

A *thin* event — it carries only the call id. Fetch the transcript from the call record. Emitted only for calls that produced a transcript, and after the call's `call.ended`.

```json
{
  "id": "evt_9d2e…",
  "object": "event",
  "type": "call.transcribed",
  "version": 1,
  "createdAt": "2026-05-28T18:32:19Z",
  "relatedObject": { "id": "clxxx", "type": "call", "url": "/api/v1/calls/clxxx" },
  "data": { "callId": "clxxx" }
}
```

Fetch the full transcript with `dial call get <callId>` when `data.transcriptAvailable` is `true` on `call.ended`, or on the `call.transcribed` event.

## How it pairs with `wait-for`

[`dial wait-for`](/documentation/cli/commands) consumes events from this log when the daemon is installed and running — so multiple waits share one connection and resolve instantly from already-captured events. If the daemon isn't running, `wait-for` falls back to calling the REST API directly, so it still works either way; the daemon just makes it cheaper and shared.