Domain:
tip.re-codex.com| Built with Node.js, Express, PostgreSQL, Stripe Connect, and Socket.io.
TipX is a fully self-hosted, non-custodial tip platform for streamers. It uses Stripe Connect to route tips directly to streamers' bank accounts, charges a minimal platform fee, and provides native OBS widget integration — completely replacing Streamlabs.
- Architecture Overview
- Project Structure
- Quick Start
- Environment Variables
- Database Migration
- API Reference
- OBS Widget Integration
- Route Access Control (RBAC)
- Fee Structure
- Stripe Webhook Setup
- Deployment (Render / Railway)
tip.re-codex.com/ → 302 Redirect → re-codex.com
tip.re-codex.com/:slug → Public tip page (no login)
tip.re-codex.com/dashboard/:slug → Streamer private dashboard (JWT auth)
tip.re-codex.com/dashboard → Admin master dashboard (admin only)
tip.re-codex.com/public/widget/alert?token=XYZ → OBS Alert Box
tip.re-codex.com/public/widget/progress?token=XYZ → OBS Progress Bar
Real-time flow:
Donor pays → Stripe → payment_intent.succeeded webhook → TipX backend
↓
PostgreSQL (tip logged)
↓
Socket.io /widget namespace
↓
OBS Browser Source (alert pops)
tipx/
├── schema.sql # PostgreSQL schema (run first)
├── .env.example # Copy to .env and fill in values
├── package.json
└── src/
├── server.js # Express + Socket.io entry point
├── config/
│ ├── db.js # PostgreSQL connection pool
│ └── stripe.js # Stripe SDK singleton
├── middleware/
│ └── auth.js # JWT verification + RBAC guards
├── utils/
│ └── token.js # JWT + refresh token helpers
├── controllers/
│ ├── authController.js # Login / Refresh / Logout
│ ├── adminController.js # Provision streamers, master list
│ ├── streamerController.js # Dashboard, tips, widget config
│ ├── publicController.js # Public page + Payment Intent
│ └── webhookController.js # Stripe webhook handler
├── routes/
│ ├── authRoutes.js
│ ├── adminRoutes.js
│ ├── streamerRoutes.js
│ ├── publicRoutes.js # Also serves OBS widget HTML
│ └── webhookRoutes.js # express.raw() for Stripe sig verify
├── websockets/
│ └── widgetSocket.js # Socket.io /widget namespace
└── scripts/
├── migrate.js # Apply schema.sql to DB
└── seedAdmin.js # Create the first admin account
# 1. Install dependencies
npm install
# 2. Configure environment
cp .env.example .env
# Fill in DATABASE_URL, JWT_SECRET, STRIPE_SECRET_KEY, etc.
# 3. Run database migration
npm run migrate
# 4. Seed the master admin account
npm run seed:admin admin YourStrongPassword123!
# 5. Start development server
npm run dev| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (Neon / Railway / Render) |
JWT_SECRET |
64-char random secret for access tokens |
REFRESH_TOKEN_SECRET |
64-char random secret for refresh tokens |
STRIPE_SECRET_KEY |
Stripe secret key (sk_live_... or sk_test_...) |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret (whsec_...) |
CORS_ORIGIN |
Your frontend origin, e.g. https://tip.re-codex.com |
ROOT_REDIRECT_URL |
Where / redirects, e.g. https://re-codex.com |
PORT |
Server port (default: 4000) |
The schema is in schema.sql. Run it against your Neon / Railway / Render DB:
npm run migrate
# or manually:
psql $DATABASE_URL -f schema.sqlKey optimizations in schema:
- All monetary amounts stored as
INT(satang/cents) — no floats VARCHARlimits enforced on donor names (50), messages (255), slugs (50)- Indexes on
slug,stripe_account_id,stripe_payment_intent,alert_token updated_atauto-maintained via DB trigger
| Method | Path | Description |
|---|---|---|
POST |
/auth/login |
Login with username + password |
POST |
/auth/refresh |
Rotate refresh token → new access token |
POST |
/auth/logout |
Revoke refresh token, clear cookies |
| Method | Path | Description |
|---|---|---|
GET |
/admin/streamers |
List all streamers with tip stats |
POST |
/admin/streamers |
Provision a new streamer account |
PATCH |
/admin/streamers/:id/toggle |
Enable/disable a streamer |
| Method | Path | Description |
|---|---|---|
GET |
/dashboard/:slug |
Dashboard data + widget settings |
GET |
/dashboard/:slug/tips |
Paginated tip history |
POST |
/dashboard/:slug/tips/:id/replay |
Re-trigger an alert in OBS |
PUT |
/dashboard/:slug/widget |
Update alert/progress bar config |
GET |
/dashboard/:slug/stripe/onboard |
Get Stripe onboarding URL |
| Method | Path | Description |
|---|---|---|
GET |
/:slug |
Donor page data |
POST |
/:slug/payment-intent |
Create Stripe Payment Intent |
GET |
/public/widget/alert?token=XYZ |
OBS Alert Box HTML |
GET |
/public/widget/progress?token=XYZ |
OBS Progress Bar HTML |
| Method | Path | Description |
|---|---|---|
POST |
/webhooks/stripe |
Stripe webhook receiver |
- Log into your dashboard and copy your alert token.
- In OBS → Sources → Add Browser Source.
- Set URL to:
- Alert Box:
https://tip.re-codex.com/public/widget/alert?token=YOUR_TOKEN - Progress Bar:
https://tip.re-codex.com/public/widget/progress?token=YOUR_TOKEN
- Alert Box:
- Set Width:
800, Height:200(alert box) or100(progress bar). - Enable "Shutdown source when not visible" and "Refresh when scene becomes active".
Widgets authenticate automatically via the token and maintain a persistent WebSocket connection. When a tip succeeds, OBS shows the alert within ~100ms.
| Route Pattern | admin |
streamer (own slug) |
streamer (other slug) |
Public |
|---|---|---|---|---|
/ |
✅ | ✅ | ✅ | ✅ (302 redirect) |
/:slug |
✅ | ✅ | ✅ | ✅ |
/dashboard/:slug |
✅ | ✅ | ❌ (404) | ❌ (401) |
/admin/* |
✅ | ❌ (403) | ❌ (403) | ❌ (401) |
Security Note: Unauthorized streamer slug access returns
404(not403) to prevent slug enumeration attacks.
All amounts are in satang (1 THB = 100 satang).
| Fee | Rate | Who Charges |
|---|---|---|
| Stripe PromptPay | 1.65% | Stripe (automatic) |
| TipX Platform Fee | 0.50% | TipX via application_fee_amount |
| Net to Streamer | ~97.85% | Routed directly to bank |
The platform fee is set via Stripe's application_fee_amount on the PaymentIntent, meaning TipX never holds funds.
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local server (development)
stripe listen --forward-to localhost:4000/webhooks/stripe
# Test a payment
stripe trigger payment_intent.succeededEvents handled:
payment_intent.succeeded→ Log tip, update progress bar, broadcast WebSocket alertaccount.updated→ Auto-confirm Stripe Connect onboarding completion
- Create project at neon.tech
- Copy connection string to
DATABASE_URL npm run migrate
- Connect your GitHub repo
- Set Build Command:
npm install - Set Start Command:
npm start - Add all environment variables in Render dashboard
- Set
NODE_ENV=production
- Switch to
sk_live_...andwhsec_live_...keys - Confirm each streamer's Stripe Express account is verified
- Set
statement_descriptorto streamer alias in their Stripe dashboard - Enable PromptPay in Stripe payment methods