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

# The devotel CLI model: auth, config, and the webhook-forwarding loop

> Why Orbit ships a dedicated CLI beyond curl and the per-language SDKs — the four subcommand families and what each owns, the credential resolution chain, and the local webhook-forwarding loop that makes handlers testable before they deploy.

# The devotel CLI model

The `devotel` CLI exists for one loop the SDKs and curl cannot close: **building a webhook handler against live events without deploying anything.** An SDK types request/response calls; curl runs them; neither can push a signed `message.delivered` event into `localhost:3000` while you edit the handler. This page explains the model behind that loop — where the credential lives, how the forwarder subscribes to temporary events, and how the four subcommand families divide the surface. For install and first-run usage, see the [devotel CLI guide](/guides/devotel-cli).

## Why a dedicated CLI exists

curl and the SDKs cover the request/response plane well. The gap is the **inbound** plane: webhooks arrive server-to-server, so a local handler under development is unreachable. The standard workaround — a public tunnel such as ngrok — puts your work-in-progress code on a public URL and adds a credential-managed dependency. A first-party CLI closes that loop client-side: it reads the event history over the API (the same endpoint the dashboard reads) and POSTs each event to your `localhost`, signed with the exact `X-Devotel-Signature` scheme the production dispatcher emits. Your handler code runs unchanged — the same verifier call, the same envelope shape — so "works locally" and "works in production" are the same claim.

## The four subcommand families

Each family owns a plane; none crosses into another's job.

| Family | Commands | Owns |
| - | - | - |
| `auth` | `login`, `logout`, `whoami` | Credential storage — writing, removing, and displaying profiles in the workspace config file. Tokens are printed redacted, always. |
| `send` | `send sms` | One-shot message sends for smoke tests. Outbound SMS terminates server-side through Devotel's wholesale network — the CLI submits the request, nothing else. |
| `listen` | `listen --forward-to <url>` | The webhook-forwarding harness — tails live tenant events and POSTs each one to a local URL, signed like production. |
| `tail` | `logs tail` | The live request-log stream — request lines and delivery receipts (DLRs) as they happen, for watching a send go `queued → delivered`. |

Two read commands (`numbers list`, `sandbox numbers`) and the migration surface (`migrate twilio|telnyx|status`) round out the surface, but the model is the four families above: auth decides *who you are*, send/numbers decide *what you transmit*, and listen/tail decide *what you watch*.

## The auth resolution chain

Every command resolves its credential through one chain, first match wins:

1. **An explicit flag** — `--token <t>` at `auth login` time.
2. **An environment variable** — `DEVOTEL_TOKEN` as the login-time fallback (used in CI where flags would put the key in the argv list).
3. **The workspace config file** — `~/.devotel/config.json`, written by `auth login` with `0600` permissions (owner-read-only). Move it with `DEVOTEL_CONFIG_HOME`; pick a named profile with `--profile` or `DEVOTEL_PROFILE`.

The token format decides the auth scheme the client sends: `dv_…` keys go as `X-API-Key`, anything else as `Authorization: Bearer`. `auth whoami` prints the resolved profile with the token redacted, so it is safe to paste into a chat.

## The webhook-forwarding loop

`devotel listen` is the harness the CLI exists for. A session runs four steps, and all of them are client-side polling plus local POSTs — no new server surface, no public URL:

1. **Open the session.** Mint an ephemeral signing secret (`whsec_…`) unless you pinned one with `--secret` or `DEVOTEL_WEBHOOK_SECRET`. The CLI prints it once, on startup.
2. **Subscribe.** Poll the first-party `GET /events` history endpoint — the same Redis replay buffer the dashboard's event stream reads — tailing forward from a monotonic stream-id cursor. A `--types` allowlist narrows the subscription (mirroring the server's filter semantics: no filter delivers everything).
3. **Forward.** Re-shape each event into the canonical webhook envelope `{ id, type, created_at, data }` and POST it to your local URL with `X-Devotel-Signature: t=<ts>,v1=<hmac>` — byte-for-byte the header the production dispatcher emits. Your handler verifies it with the same SDK verifier call it uses in production.
4. **Terminate on exit.** Ctrl-C ends the poll loop; the ephemeral secret and the cursor die with the process. Nothing was registered server-side, so nothing needs cleanup. `--replay` backfills the buffered events first, then tails.

### One session, end to end

Start the forwarder while your handler runs on the machine:

```bash theme={null}
devotel listen --forward-to localhost:3001/webhook --types message.delivered
# Forwarding Orbit webhooks → http://localhost:3001/webhook
# Webhook signing secret: whsec_8f3c…
# message.delivered  1719345600123-0 → http://localhost:3001/webhook  [500]
# message.delivered  1719345600123-0 → http://localhost:3001/webhook  [200]
```

The first POST hit a bug in your handler and got a `500`. The CLI prints the non-2xx line and keeps the session alive; the platform's own retry schedule (attempt one of ten, on the [delivery-semantics backoff](/concepts/webhook-delivery-semantics)) re-delivers the event, the forwarder sees it again, and this attempt resolves locally once you fix the handler. That is the loop: the retried webhook resolves on your machine, before anything deploys.

## CLI planes vs. the dashboard event pages

Use `listen`/`tail` when you are **developing** — you want events pushed into local code, or a raw stream piped into `jq`. Use the dashboard's event log and webhook debug pages when you are **operating** — historical search across the team, dead-letter inspection and replay, endpoint health. The CLI is the inner loop; the dashboard is the shared record. Neither replaces the other.

## Map to the guide

Install, login, first send, and the flag-by-flag walkthrough live in the [devotel CLI guide](/guides/devotel-cli). This page is the model behind it: read the guide to run the commands, read this page when you need to explain to a teammate why the credential file sits at `~/.devotel`, why the forwarded signature matches production, or why the listen loop was worth a first-party binary.


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