How Bright Side Plumbing's digital ecosystem is wired together β what runs where, how pieces talk to each other, and what happens when a customer request comes in. Primary audience: Stephanie, Kalen, Tim, and any future team member needing to orient to the stack.
BSP runs a three-ring digital ecosystem. The outer ring is what customers touch: the website (Bricks Builder on WordPress), phone calls (3CX PBX), SMS (Telnyx), and the AI voice agent Daniel (Vapi). The middle ring orchestrates: the Nexus VM runs Python pipelines (Titan) that pull data from external systems, generate page content, route messages, and schedule work. The inner ring holds data: ServiceTitan is the source of truth for jobs and customers; Google Business Profile is the source for reviews; LiteSpeed + Cloudflare cache the edge. Claude Code agents (like this one) sit outside the rings β they use SSH + REST to ship changes.
The whole thing is held together by handshakes β the well-defined points where one system hands data or control to another. When those handshakes work, customers get same-day service with confirmation SMS within 60 seconds of booking. When they break (like the Apr 20 Tim Mahony incident β ServiceTitan booking auto-dismissed + our form submission threw 400), customers almost churn. This document catalogs every handshake so the next breakage is easy to diagnose.
βββββββββββββββββββββββββββββββββββββ
β CUSTOMER-FACING SURFACE (outer) β
β π website π phone π¬ sms β
β π€ Daniel AI voice β
ββββββββββββββββ¬βββββββββββββββββββββ
β
handshakes
β
ββββββββββββββββΌβββββββββββββββββββββ
β ORCHESTRATION (middle) β
β Nexus VM Β· Titan pipelines Β· WP β
β cron Β· Claude Code agents β
ββββββββββββββββ¬βββββββββββββββββββββ
β
handshakes
β
ββββββββββββββββΌβββββββββββββββββββββ
β DATA + EDGE (inner) β
β ServiceTitan Β· GBP Β· Telnyx Β· 3CXβ
β LiteSpeed Β· Cloudflare Β· Postgres β
ββββββββββββββββββββββββββββββββββββββ
Each card: what the system is, what it does, where it lives, who owns it, why it matters.
The orchestration host. Ubuntu server running all Python pipelines, custom APIs, cron jobs, and integration wrappers.
/opt/nexus/ codebaseWhere: dovew@34.55.179.122 (GCP)
Owner: Robert. SSH key ~/.ssh/google_compute_engine
Timezone: America/Chicago
Python scripts that generate content, pull data, run recurring work. Live under /opt/nexus/titan/.
location_page_mining.py β generates city page briefs from ST + GBP + Fleet datafleet_availability.py β every 15 min, computes per-city tech ETA via Google Distance Matrix (Rung 1: ST schedule-based, no GPS)nexus_html_logger.py β writes MH entriestitan_sync_daemon.py β mirrors ServiceTitan data to local PostgresWhere: /opt/nexus/titan/
Owner: Robert (development) Β· Nexus VM cron (execution)
WordPress theme that builds pages visually. Replaces Oxygen (scheduled for Mon/Tue Apr 27-28 2026 production cutover).
_bricks_page_content_2 post meta (PHP-serialized array)Where: staging bricks.callbrightside.com Β· prod (post-cutover) callbrightside.com
Owner: Robert (license + domain) Β· Claude Code (page builds via REST)
WordPress plugin that injects custom PHP/CSS/JS at runtime hooks (wp_head, wp_footer, init). Our entire polish stack lives here.
/wp-json/code-snippets/v1/snippetsactive field inconsistent across plugin versionsWhere: WordPress wp_snippets table
Owner: Claude Code (runtime) Β· Robert (approval for production ships)
SaaS CRM/dispatch system. Source of truth for customers, jobs, bookings, invoices, technician schedule.
4316907157api.servicetitan.io with /crm/v2, /jpm/v2, /dispatch/v2, /telecom/v2, /settings/v2 pathsWhere: ServiceTitan SaaS (URL TBD β ask Robert for login URL)
Owner: Ashton (operational) Β· Robert (API credentials)
Google's local-business listing + reviews source. BSP's 4.9β rating visible on Maps + Search comes from here.
/api/gbp/reviews, /api/gbp/reviews/recent, /api/gbp/reviews/statsmybusiness.googleapis.comWhere: Google infrastructure (account: BSP)
Owner: Robert (account) Β· Ashton (review responses)
Programmable SMS provider. Replaces prior Twilio consideration for customer-facing and internal alert messaging.
/opt/nexus/titan/integrations/telnyx_sms.pyWhere: Telnyx SaaS + Nexus wrapper
Owner: Robert (account) Β· Tim (incoming implementation for dismissed-queue worker)
Voice PBX running BSP phone lines. VoIP routing + extension management + call control API.
bsp-apr18-3cx-officehours-8am-correctionWhere: 3CX instance URL TBD β in LawnPhone.com-managed env
Owner: Robert (account, approves changes) Β· Ashton (daily operations) Β· LawnPhone.com (technical admin)
AI voice assistant for inbound/outbound calls. Handles scheduling prompts, FAQ, and can trigger ServiceTitan booking.
e2920d04 Β· Phone +19139639817/opt/nexus/titan/api/vapi_voice.py (webhook handler)Where: Vapi SaaS + Nexus wrapper
Owner: Robert (prompt + flow design) Β· Ashton (takes escalations from Daniel)
Documentation rendering layer. Serves the playbook HTML files (Codebase Doc, Master History, this doc) under morpheus.callbrightside.com/documents/.
/opt/nexus/nexus/scripts/output/playbooks/ on VMWhere: Nexus VM Β· DNS alias
Edge CDN + cache in front of callbrightside.com. Also bricks.callbrightside.com via orange-cloud.
/client/v4/zones/{id}/purge_cacheCLOUDFLARE_ZONE_ID + CLOUDFLARE_API_TOKEN env varsCF_API_TOKEN β silently failing for monthsWhere: Cloudflare SaaS
Owner: Robert
WordPress-side page cache. Sits between the Bricks render pipeline and Cloudflare edge.
litespeed-cache/litespeed-cache.php/wp-json/bsp/v2/cache/purgeWhere: WordPress plugin on staging + prod
Owner: Robert
Development agent (the one writing this doc). SSHs into Nexus VM, executes Python, edits code, ships snippets.
google_compute_engine + WordPress app password claude-api userWhere: Claude Code CLI (local) + ephemeral SSH sessions
Owner: Robert (operator)
New Python cron/systemd worker to rescue ServiceTitan Dismissed bookings. See /tmp/tim_handoff_plan.md (317 lines, updated Apr 23).
/opt/nexus/titan/workers/dismissed_queue/Where: Nexus VM (once shipped)
Owner: Tim (engineer) β Robert confirms prereqs before Tim starts
This is the core of this document. Every place where two systems exchange data or control is a handshake β the clean, defined boundary between them. Breakages almost always live at handshakes, not inside systems.
Each handshake below lists: direction, auth, protocol, endpoint(s), trigger, payload, purpose, failure mode, retry logic, observability.
Customer phone β 3CX (VoIP) β answered by tech OR voicemail
β
βββ answered β manual ST entry by Ashton
βββ voicemail β callback flow (70% book next day per MH)
THREECX_API_URL; MH bsp-apr20-memory-backfill-3cx-afterhours-routingSources: MH apr17-3cx-toggle-retired-permanent-fix, apr18-3cx-officehours-8am-correction, apr20-memory-backfill-3cx-afterhours-routing, apr21 Tim Mahony incident.
Customer SMS β Telnyx number β webhook (POST) β /api/telnyx/inbound
β
βββ STOP/HELP β TCPA opt-out handler
βββ Lead text β ServiceTitan lead creation
βββ Other reply β Slack to Ashton + titan.telnyx_messages log
https://[VM]/api/telnyx/inbound route not yet built Β· Outbound: https://api.telnyx.com/v2/messagestitan.telnyx_messages table (once Tim's worker ships)Sources: Codebase Doc Β§38 SMS+Voice rails Β· Tim handoff plan Apr 23 update.
cron (hourly) β fetch_gbp_reviews_for_city(city_slug)
β Internal /api/gbp/reviews or /api/gbp/reviews/recent
β (internal wraps) mybusiness.googleapis.com
β returns reviews[] with author, rating, text, date
β pipeline filters by city + landmarks mention
/api/gbp/reviews/recent?limit=400 Β· External: mybusiness.googleapis.comreview_intelligence.db on failurereview_intelligence.dbSources: /opt/nexus/titan/api/gbp.py Β· /opt/nexus/titan/location_page_mining.py.
Multiple callers (titan_sync_daemon, eta_relay, auto_dispatcher, referral_weapon, money_finder)
β api.servicetitan.io/{crm|jpm|dispatch|telecom|settings}/v2/tenant/4316907157/...
β OAuth2 bearer token per-tenant
β returns jobs, customers, dispatch, calls
api.servicetitan.io/crm/v2/tenant/4316907157/leads (create lead β see Snippet #97 campaignId fix) Β· /jpm/v2/tenant/.../jobs (read jobs) Β· /telecom/v2/tenant/.../calls (call history) Β· /dispatch/v2/tenant/.../... (fleet dispatch) Β· /settings/v2/tenant/... (config)campaignId, businessUnitId, jobTypeId, customer object (learned via Apr 21 Tim Mahony incident).titan.jobs, titan.customers tables Β· Nexus logsSources: /opt/nexus/titan/api/*.py Β· MH bsp-apr20-contact-form-pipeline-full-fix Β· MH bsp-apr21-tim-mahony-incident-snippet97.
Pipeline β /wp-json/wp/v2/pages/{id} (GET/POST/PUT)
β /wp-json/code-snippets/v1/snippets (GET/POST/PUT/DELETE)
β /wp-json/bsp/v2/cache/purge (POST)
β Basic auth with claude-api application pw
BRICKS_WP_APP_PASSWORD)/wp-json/ lists: wp/v2, code-snippets/v1, bricks/v1, bricks-ai-studio/v1, litespeed/v1-v3, bsp/v1-v3, hostinger-*, mcp.update_post_meta returns false on success (WP internal hash-compare false positive; Apr 23 discovery) β use $wpdb->update fallback Β· PUT on snippet active field inconsistent across Code Snippets plugin versionsSources: Codebase Doc Β§28.2 update_post_meta false Β· Β§28.1 PUT active inconsistent.
HTTP request β WP bootstrap β init action β snippets load
β
βββ wp_head hook: CSS snippets inject into
βββ wp_footer priority 99999: CSS/JS snippets inject before
βββ template_redirect priority 1: ob_start snippets rewrite HTML
βββ Bricks renders its own content_2 meta into main
wp_footer priority 99999 (our standard) Β· template_redirect priority 1 (server-side img src swap) Β· init (one-shot mutations)wp_snippets table Β· executed in-processSources: Codebase Doc Β§27.5 Custom Code integration Β· Academy lesson 01_custom_code.md.
Builder save β write to post_meta._bricks_page_content_2 (PHP-serialized array)
β Bricks 2.3.2 quirk: some 4-value properties serialize as literal "Array" string
β On frontend render, Bricks parses meta β emits CSS (inline or external)
get/update_post_meta_bricks_page_content_2 (~49KB for OP 258), _bricks_template_conditions, _bricks_editor_mode, _bricks_template_typewp_postmeta.meta_value must be LONGTEXT (verified β on staging). Bricks Array serialization bug for 4-value padding/margin/border-radius.$wpdb->update when update_post_meta no-ops.Sources: Codebase Doc Β§28.2, Β§28.3, Β§37.4 Β· Bricks Academy lesson 10 Known Issues.
Claude Code (local) ββssh/scpβββΆ Nexus VM (34.55.179.122)
β
βββ python3 /tmp/script.py (with .env credentials)
βββ curl /wp-json/... (WP REST)
βββ curl /api/cloudflare/zones/{id}/purge_cache
βββ python3 nexus_html_logger.py (MH writes)
~/.ssh/google_compute_engine (public half authorized on VM for user dovew)dovew@34.55.179.122 port 22
Linux cron daemon β /usr/bin/python3 /opt/nexus/titan/{script}.py
β
βββ fleet_availability.py every 15 min
βββ GA4 full report daily 8am
βββ financial validator daily after briefing
βββ review intelligence hourly
βββ CPL monitor daily 3pm
βββ (22+ other automations)
dovew; .env loaded at process startcrontab -l under dovew/opt/nexus/nexus/scripts/output/, SQLite/Postgres writes, MH entries, Slack messages)/opt/nexus/nexus/scripts/output/*_cron.log files Β· MH entries
Nexus script β POST https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache
Body: {"purge_everything": true}
Auth: Bearer CLOUDFLARE_API_TOKEN
CLOUDFLARE_API_TOKEN + CLOUDFLARE_ZONE_ID.https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache{"purge_everything": true} or {"files": ["https://..."]} for surgical purgeCF_API_TOKEN (wrong name) β env actually uses CLOUDFLARE_API_TOKEN. Purges silently returned no-op. Fixed.success: falseresult.id for traceabilitySource: Codebase Doc Β§34.1.
/wp-json/bsp/v2/cache/purge (BSP custom route that calls LSC internally)x-litespeed-cache header after.morpheus.callbrightside.com/documents/*.html β serves from /opt/nexus/nexus/scripts/output/playbooks/*.html
Inbound call β 3CX routes to Daniel extension β Vapi answers
β
βββ AI conversation (Vapi-managed)
βββ Transfer to human ext β 3CX routes to Ashton
βββ Booking intent β vapi_voice.py webhook β ST lead creation
+19139639817 Β· Vapi assistant e2920d04 Β· Nexus webhook at /opt/nexus/titan/api/vapi_voice.py handlertitan.vapi_calls table via transcript_repo.pySource: /opt/nexus/titan/api/vapi_voice.py Β· MH apr17-vapi-monitoring-deployed.
tel:+19139639817 (phone) OR in-browser Vapi widget TBDtel: URIa.brxe-button href="#daniel-chat" currently Β· full Vapi browser widget integration deferred#daniel-chat β no JS handler yet. Needs Vapi widget wire-up. pending integration
Customer submits contact form β WordPress POST handler β Snippet #97 β ST /leads API
β
βββ (gap): NO positive handshake send-off back to customer
Tim (customer) raised this Apr 21: "did my form even go through?"
bsp-contact-form-st-leadSource: Tim handoff plan Β· MH bsp-apr21-tim-mahony-incident-snippet97.
api.servicetitan.io/crm/v2/tenant/4316907157/bookings?status=Dismissed&source=Online Booking Widget&createdAfter={T-48h}titan.dismissed_queue_alerts tracks ack state to avoid double-pingingtitan.dismissed_queue_alerts Β· daily MH entry bsp-{YYYYMMDD}-dismissed-queue-daily (planned) Β· Slack + SMS receiptsSource: Tim handoff plan Apr 23 update (Deliverable C).
Status: medium-term (Q2-Q3 2026). Not shipped as of Apr 23 2026.
SLACK_WEBHOOK_URL TBD β Robert to create #bsp-alerts channel + webhook
1. Customer dials (913) 963-1029
2. 3CX answers, IVR or direct route to tech extension
3. Tech answers OR voicemail
a. Answered β human sales flow β ServiceTitan manual job creation by Ashton
b. Voicemail β callback flow (70% book next day per MH)
4. Job completed β invoiced in ServiceTitan β HCP mirror to Postgres
1. Customer fills contact form on callbrightside.com 2. WordPress POST handler β Snippet #97 fires 3. Snippet #97 creates ST lead via /crm/v2/leads (campaignId required since Apr 21) 4. Slack DM to Ashton + email to Robert 5. (gap) NO SMS to customer β they don't know form went through 6. Ashton manually follows up hours later 7. Future (Deliverable A): Telnyx SMS fires within 60s β customer reassured
1. Customer texts BSP's Telnyx number 2. Telnyx β webhook to /api/telnyx/inbound on Nexus 3. Nexus parses: STOP/HELP β TCPA handler Β· lead text β ST lead Β· other β Ashton Slack 4. Auto-reply via Telnyx (Variant 1 first-time OR Variant 2 repeat OR manual)
1. cron fires location_page_mining.py --city overland-park
2. Pipeline pulls:
- GBP reviews (/api/gbp/reviews/recent)
- ServiceTitan job/customer stats
- Fleet availability (precomputed every 15 min)
3. Pipeline builds brief JSON (h1_variants, subtitle, CTAs, services, etc.)
4. Writes to WP post meta via REST (or PHP-native on page edit)
5. Triggers wp_update_post β Bricks regenerates inline CSS
6. Purges: LiteSpeed (/wp-json/bsp/v2/cache/purge) + Cloudflare (/zones/{id}/purge_cache)
7. Next request fetches fresh HTML from origin
1. Robert types prompt to Claude Code 2. Claude writes Python script locally 3. scp /script.py dovew@34.55.179.122:/tmp/ 4. ssh dovew@34.55.179.122 "python3 /tmp/script.py" 5. Script loads .env, constructs payload, POSTs to /wp-json/code-snippets/v1/snippets 6. New snippet active=true β runs on next page load 7. Script fires LS + CF purges 8. Claude Code takes a headless Chrome screenshot to verify 9. Claude writes MH entry via nexus_html_logger.py
Master credential store: /opt/nexus/nexus/config/.env (mode 600, owner dovew). Not committed to git.
| Credential | Env var | Used by | Owner / rotation |
|---|---|---|---|
| ST OAuth2 client ID/secret | ST_* vars | Titan pipelines (titan_sync, eta_relay, referral_weapon) | Robert Β· per ST client policy |
| GBP OAuth2 token | GBP_* vars | gbp.py wrapper | Robert Β· refresh per Google expiry |
| WP application password | BRICKS_WP_APP_PASSWORD | Claude Code snippet operations | Robert Β· on-demand rotation |
| Cloudflare API token | CLOUDFLARE_API_TOKEN | Cache purge | Robert Β· zone-scoped |
| Cloudflare zone ID | CLOUDFLARE_ZONE_ID | Cache purge | Robert (static) |
| Telnyx API key | TELNYX_API_KEY | Future worker + SMS handshake | Robert |
| Telnyx FROM number | TELNYX_FROM_NUMBER | Outbound SMS | Robert |
| Telnyx 10DLC brand + campaign | TELNYX_10DLC_* | TCPA registration | Robert |
| 3CX API URL + keys | THREECX_API_URL, THREECX_API_KEY, THREECX_ADMIN_API_KEY | Voice integration | Robert (via LawnPhone.com) |
| Vapi API key + assistant ID | VAPI_API_KEY, VAPI_ASSISTANT_ID | Daniel voice agent | Robert |
| SSH private key (Claude β VM) | n/a β file ~/.ssh/google_compute_engine | Claude Code SSH | Robert laptop Β· rotation per GCP guidance |
| Slack webhook URL | SLACK_WEBHOOK_URL TBD | Team alerts | Robert |
Symptoms: Content changes on origin, but edge serves stale content even after "CF PURGE" log line.
Root cause (Apr 23 discovery): Scripts checked env var CF_API_TOKEN; actual var is CLOUDFLARE_API_TOKEN. Conditional fell through, no HTTP call made.
Fix: Use env.get('CLOUDFLARE_API_TOKEN') or env.get('CF_API_TOKEN') (both-compatible). Verify by checking response result.id.
update_post_meta returns false on successSymptoms: PHP says write failed, but data IS actually updated in DB.
Root cause: WP's internal hash-compare false positive on deeply-nested arrays.
Fix: Fall back to $wpdb->update($wpdb->postmeta, [...], [...]). Pair with wp_update_post(['ID' => $id]) to bump modified time + trigger Bricks regen.
activeSymptoms: PUT {active: true} returns 200, subsequent GET still shows active: false.
Fix: DELETE + POST new snippet with target active state. Or use PUT on code field only (which does persist reliably).
Symptoms: CSS output contains padding-top: Array; instead of valid value. Browser ignores.
Fix: Override via custom CSS snippet with explicit values. Possibly resolved in 2.3.3 "Fix DB" tool.
Symptoms: Your new rule doesn't apply despite being later in source order.
Diagnosis: Inspect which selector has higher specificity. #115 often has (0,1,3,1) patterns.
Fix: Use 3-ID selector chain for a (0,3,0,0) override that beats on pure ID count. In the future, use @layer bricks.reset β but cascade layers not currently active on our site (verified Apr 23).
Symptoms: Chip shows no ETA; pipeline uses fallback value.
Expected: Fleet data is only populated during business hours 7am-6pm CT when techs have scheduled jobs. Weekend + holidays = null.
Fix: Chip uses 2-state logic (Snippet #161) β "Open Now" during business hours, "Open for Emergencies" otherwise. No fallback number exposed to customer.
| Person | Domain | Authority |
|---|---|---|
| Robert Dove (you) | Product Β· launch timing Β· brand Β· credentials Β· Phase D/F/G decisions | Final say on ship-or-not |
| Kalen Barker (4th-gen master plumber) | Operational decisions Β· pricing Β· service promises Β· chip copy Β· customer-facing claims | Veto on anything that touches customer experience |
| Stephanie Barker | Business ops Β· hiring Β· financial oversight full scope TBD β confirm with Robert | P/I/S/D/N format communication standard is hers |
| Ashton King (he) | CX Β· ServiceTitan operational Β· phone ops Β· 100 Year Plumbing duties | Daily booking flow, review response |
| Audrey Grant | Design (Figma source of truth) Β· service page content playbook | Visual design decisions Β· no emojis Β· project-by-project |
| Tim (engineer β TBD full name) | Dismissed-queue worker (Deliverable C) | Owns implementation of Tim handoff plan deliverables, pending Robert's prereqs |
| LawnPhone.com (David Kite / Brandon Lofthouse) | 3CX administration | Vendor β manages 3CX instance |
| Claude Code | Implementation execution Β· documentation Β· analysis | Zero authority β requires Robert approval before production action |
| Resource | URL | Access |
|---|---|---|
| Production site | https://callbrightside.com/ | Public Β· Oxygen theme currently |
| Staging site | https://bricks.callbrightside.com/ | Public (noindex) |
| Morpheus docs | https://morpheus.callbrightside.com/documents/ | Public |
| Codebase Doc | .../BSP_Bricks_Codebase_Documentation.html | Public |
| Master History | .../BSP_Master_Session_History.html | Public |
| This reference | .../BSP_Ecosystem_Handshake_Reference.html | Public |
| Nexus VM | dovew@34.55.179.122 :22 | SSH key required |
| ServiceTitan | tenant URL TBD Β· API base api.servicetitan.io | Ashton (UI) Β· OAuth2 (API) |
| 3CX instance | URL TBD β ask LawnPhone.com | Admin creds |
| Telnyx dashboard | https://portal.telnyx.com/ | Robert account |
| Vapi dashboard | https://vapi.ai/ | Robert account |
| Cloudflare dashboard | https://dash.cloudflare.com/ | Robert account |
| Google Business Profile | Managed via business.google.com | Robert account Β· Ashton user |
| Hostinger (WP host) | https://hpanel.hostinger.com/ | Robert account |
| Change | Owner | Target | Status |
|---|---|---|---|
| Monday/Tuesday production launch β deactivate Oxygen, activate Bricks | Robert | Apr 27-28 2026 | Prep in progress |
| Tim's dismissed-queue worker (Deliverable C) | Tim | Post-launch | Blocked on Robert prereqs |
| Native Bricks header template (Phase G) | Claude Code | Pre-launch recommended | G0 context done; awaiting Robert go |
| 13 remaining city pages (Friday clone batch) | Claude Code via pipeline | Pre-launch | Pipeline ready; bulk generation deferred |
| Β§32 Tier 1 data-accuracy strip (4.9/384+/15-sec/60-min) | Claude Code | Pre-launch | Ready to ship on Robert go |
| Daniel booking flow (ST integration) | Robert + Daniel prompt design | Q2-Q3 2026 | Medium-term |
| Fleet API real-time integration (beyond schedule-based) | Robert | Q3-Q4 2026 | Long-term |
| Form submission β SMS handshake (Deliverable A) | Tim | Week after Deliverable C | Planned; addresses Tim Mahony pain point |
wp_snippets DB table, executed via WP hooks.wp_options.bricks_global_classes.wp_options.bricks_theme_styles.@layer) that manages priority by layer order instead of specificity. Bricks 2.0+ wraps defaults in @layer bricks β but we verified this NOT active on our render output (Apr 23)._bricks_page_content_2wp_footer / wp_head hooksdata_weapons_plan_v2.html (NOT BSP_Sacred_HTML_v2.html).BSP_Master_Session_History.html. Load-bearing for Zeus RAG retrieval between Claude Code sessions.