Stack: Node.js (Express) · Supabase · Railway · Chiirp · Tango Card · WordPress
Estimated setup time: 2–3 hours
Monthly cost estimate: ~$5/mo (Railway) + Tango Card gift card float
- Prerequisites
- Supabase — Database Setup
- GitHub — Push the Code
- Railway — Deploy the Backend
- ServiceTitan — Configure the Webhook
- Chiirp — Get Your API Key
- Tango Card — Gift Card Setup
- WordPress — Install the Plugin
- Environment Variables Reference
- Verify Everything Is Working
- Admin Dashboard
- Troubleshooting
Before you start, make sure you have:
- Access to the LEX WordPress admin panel
- ServiceTitan admin/owner access (to configure webhooks)
- Access to the Chiirp account (or credentials to create one)
- A GitHub account (free)
- A Railway account — sign up at railway.app with your GitHub account
- Node.js 18+ installed locally if you want to test before deploying (optional)
Supabase is the free Postgres database that stores all referral data.
- Go to supabase.com and click Start your project
- Sign in with GitHub
- Click New project
- Fill in:
- Name:
lex-referral - Database Password: Generate a strong one and save it somewhere safe
- Region:
US East (N. Virginia)— closest to DFW
- Name:
- Click Create new project and wait ~2 minutes for it to provision
- In the left sidebar, click SQL Editor
- Click New query
- Open the file
sql/schema.sqlfrom the project folder - Copy the entire contents and paste it into the SQL editor
- Click Run (or press
Ctrl+Enter) - You should see:
Success. No rows returned - In the left sidebar, click Table Editor — you should now see 4 tables:
customersreferralsjob_eventstexts_log
- In the left sidebar, go to Settings → API
- Copy and save these two values — you'll need them later:
- Project URL → this is your
SUPABASE_URL - service_role key (under "Project API keys", click reveal) → this is your
SUPABASE_SERVICE_KEY
- Project URL → this is your
⚠️ Keep the service_role key secret. It has full database access. Never put it in public code or the WordPress plugin.
- Go to github.com/new
- Name it
lex-referral-app - Set it to Private
- Do NOT initialize with a README (your project already has one)
- Click Create repository
Open a terminal in the lex-referral-app project folder and run:
git init
git add .
git commit -m "Initial commit — LEX Referral App"
git branch -M main
git remote add origin https://github.com/YOUR_GITHUB_USERNAME/lex-referral-app.git
git push -u origin mainReplace YOUR_GITHUB_USERNAME with your actual GitHub username.
✅ Make sure
.envis listed in.gitignore(it is by default) so your secrets never get pushed to GitHub.
Railway auto-deploys from GitHub and hosts the Node.js server.
- Go to railway.app and sign in with GitHub
- Click New Project
- Choose Deploy from GitHub repo
- Select
lex-referral-app - Railway will detect it's a Node.js app and start a first (failing) deploy — that's fine, we need to add env variables first
- Click on your new service in Railway
- Go to the Variables tab
- Click Raw Editor and paste in the following, filling in your real values:
PORT=3000
NODE_ENV=production
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_SERVICE_KEY=your-supabase-service-role-key
ST_APP_ID=rlaxwjh55wy6t
ST_TENANT_ID=1498628772
ST_CLIENT_ID=your-st-client-id
ST_CLIENT_SECRET=your-st-client-secret
ST_WEBHOOK_SECRET=make-up-a-long-random-string-here
CHIIRP_API_KEY=your-chiirp-api-key
CHIIRP_FROM_NUMBER=your-chiirp-sending-number
TANGO_API_KEY=your-tango-api-key
TANGO_ACCOUNT_ID=your-tango-account-id
TANGO_FUND_ID=your-tango-fund-id
TANGO_DEFAULT_CATALOG_ITEM=VISA_VIRTUAL
SITE_URL=https://lexair.com
REFERRAL_PAGE_SLUG=referral
MIN_JOB_VALUE=150
REFERRER_REWARD=75
NEW_CUSTOMER_DISCOUNT=50
ADMIN_PASSWORD=choose-a-strong-password-here
- Click Save — Railway will trigger a new deploy automatically
- Click the Settings tab in your Railway service
- Under Networking, click Generate Domain
- Your app URL will look like:
https://lex-referral-app-production.up.railway.app - Save this URL — you'll need it for the ST webhook and WordPress plugin
Visit https://your-railway-url.up.railway.app/health in your browser.
You should see:
{ "status": "ok", "app": "LEX Referral App", "timestamp": "..." }If you see an error, check the Deploy Logs tab in Railway for what went wrong.
This is what triggers the whole referral flow — ST calls your app every time a job is completed.
- In ServiceTitan, go to the gear icon → Settings
- Search for Webhooks or navigate to Integrations → Webhooks
📝 ServiceTitan webhook configuration may require Owner or Admin access. If you don't see it, contact ST support or check your permission level.
Click Add Webhook (or New) and fill in:
| Field | Value |
|---|---|
| Name | LEX Referral App |
| URL | https://your-railway-url.up.railway.app/webhooks/servicetitan |
| Events | Job Completed and Booking Created |
| Secret | The same random string you put in ST_WEBHOOK_SECRET |
| Active | Yes |
Click Save.
For the referral tracking to work when a referred friend books, you need a way to pass the referral slug through to ServiceTitan. There are two approaches:
Option A (Recommended) — Custom field on the booking:
- In ST Settings, find Custom Fields
- Create a new field on the Customer or Booking record:
- Name:
Referral Code - Field ID / API Name:
referralSlug
- Name:
- When your WordPress booking form passes a referral slug in the URL, include it as this custom field value
Option B — Use a call tag: If Option A isn't available, office staff can manually add a "Referral" tag to jobs that came from referrals, and we can filter on that. Less automated but workable.
We'll revisit this in Phase 4 when we tighten up the booking-to-referral link flow.
- Log into your Chiirp account
- Go to Settings → API (or Integrations → API Keys)
- Click Generate New Key (or copy your existing key)
- This is your
CHIIRP_API_KEY
- In Chiirp, go to Settings → Phone Numbers
- Copy the phone number LEX uses for outbound texts
- Format it as
+19725551234(E.164 format with country code) - This is your
CHIIRP_FROM_NUMBER
Add both values to your Railway environment variables if you haven't already, then Railway will redeploy automatically.
📝 Note: Chiirp's API documentation may call the endpoint differently. If the texts aren't sending, check the Chiirp API docs for the correct base URL and request format, and compare it to
src/services/chiirp.js. The fields most likely to need adjusting areto,from, andbody.
Tango Card handles automatic digital gift card delivery (Visa or Amazon).
- Go to tangocard.com and click Get Started
- Sign up as a business
- Complete the account verification (may take 1 business day)
- Once approved, go to Funds → Add Funds
- Add at least $500 to start (this is your gift card float — it draws down as rewards go out)
- Set up auto-refill to avoid running out (recommended threshold: $200)
- Go to Account → API Access or Settings → Integrations
- Copy:
- API Key →
TANGO_API_KEY - Account Identifier →
TANGO_ACCOUNT_ID - Fund ID →
TANGO_FUND_ID
- API Key →
The default is VISA_VIRTUAL (a virtual Visa prepaid card — most flexible for customers).
Other options you can use for TANGO_DEFAULT_CATALOG_ITEM:
AMAZON— Amazon gift card- Check the Tango API catalog endpoint for the full list of available items
- In your WordPress admin, go to Plugins → Add New → Upload Plugin
- Upload the file
wordpress/lex-referral.php - Click Install Now, then Activate Plugin
Alternatively, upload via FTP/SFTP:
- Connect to your server
- Navigate to
/wp-content/plugins/ - Create a folder called
lex-referral - Upload
lex-referral.phpinto that folder - Activate it in WordPress → Plugins
- Open
wordpress/lex-referral.phpin a text editor - Find this line near the top:
define('LEX_REFERRAL_API_URL', 'https://lex-referral-app.up.railway.app');
- Replace the URL with your actual Railway app URL
- Save and re-upload the file (or edit directly in Plugins → Plugin Editor)
- In WordPress, go to Pages → Add New
- Fill in:
- Title:
Refer a Friend - Slug:
referral(so the URL islexair.com/referral)
- Title:
- In the page content, add the shortcode:
[lex_referral] - Set the page template to full-width or blank if your theme supports it (removes sidebar for a cleaner look)
- Click Publish
Visit https://lexair.com/referral — you should see a generic "Refer a Friend, Earn $75!" page.
Now test with a fake referral link: https://lexair.com/referral?r=test-slug
You should see "Referral Link Not Found" — which is correct since that slug doesn't exist yet.
Complete list of all variables and what they do:
| Variable | Required | Description |
|---|---|---|
PORT |
Yes | Server port. Railway sets this automatically — use 3000 |
NODE_ENV |
Yes | Set to production |
SUPABASE_URL |
Yes | Your Supabase project URL |
SUPABASE_SERVICE_KEY |
Yes | Supabase service role key (keep secret) |
ST_APP_ID |
Yes | ServiceTitan App ID: rlaxwjh55wy6t |
ST_TENANT_ID |
Yes | ServiceTitan Tenant ID: 1498628772 |
ST_CLIENT_ID |
Yes | ST OAuth client ID |
ST_CLIENT_SECRET |
Yes | ST OAuth client secret |
ST_WEBHOOK_SECRET |
Yes | Random string to verify ST webhook signatures |
CHIIRP_API_KEY |
Yes | Chiirp API key for sending texts |
CHIIRP_FROM_NUMBER |
Yes | Chiirp sending number in E.164 format (+19725551234) |
TANGO_API_KEY |
Yes | Tango Card API key |
TANGO_ACCOUNT_ID |
Yes | Tango Card account identifier |
TANGO_FUND_ID |
Yes | Tango Card fund to draw from |
TANGO_DEFAULT_CATALOG_ITEM |
Yes | VISA_VIRTUAL or AMAZON |
SITE_URL |
Yes | https://lexair.com |
REFERRAL_PAGE_SLUG |
Yes | referral |
MIN_JOB_VALUE |
Yes | Minimum job total to qualify (150) |
REFERRER_REWARD |
Yes | Gift card amount for referrer (75) |
NEW_CUSTOMER_DISCOUNT |
Yes | Discount shown to new customer (50) |
ADMIN_PASSWORD |
Yes | Password to access /admin dashboard |
Work through this checklist after setup to confirm the full flow is operational.
-
GET /healthreturns{"status":"ok"}on your Railway URL - Supabase Table Editor shows all 4 tables (customers, referrals, job_events, texts_log)
- Railway deploy logs show
✅ LEX Referral App running on port 3000
- Visit
https://your-railway-url.up.railway.app/admin - You're redirected to the login page
- Log in with your
ADMIN_PASSWORD - Dashboard loads showing 0s across all stats (expected — no data yet)
- All 4 tabs (Overview, Referrals, Top Referrers, Activity) load without errors
-
https://lexair.com/referralloads and shows the referral page - The page renders without JS errors (check browser console)
-
https://lexair.com/referral?r=sarah-m-testshows "Referral Link Not Found"
Send a test POST to your webhook endpoint using a tool like Postman or the curl command below:
curl -X POST https://your-railway-url.up.railway.app/webhooks/servicetitan \
-H "Content-Type: application/json" \
-d '{
"eventType": "job.completed",
"jobId": "TEST-001",
"customerId": "99999999",
"customerName": "Test Customer",
"customerPhone": "9725550001",
"customerEmail": "test@example.com",
"total": 250
}'After running this, check:
- Supabase
job_eventstable has a new row withst_job_id = TEST-001 - Supabase
customerstable has a new row for "Test Customer" with areferral_slug - Railway logs show
[Customer] Created: Test Customer | Slug: test-c-xxxx
- Complete a real job in ServiceTitan for a test customer
- Wait ~30 seconds for the webhook to fire
- Check Supabase
customers— new row should appear - Check Supabase
texts_log— referral invite text should be logged - Confirm the customer received the text with their personal link
- Click the link, verify the landing page loads correctly
- The admin dashboard should show the new referral in the pipeline
The admin dashboard is live at:
https://your-railway-url.up.railway.app/admin
| Tab | What It's For |
|---|---|
| Overview | KPI cards, pipeline bar, trend charts, activity feed |
| Referrals | Full table of every referral with status filters |
| Top Referrers | Leaderboard of your best referring customers |
| Activity | Real-time log of every referral event |
The dashboard is password-protected with a single shared password. Share the URL and ADMIN_PASSWORD only with:
- Yourself
- Anyone else at LEX who needs visibility into the program
There's no sensitive financial data in the dashboard (gift card numbers are emailed by Tango directly to the customer), so it's safe to share with office management.
Update ADMIN_PASSWORD in your Railway environment variables. The change takes effect on the next deploy (Railway auto-deploys on env var changes).
- Check Railway logs for
[Chiirp]lines — is there an error message? - Verify
CHIIRP_API_KEYandCHIIRP_FROM_NUMBERare set correctly in Railway - Confirm the customer record in Supabase has a non-empty
phonefield - Check that the Chiirp API base URL in
src/services/chiirp.jsmatches what Chiirp's docs specify
- Check Railway logs for
[Tango]lines - Verify your Tango account has sufficient funds
- Confirm the referrer has an
emailin their customer record in Supabase - Check that the referral record moved to
status = completedbefore the gift card attempt
- In ServiceTitan, check Settings → Webhooks and look for a delivery history/log
- Make sure the webhook URL is exactly right (no trailing slash, correct Railway URL)
- Verify the webhook is set to Active
- Check Railway logs — if the request is hitting the server you'll see
[ST Webhook]log lines
- Confirm the WordPress page slug is exactly
referral - Make sure the
LEX_REFERRAL_API_URLin the plugin file matches your Railway URL - Check the browser console for any JS errors or blocked requests
- Check CORS — the Railway app only allows requests from
SITE_URL; confirm that's set tohttps://lexair.com
- Check the Deploy Logs tab in Railway
- Most common cause: a missing environment variable. Check that all required variables from Section 9 are set.
- Check
package.jsonhas"start": "node src/index.js"— Railway uses this to start the app
- Double-check
SUPABASE_URL— should end in.supabase.cowith no trailing slash - Make sure you're using the service_role key, not the
anonkey - Check that the schema was applied correctly in Supabase's Table Editor
| Phase | What Was Built | Status |
|---|---|---|
| Phase 1 | Database schema, ST webhook handler, customer upsert logic | ✅ Complete |
| Phase 2 | Referral link generation, WordPress landing page, Chiirp + Tango integrations | ✅ Complete |
| Phase 3 | Password-protected admin dashboard with 4 tabs, charts, pipeline view | ✅ Complete |
| Phase 4 | Chiirp follow-up sequence for non-sharers | 🔜 Planned |
| Phase 5 | Customer-facing "my referrals" portal | 🔜 Planned |
LEX Air Conditioning — Serving DFW Since 2004
(972) 466-1917 · lexair.com