Three repos, one ecosystem
SlabTrack is a constellation: an operator brain (slabtrack), a marketplace where
buyers play games for cards (thisorthat), and a storefront platform where vendors
run their own shops (slabtrack-storefront). Each is an independent Next.js app
with its own database — they coordinate via webhooks, JWT auth, and a shared sale-lock layer.
The mental model
┌──────────────────────────────────────────────────────────────┐
│ slabtrack (brain) │
│ collection · pricing · strategist · bridge │
│ Express + Postgres + React │
└──────────────────────────────────────────────────────────────┘
▼ webhook ingest ▲ engage/disengage
│ (push lots/singles) │ (canonical state)
┌───────────────┴───────────────┐ ┌──────┴────────────────────┐
│ thisorthat (marketplace) │ │ storefront (per-vendor) │
│ Next.js 16 + Postgres │ │ Next.js 16 + Postgres │
│ buyer plays Flip / Pull │ │ buyer direct-buys cards │
│ Stripe direct charges │ │ Stripe Connect (Express) │
│ Shippo labels │ │ Shippo labels │
└───────────────────────────────┘ └──────────────────────────┘
Repo-by-repo
slabtrack Brain
The operator-side dashboard. Catalogs every card you own, prices them, scores them, and routes them to the right channel via the Strategist. Also runs the Command Bridge — the cross-channel awareness layer that knows which card is live where, and the Sync Center that reconciles SlabTrack's view with each satellite.
- Stack: Express 5 + PostgreSQL + React 19 (Vite) + JWT auth
- Owns:
cards,users,card_channels,consignment_*tables - Talks outbound to: ToT (engage/disengage webhooks, comp lookup), Storefront (publish cards)
- Receives inbound from: ToT + Storefront (sync events)
thisorthat Marketplace
The buyer-facing game marketplace. Buyers pick cards, then choose a Flip (50/50 spread) or Pull (mystery stack from 2-5 cards) — the outcome decides their final price, but the card always ships. Provably fair via SHA-256 + Solana on-chain seed anchoring.
- Stack: Next.js 16 (App Router) + PostgreSQL + Prisma + JWT auth (shared with SlabTrack)
- Owns:
Listing,FlipConfig,Pull,Play,Order,CollectionCard - Stripe model: direct charges on the platform account, manual capture for sale-lock safety
- Shipping: Shippo, ship-from from
SHIP_FROM_*env vars (one address per ToT install) - Receives webhook from SlabTrack:
POST /api/webhook/ingest→ stages cards as CollectionCard rows + auto-publishes Listings
slabtrack-storefront Multi-tenant
Vendor storefronts. Each operator gets their own slug at storefront.slabtrack.io/[slug]
with their own theme, logo, accent color, and Stripe Connect account. Direct-buy commerce
(no games) — the buyer adds cards to a cart and checks out via Stripe-hosted Checkout.
- Stack: Next.js 16 (App Router) + PostgreSQL + Prisma + JWT auth (shared with SlabTrack)
- Owns:
Storefront,StorefrontCard,Order,Want,SellOffer - Stripe model: Stripe Connect Express — funds settle directly into operator accounts, platform takes a 3% application fee
- Shipping: Shippo, ship-from per-shop (read from
Storefront.shipFromName/Street1/...) - Receives card publishes from SlabTrack via
POST /api/storefronts/[slug]/cards
Auth — the JWT shared secret
All three repos verify JWTs signed with the same JWT_SECRET. SlabTrack issues
the JWT at login; ToT and Storefront accept it via the handoff route
(/auth/handoff?token=…) which sets a same-origin cookie on the satellite.
If JWT_SECRET drifts between repos, the operator can sign in to SlabTrack but every
handoff to ToT / Storefront fails silently with "auth required." Triple-check the value
in Railway env-var settings on all three services.
Order lifecycle (storefront example)
- Buyer adds cards to cart → POST
/api/stripe/checkouton storefront - Storefront acquires sale-locks for each card across SlabTrack + ToT (so the same card can't be bought twice on different channels)
- Storefront creates pending
Orderrows + Stripe Checkout Session (idempotent) - Buyer pays on Stripe-hosted page
checkout.session.completedwebhook fires → flips Orders topaid+ reservations tosold+ completes the sale-locks (which fan out bulk-disengage to ToT/etc.)- Operator clicks "Print Label" in dashboard →
/api/shippo/buy-label/[orderId]purchases USPS label, stores tracking - Buyer email fires with tracking link
Webhook endpoints (production URLs)
| From | To | Endpoint | Purpose |
|---|---|---|---|
| Stripe | Storefront | storefront.slabtrack.io/api/stripe/webhook | Order paid / refunded / failed |
| Stripe | ToT | thisorthat.slabtrack.io/api/stripe/webhook | Pull/Flip play settlement, refunds |
| SlabTrack | ToT | thisorthat.slabtrack.io/api/webhook/ingest | Engage/disengage, lot/single publish |
| SlabTrack | Storefront | storefront.slabtrack.io/api/storefronts/[slug]/cards | Card publish from SlabTrack |
| ToT | SlabTrack | www.slabtrack.io/api/webhooks/ecosystem-transfer/... | Sale events, bulk disengage |
| Storefront | SlabTrack | www.slabtrack.io/api/webhooks/ecosystem-transfer/... | Same — sale propagation |
Where data lives
Each repo has its own Postgres database (separate Railway services). Cards are referenced
across repos by slabtrackId (the canonical SlabTrack cards.id). Listings/orders/etc.
are owned by whichever repo creates them and never cross-leak.
| Data | Lives in | Referenced via |
|---|---|---|
| Card master record (player, set, year, image, comp price) | SlabTrack | cards.id |
| ToT Listing (FlipConfig, lot/single, theme metadata) | ToT | Listing.cards[].card.slabtrackId |
| Storefront listing (custom price, snapshot, featured flag) | Storefront | StorefrontCard.slabtrackCardId |
| Sale state (which channel currently owns the card) | SlabTrack | card_channels table |
| Order (buyer email, payment intent, ship address) | Wherever the sale happened | Local Order table |