A fictional company website with an infinite hold music phone line, powered by Cloudflare Workers, D1 Database, and Twilio. Features privacy-preserving call analytics with HMAC-SHA256 hashed phone numbers.
This is a parody project that creates a professional-looking corporate website for the fictional "Red Ball Market Global" company (based on The Chair Company on HBO). When visitors call the phone number, they're greeted with a corporate hold message and infinite hold music. The system tracks all calls with privacy-preserving analytics.
- π Fictional Corporate Website: Replica of the RBMG website with professional design
- π Twilio Integration: Real phone number that plays infinite hold music
- π Privacy-Preserving Analytics: Phone numbers are hashed using HMAC-SHA256 with secret key - never stored in plain text
- π Call Tracking:
- Longest single hold time
- Most persistent caller (most calls)
- Total time champion
- Geographic statistics by state/city
- Recent calls list
- βοΈ Cloudflare Stack:
- Workers for serverless compute
- D1 Database for SQL storage
- Static Assets for the website
Phone numbers are never stored in the database. Instead:
- Full number is hashed with HMAC-SHA256 using a secret key for unique identification
- Rainbow table attacks are prevented by the secret key
- Display shows area code + hash:
(224) A3B4 - Geographic data (city/state) is preserved from Twilio
- See PRIVACY.md for full details
- Frontend: Vanilla HTML/CSS/JavaScript
- Backend: Cloudflare Workers (TypeScript)
- Database: Cloudflare D1 (SQLite)
- Telephony: Twilio Voice API
- Deployment: Cloudflare Workers Platform
rbmg/
βββ public/
β βββ index.html # Main website
β βββ styles.css # Website styling
β βββ app.js # Frontend analytics
β βββ please-hold.mp3 # Hold music file
βββ src/
β βββ index.ts # Worker main entry point
β βββ utils.ts # Phone number hashing utilities
βββ migrations/
β βββ 0001_init.sql # Database schema
βββ DEPLOYMENT.md # Detailed deployment guide
βββ PRIVACY.md # Privacy implementation details
βββ wrangler.json # Cloudflare Worker config
-
Install dependencies
npm install
-
Set up environment variables
Create a
.dev.varsfile (don't commit this):# Generate a secret key openssl rand -hex 32Add to
.dev.vars:PHONE_HASH_SECRET=your_generated_secret_here -
Apply database migrations locally
npm run seedLocalD1
-
Start local dev server
npm run dev
-
Visit
http://localhost:8787to see the website
See DEPLOYMENT.md for complete deployment instructions including:
- Deploying to Cloudflare
- Setting up Twilio phone number
- Configuring webhooks
- Environment variables
Quick deploy:
# Deploy to Cloudflare
npm run deploy
# Apply migrations to production
npm run predeploy
# Set phone hash secret (REQUIRED)
openssl rand -hex 32 # Generate a secret
wrangler secret put PHONE_HASH_SECRET
# Set Twilio Auth Token for request validation (recommended)
wrangler secret put TWILIO_AUTH_TOKEN
# (Optional) Set worker URL if needed for MP3 file
wrangler secret put WORKER_URLGET /- Main websiteGET /please-hold.mp3- Hold music file
GET /api/analytics- Get call analytics data
POST /twilio/voice- Incoming call webhook (returns TwiML)POST /twilio/status- Call status callback (tracks call completion)
CREATE TABLE calls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
call_sid TEXT UNIQUE NOT NULL,
from_number TEXT NOT NULL, -- HMAC-SHA256 hash
from_number_hash TEXT, -- HMAC-SHA256 hash (duplicate for views)
from_number_display TEXT, -- Display format: (224) A3B4
from_area_code TEXT, -- First 3 digits
to_number TEXT, -- Your Twilio number
start_time DATETIME NOT NULL,
end_time DATETIME,
duration_seconds INTEGER,
status TEXT NOT NULL,
city TEXT, -- From Twilio
state TEXT, -- From Twilio
country TEXT, -- From Twilio
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);Includes pre-built views for analytics:
longest_single_hold- Top 10 longest hold timesmost_calls- Most frequent callersmost_time_overall- Total time championsgeographic_stats- Calls by location
Replace public/please-hold.mp3 with your own file and redeploy.
Edit the <Say> element in src/index.ts (handleTwilioVoiceWebhook function):
<Say voice="joey" language="en-US">
Your custom message here
</Say>Edit files in public/:
index.html- Structurestyles.css- Stylingapp.js- Frontend logic
npm run dev # Start local development server
npm run check # TypeScript check + dry-run deploy
npm run deploy # Deploy to Cloudflare
npm run predeploy # Apply DB migrations to production
npm run seedLocalD1 # Apply DB migrations locally
npm run cf-typegen # Generate TypeScript types- Cloudflare Workers: Free tier (100,000 requests/day)
- Cloudflare D1: Free tier (5GB storage, 5M reads/day)
- Twilio: ~$1/month for phone number + per-minute charges for calls
The twilio costs will soon bankrupt me. I gotta figure out how to make money on this. Itβs simply too good.
- Request Validation: Twilio webhook requests are validated using HMAC-SHA1 signatures (when
TWILIO_AUTH_TOKENis set) - Phone Number Privacy: Phone numbers are hashed with HMAC-SHA256 using a secret key before storage
- Rainbow Table Resistant: Secret key prevents pre-computed rainbow table attacks
- No PII: No personally identifiable information is stored in plain text
- Geographic Data: City/state/country from Twilio is preserved for analytics
- GDPR/CCPA: Suitable for compliance with privacy regulations
- See PRIVACY.md for details
MIT - This is a parody/joke project. RBMG is a fictional company.
- RBMG design inspired by The Chair Company on HBO
- Built with Cloudflare Workers and D1
- Powered by Twilio Voice API