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
- Browse Men's and Women's collections with rich card carousels
- Pick a size and add a single item or a full outfit to the cart
- Paginated cart view with up to 10 cards per checkout step
- Earn and redeem loyalty points for percent-off discounts
- Order confirmation with order number and shipping ETA
A personal style assistant that lives inside an RCS conversation. Shoppers browse curated outfit collections by gender and vibe, pick a size, add items to a cart, and apply loyalty discounts at checkout.
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 responds to button taps with rich card carousels
- A typed Express webhook with a single trigger router
- An in-memory cart and loyalty points system you can customize per shopper
- A clean separation between transport (router.ts), logic (lib/agent.ts), and data (lib/data.ts)
Prerequisites
- Node.js 18+
- A Pinnacle account — sign up at app.pinnacle.sh
- An RCS agent in your Pinnacle dashboard (a test agent works for development)
- An API key and a webhook signing secret
1. Clone and install
git clone https://github.com/pinnacle-samples/Vertex
cd Vertex
npm install2. Configure environment
cp .env.example .envFill in the values:
PINNACLE_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=3000TEST_MODE=true routes messages through Pinnacle's test sandbox — useful while you're whitelisting numbers and iterating.
3. Expose your webhook
For local development, use a tunnel like ngrok:
ngrok http 30004. Connect the webhook in Pinnacle
- Open the Webhooks dashboard.
- Add
https://<your-tunnel-domain>/webhookand attach it to your RCS agent. - Copy the signing secret into
PINNACLE_SIGNING_SECRET. The router callsrcsClient.messages.process(req)which uses this to verify every incoming request.
5. Run it
npm run devSend MENU or START to your agent from a whitelisted device. You should see the Vertex landing card with Browse outfits and Loyalty points buttons.
How the pieces fit together
Vertex/
├── server.ts # Express bootstrap + graceful shutdown
├── router.ts # /webhook POST — verifies + dispatches actions
├── lib/
│ ├── rcsClient.ts # PinnacleClient instance
│ ├── baseAgent.ts # Shared send + typing helpers
│ ├── typing.ts # Fire-and-forget typing indicator
│ ├── agent.ts # VertexAgent — every action handler lives here
│ ├── data.ts # Outfit catalog, customers, loyalty rewards
│ └── types.ts # Gender / OutfitItem / LoyaltyReward types
Every interaction follows the same path:
- RCS button click hits
POST /webhook rcsClient.messages.processverifies the signature- The router decodes the trigger payload and switches on
action - The matching method on
VertexAgentbuilds and sends the next rich message
Action handlers
The router dispatches these actions (see router.ts):
| Action | What it does |
|---|---|
showMainMenu / welcome | Landing card with browse + loyalty entry points |
showGenderOptions | Men's / Women's gender split |
showVibeOptions | Outfit vibes (casual, professional, etc.) for a gender |
showItems | Items inside a chosen vibe |
selectSize | Size picker for a single item or a full outfit |
addItemsToCart | Adds picked items at the chosen size |
instantCheckout | Paginated cart view |
showLoyaltyOptions | Loyalty rewards card carousel |
redeemDiscount | Spend 400 loyalty points for a flat 10% off |
applyCheckoutDiscount | Applies a percent discount to the cart |
completeCheckout | Confirms the order and clears the cart |
clearCart | Empties the cart |
Customize the catalog
Edit lib/data.ts to swap in your own brand. The outfits map is keyed by gender, then by vibe — add a new vibe like this:
womens: {
evening: {
name: 'Evening Out',
description: 'Statement pieces for nights out',
media: 'https://yourcdn.com/evening.jpg',
items: [
{
name: 'Silk Slip Dress',
price: 189,
image: 'https://yourcdn.com/slip-dress.jpg',
stock: 'In stock',
},
],
},
}The agent picks up the new vibe automatically — showVibeOptions builds cards from whatever's in outfits[gender].
Add a new action
- Add a method to the
VertexAgentclass in lib/agent.ts - Define a trigger button that references it:
JSON.stringify({ action: 'myNewAction', params: {...} }) - Add a
case 'myNewAction':in router.ts that calls the method
That's it — no router boilerplate, no manual signature handling.
Going to production
- Set
TEST_MODE=falseand submit your agent for carrier approval - Deploy
server.tsto any Node host (Railway, Fly, Render, your own box) and point the webhook at the public URL - Replace the in-memory
customersMap with a real database — Postgres + Prisma is the lowest-friction swap
Resources
- Repo: github.com/pinnacle-samples/Vertex
- Docs: docs.pinnacle.sh
- Dashboard: app.pinnacle.sh/dashboard
- Support: founders@trypinnacle.app

