Note: the visuals in this demo recording have since been refreshed with sharper brand assets. The conversation flow is identical to what you'll get from a fresh clone.
What's inside
- Pull up upcoming and past flights from one menu
- Boarding pass card with QR, gate, seat, and boarding time
- Request seat upgrades inside the conversation
- Per-user session state for multi-step flows
- Hooks for proactive gate-change and delay alerts
A flight management chatbot that lives inside RCS. Travelers can view their flights, pull up boarding passes, request bid-style seat upgrades, check flight status, cancel flights, and opt in to proactive notifications — without ever opening an app.
This guide walks you from a fresh clone to a working demo on your phone in under 10 minutes.
What you'll build
- A Pinnacle RCS agent that walks travelers through trip management
- A typed Express webhook with per-user session state for multi-step flows
- A boarding pass card with QR, gate, seat, and boarding time
- A bid-for-upgrade flow that takes the user from main menu to confirmation in three taps
- Hooks for proactive flight notifications
Prerequisites
- Node.js 18+
- A Pinnacle account — sign up. Add an RCS test agent for development
- An API key and a webhook signing secret
1. Clone and install
git clone https://github.com/pinnacle-samples/Zenith-Airways
cd Zenith-Airways
npm install2. Configure environment
cp .env.example .envPINNACLE_API_KEY=your_pinnacle_api_key_here
PINNACLE_AGENT_ID=your_agent_id_here
PINNACLE_SIGNING_SECRET=your_signing_secret_here
TEST_MODE=false
PORT=30003. Expose your webhook
ngrok http 30004. Connect the webhook
In the Webhooks dashboard:
- Add
https://<your-tunnel-domain>/webhook - Attach it to your RCS agent
- Copy the signing secret into
PINNACLE_SIGNING_SECRET
5. Run it
npm run devSend MENU or START to your agent. You'll see the Zenith Airways landing card with My Flights, Get Boarding Pass, View Upgrades, and Cancel Flights buttons.
How the pieces fit together
Zenith-Airways/
├── server.ts # Express bootstrap
├── router.ts # /webhook POST — verifies + dispatches
├── lib/
│ ├── rcsClient.ts # PinnacleClient instance
│ ├── baseAgent.ts # Shared send + typing helpers
│ ├── typing.ts # Fire-and-forget typing indicator
│ ├── agent.ts # ZenithAgent — every action handler
│ ├── data.ts # Flights, upgrade options, agentInfo (brand)
│ ├── session.ts # Per-user multi-step session state
│ └── types.ts # Flight, BoardingPass, UpgradeOption
Action handlers
| Action | What it does |
|---|---|
showMainMenu | Landing card with all entry points |
viewMyFlights | Upcoming flights as a card carousel |
getBoardingPass | Boarding pass card with QR, gate, seat |
checkFlightStatus | On-time / delay status for a chosen flight |
viewUpgrades / startBidding | Browse and bid on seat upgrades |
viewCancelFlights / cancelFlight / confirmCancelFlight | Cancellation flow |
enableNotifications / noThanks | Opt in to proactive alerts |
Customize the airline brand
agentInfo in lib/data.ts controls the airline name, tagline, and logo. Swap those fields and the main menu rebrands automatically.
flights is a static array per phone number. In production you'll fan out to your reservation system on demand:
async getFlightsByPhone(from: string): Promise<Flight[]> {
return await reservationApi.lookupByPhone(from);
}Sessions and multi-step flows
lib/session.ts keeps a small Map<string, Session> so the agent remembers what the user was doing across messages — for example, which flight they're upgrading. Replace with Redis when you outgrow a single process.
Going to production
- Set
TEST_MODE=falseand submit your agent for carrier approval - Wire
getFlightsByPhoneto your real reservation system - Add proactive notifications by calling
agent.sendMessage(from, ...)from a cron or queue worker on gate changes / delays
Resources
- Repo: github.com/pinnacle-samples/Zenith-Airways
- Docs: docs.pinnacle.sh
- Dashboard: app.pinnacle.sh/dashboard
- Support: founders@trypinnacle.app

