> ## 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.

# Starter examples

> Five complete, runnable starter repos that put Orbit to work — OTP sign-in in Next.js, a WhatsApp chatbot, an AI voice agent, transactional SMS notifications, and a two-way SMS autoresponder. Each runs in sandbox in about five minutes.

Each starter is a standalone repo with its own `package.json`, `.env.example`,
README, and runnable code — not a snippet museum. Clone one, add a Test API key,
and you have a working integration to read, run, and adapt. Every starter calls
Orbit's public REST API (`https://api.orbit.devotel.io/api/v1`) or the official
[`@devotel-orbit/node`](/sdks/node) SDK — none of them wire a carrier directly,
so the same code moves from sandbox to production by swapping one API key.

## Run any of them in 5 minutes

1. Create a free account at [orbit.devotel.io/signup](https://orbit.devotel.io/signup).
2. In **Settings > API Keys**, create a **Test** key (prefixed `dv_test_sk_`).
   Sandbox sends are simulated — free, instant, and they never reach a real carrier.
3. `cd` into a starter, copy `.env.example` to `.env`, paste your key, and
   follow that repo's README.

Switch to a live key (`dv_live_sk_`) once your organization passes KYC. The code
does not change.

<Note>
  The starters are being published one by one as individual public GitHub repos.
  Until a repo's page is live, the links below point at its folder in the
  `examples/` directory of the main Orbit repo — the contents are identical and
  complete. Copy the folder into your own project and run it as-is.
</Note>

## OTP sign-in in Next.js 15 (`orbit-otp-nextjs`)

Phone or email one-time passcode (OTP) sign-in — also called 2FA or passwordless
login — in a Next.js 15 App Router application. Orbit generates the code,
delivers it over SMS, WhatsApp, email, or a voice call, enforces expiry, and
checks the user's answer. The app never stores or compares codes itself; the
API key stays server-side in the route handlers.

**Run it**

```bash theme={null}
npm install
cp .env.example .env      # then paste your Orbit API key
npm run dev
```

Open [http://localhost:3000](http://localhost:3000), enter a phone number in E.164 format (for example
`+14155552671`), and you'll receive a code to enter back. With a Test key the
send is simulated and free — check the verification in your Orbit dashboard to
see the sandbox code.

**The smallest call** — send a code with a plain `fetch` (from
`src/lib/orbit.ts`, no SDK required):

```typescript theme={null}
export function sendOtp(to: string, channel: OtpChannel) {
  return orbitPost<VerifySendResult>("/verify/send", {
    to,
    channel,
    code_length: 6,
  });
}
```

Verify is a two-call flow from request to response:

1. `POST /verify/send` with `{ to, channel, code_length }` — the response
   data carries the pending verification:

   ```json theme={null}
   {
     "verification_id": "vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a",
     "status": "pending",
     "channel": "sms",
     "expires_at": "2026-08-24T12:10:00.000Z"
   }
   ```

2. `POST /verify/check` with the `verification_id` and the code the user
   entered (the second helper in `src/lib/orbit.ts`):

   ```typescript theme={null}
   export function checkOtp(verificationId: string, code: string) {
     return orbitPost<VerifyCheckResult>("/verify/check", {
       verification_id: verificationId,
       code,
     });
   }
   ```

   A correct, unexpired code completes the round trip with an approved
   result — a wrong code surfaces in the error envelope with the attempts
   remaining, not a `200`:

   ```json theme={null}
   {
     "verification_id": "vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a",
     "status": "approved",
     "channel": "sms"
   }
   ```

* Docs: [Verify](/verify/overview)
* Repo: [orbit-otp-nextjs](https://github.com/devotel/orbit/tree/main/examples/orbit-otp-nextjs)

## WhatsApp chatbot (`whatsapp-chatbot-starter`)

An inbound WhatsApp bot on the WhatsApp Business Platform that does the two
things every bot needs: receives inbound messages through a signed Orbit
webhook (verified before it acts on anything) and auto-replies based on what
the user wrote. The reply function is a stub — swap it for your own routing, a
knowledge base, or an Orbit AI agent.

**Run it**

```bash theme={null}
npm install
cp .env.example .env      # add your API key and webhook signing secret
npm start
```

The server listens on `POST /webhooks/orbit`. While developing locally, expose
the port with a tunnel (for example `ngrok http 3000`), register the public URL
in **Settings > Webhooks** subscribed to the `message.received` event, and copy
the endpoint's signing secret into `ORBIT_WEBHOOK_SECRET`. Send a WhatsApp
message to your connected number and the bot replies — always inside WhatsApp's
24-hour reply window, since it only answers inbound messages.

**The smallest call** — verify the webhook, then reply:

```javascript theme={null}
event = constructEvent(payload, signature, ORBIT_WEBHOOK_SECRET);
// ...acknowledge the webhook, then:
await orbit.messages.sendWhatsApp({
  to: from,
  type: "text",
  text: { body: replyTo(text) },
});
```

`constructEvent` recomputes the HMAC over the exact bytes Orbit sent and
rejects anything that doesn't match — never act on a webhook you haven't
verified. To message someone first, or after 24 hours of silence, send an
approved template instead.

The full round trip is a webhook in, an API call out:

1. **Inbound** — Orbit delivers a signed `message.received` event. Read the
   raw body, verify the signature, then parse it. The event shape:

   ```json theme={null}
   {
     "type": "message.received",
     "created_at": "2026-08-24T12:00:00.000Z",
     "data": {
       "channel": "whatsapp",
       "from": "+14155552671",
       "to": "+12125559876",
       "body": "hours"
     }
   }
   ```

2. **Outbound** — the bot replies with `messages.sendWhatsApp` (shown above)
   and gets back the queued message:

   ```json theme={null}
   {
     "id": "msg_9a2f7c1e4b8d4e3a9f1b6c2d",
     "channel": "whatsapp",
     "status": "sent"
   }
   ```

* Docs: [WhatsApp](/channels/whatsapp)
* Repo: [whatsapp-chatbot-starter](https://github.com/devotel/orbit/tree/main/examples/whatsapp-chatbot-starter)

## AI voice agent (`ai-voice-agent-quickstart`)

Create, deploy, and dial a conversational AI voice agent — a bot that answers a
phone call, understands what the caller says, and talks back. One script does
all four steps: it creates an agent from a plain-English persona
(`system_prompt`), deploys it, tests it over text (instant and free, no call
needed), and optionally places a real call so you can hear it answer.

**Run it**

```bash theme={null}
npm install
cp .env.example .env      # add your Orbit API key
npm start
```

With just an API key (a Test key works) you'll see the agent created, deployed,
and answering a text question. To hear it on a real call, set `VOICE_FROM` (a
voice-capable Orbit number) and `VOICE_TO` (the phone to ring) in `.env` — live
calls require an approved (KYC'd) account.

**The smallest call** — create, deploy, and dial:

```javascript theme={null}
const { data: agent } = await orbit.agents.create({
  name: "Orbit Quickstart Voice Agent",
  system_prompt: "You are Robin, the friendly voice receptionist for Acme Inc. ...",
});
await orbit.agents.deploy(agent.id);
const { data: call } = await orbit.voice.calls.create({
  to: VOICE_TO,
  from: VOICE_FROM,
  agent_id: agent.id,
});
```

Orbit connects the agent to the call leg end to end — your code never touches a
carrier or media server. Subscribe to the live transcript with
`voice.calls.streamTranscript(callId, handlers)` to act on what's said in real
time.

The script's three API calls, with what each returns:

1. `agents.create` — the persona in, the new agent out:

   ```javascript theme={null}
   const { data: agent } = await orbit.agents.create({
     name: "Orbit Quickstart Voice Agent",
     system_prompt: "You are Robin, the friendly voice receptionist for Acme Inc. ...",
   });
   // → { id: "agt_7e3b9c1f...", status: "draft", ... }
   ```

2. `agents.deploy(agent.id)` — flips the agent's status to `active` so it can
   take live interactions.

3. `voice.calls.create` — place the call the agent answers:

   ```javascript theme={null}
   const { data: call } = await orbit.voice.calls.create({
     to: VOICE_TO,
     from: VOICE_FROM,
     agent_id: agent.id,
   });
   // → { id: "call_4f1a8d2c...", status: "ringing", ... }
   ```

* Docs: [Voice quickstart](/voice/quickstart)
* Repo: [ai-voice-agent-quickstart](https://github.com/devotel/orbit/tree/main/examples/ai-voice-agent-quickstart)

## Transactional SMS notifications (`orbit-sms-notifications-node`)

Send transactional SMS — order updates, shipping alerts, appointment reminders
— and track whether each one was delivered. Two small scripts: `src/send.js`
sends one SMS from the command line, and `src/webhook-server.js` receives
signed delivery-status webhooks and logs each transition (`sent`, then
`delivered` or `failed`).

**Run it**

```bash theme={null}
npm install
cp .env.example .env      # add your Orbit API key
npm run send -- "+14155552671" "Your Acme order #1024 has shipped."
```

With a Test key the send is simulated and free; the response includes the
message id and its current status. To track delivery, run `npm run webhook`,
expose it with a tunnel, and register the public URL in **Settings > Webhooks**
subscribed to the `message.*` events. Replace the `console.log` with your own
logic — mark an order notified, retry on `failed`, or update a dashboard.

**The smallest call** — send one SMS:

```javascript theme={null}
const { data: message } = await orbit.messages.sendSms({
  to,
  body,
});
console.log(`Sent SMS ${message.id} — status: ${message.status}`);
```

The tracking half of the round trip is a signed delivery webhook. With the
webhook server registered for `message.*` events, each status transition
arrives as an event like:

```json theme={null}
{
  "type": "message.delivered",
  "created_at": "2026-08-24T12:00:04.000Z",
  "data": {
    "id": "msg_9a2f7c1e4b8d4e3a9f1b6c2d",
    "channel": "sms",
    "to": "+14155552671"
  }
}
```

Verify the signature the same way the WhatsApp starter does, ack with a `200`,
then act on `event.type` — the script logs each transition; swap the
`console.log` for your own persistence.

To exercise a specific sandbox delivery outcome (delivered, undelivered,
expired, and others) on demand, send to a
[sandbox magic number](/sandbox/magic-numbers). Going live needs an approved
account, and US SMS additionally needs 10DLC brand and campaign registration —
see [Sender ID registration](/compliance/sender-id-registration).

* Docs: [SMS](/channels/sms)
* Repo: [orbit-sms-notifications-node](https://github.com/devotel/orbit/tree/main/examples/orbit-sms-notifications-node)

## Two-way SMS autoresponder (`orbit-sms-autoresponder-node`)

Reply to inbound text messages automatically — a two-way SMS bot that answers
keywords and honors opt-outs. When someone texts your Orbit number, Orbit
delivers a signed `message.received` webhook; the server verifies it, decides
on a reply (`HELP` lists commands, `BALANCE` returns a demo balance, anything
else gets a default prompt), and sends it back. STOP/START keywords confirm
opt-out and opt-in in the app's own logic, on top of the opt-outs carriers and
the platform already enforce.

**Run it**

```bash theme={null}
npm install
cp .env.example .env      # add your API key and webhook signing secret
npm start
```

Expose the port with a tunnel, register the public URL in **Settings >
Webhooks** subscribed to `message.received`, and copy the endpoint's signing
secret into `ORBIT_WEBHOOK_SECRET`. Text your Orbit number and it replies. Edit
the keyword map to match your product — look up an order, hand off to a person,
or route to an Orbit AI agent for open-ended questions.

**The smallest call** — verify the webhook, then answer with
`messages.sendSms`:

```javascript theme={null}
event = constructEvent(payload, signature, ORBIT_WEBHOOK_SECRET);
// ...acknowledge the webhook, then:
await orbit.messages.sendSms({
  to: from,
  body: replyTo(text),
});
```

The inbound half — the `message.received` event Orbit signs and delivers when
someone texts your number:

```json theme={null}
{
  "type": "message.received",
  "created_at": "2026-08-24T12:00:00.000Z",
  "data": {
    "channel": "sms",
    "from": "+14155552671",
    "body": "BALANCE"
  }
}
```

The reply returns the queued outbound message, same shape as the WhatsApp
starter's — `{ id, channel, status }`.

* Docs: [Webhooks](/webhooks/overview)
* Repo: [orbit-sms-autoresponder-node](https://github.com/devotel/orbit/tree/main/examples/orbit-sms-autoresponder-node)

## Next steps

* [Quickstart](/quickstart) — create a key and make your first API call.
* [SDKs](/sdks) — typed clients for Node, Python, Go, and more; the Node SDK
  used by four of the starters is [`@devotel-orbit/node`](/sdks/node).
* [Webhook consumer guide](/guides/webhook-consumer) — retries, idempotency,
  and signature verification pattern used by the webhook-based starters.
* [Go-live checklist](/guides/go-live-checklist) — what changes when you swap a
  Test key for a live one.

All five starters are MIT licensed — copy them into your own project and ship.
