Back to samples
Vertex logo
Retail

Vertex

Personal style assistant — outfits, sizing, loyalty rewards

4 min read

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

1. Clone and install

Bash
git clone https://github.com/pinnacle-samples/Vertex
cd Vertex
npm install

2. Configure environment

Bash
cp .env.example .env

Fill in the values:

env
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=3000

TEST_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:

Bash
ngrok http 3000

4. Connect the webhook in Pinnacle

  1. Open the Webhooks dashboard.
  2. Add https://<your-tunnel-domain>/webhook and attach it to your RCS agent.
  3. Copy the signing secret into PINNACLE_SIGNING_SECRET. The router calls rcsClient.messages.process(req) which uses this to verify every incoming request.

5. Run it

Bash
npm run dev

Send 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:

  1. RCS button click hits POST /webhook
  2. rcsClient.messages.process verifies the signature
  3. The router decodes the trigger payload and switches on action
  4. The matching method on VertexAgent builds and sends the next rich message

Action handlers

The router dispatches these actions (see router.ts):

ActionWhat it does
showMainMenu / welcomeLanding card with browse + loyalty entry points
showGenderOptionsMen's / Women's gender split
showVibeOptionsOutfit vibes (casual, professional, etc.) for a gender
showItemsItems inside a chosen vibe
selectSizeSize picker for a single item or a full outfit
addItemsToCartAdds picked items at the chosen size
instantCheckoutPaginated cart view
showLoyaltyOptionsLoyalty rewards card carousel
redeemDiscountSpend 400 loyalty points for a flat 10% off
applyCheckoutDiscountApplies a percent discount to the cart
completeCheckoutConfirms the order and clears the cart
clearCartEmpties 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:

TypeScript
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

  1. Add a method to the VertexAgent class in lib/agent.ts
  2. Define a trigger button that references it: JSON.stringify({ action: 'myNewAction', params: {...} })
  3. 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=false and submit your agent for carrier approval
  • Deploy server.ts to any Node host (Railway, Fly, Render, your own box) and point the webhook at the public URL
  • Replace the in-memory customers Map with a real database — Postgres + Prisma is the lowest-friction swap

Resources

© 2026 Pinnacle Software Development, Inc.