Skip to content

JB360 / ServiceTitan Integration — System Overview

Repository: jbandassociates/st-integration · AWS account 660537473428, us-east-1

JB360 connects HVAC/plumbing dealers’ field-service CRMs — ServiceTitan (primary) and Workiz (secondary) — to the JB Warranties extended-warranty platform (public-api.jbwarranties.com / the JB&Associates member portal).

For an onboarded dealer, the system continuously:

  1. Pulls the dealer’s invoices, locations, and installed equipment out of ServiceTitan into a MySQL staging database (jbwst).
  2. Matches ServiceTitan locations against the dealer’s existing JB Warranties agreements (stored-procedure matching, with a UI for manual mapping and approval).
  3. Creates warranty agreements in the JB Warranties platform from approved invoice/equipment data.
  4. Pushes agreement references and claim statuses back into ServiceTitan (location tags, custom fields, notes, equipment warranty dates, job updates), so technicians see warranty coverage inside their CRM.
  5. Syncs claims bidirectionally (JBW claim status → staging DB → ServiceTitan jobs).
ModuleWhat it isStack
jbwuiThe JB360 web portal (admin + dealer) at jbw360.jbwarranties.comReact 18 / MUI / CRA, Cognito auth
jbw-samServerless backend: ~65 Lambdas + API Gateway + SPA hosting (S3/CloudFront), sync orchestration, JBW API integrationAWS SAM, Node.js 20, MySQL via RDS Proxy
extractionHeavy-duty per-tenant ServiceTitan sync worker (invoice/equipment pull + agreement/claim push), triggered via SQSJava 17 / Spring Boot 3 Lambda
flowchart TB
Browser["<b>Browser</b><br/>jbw360.jbwarranties.com"]:::ui

subgraph AWS["AWS · us-east-1"]
  direction TB
  CF["<b>CloudFront + S3</b><br/>jbwui SPA"]:::aws
  APIGW["<b>API Gateway</b><br/>Cognito-authorized"]:::aws
  Lambdas["<b>jbw-sam</b><br/>~65 Node.js Lambdas"]:::aws
  EB["<b>EventBridge</b><br/>daily / hourly schedules"]:::aws
  SQS["<b>SQS</b> prodtenantList<br/>one message per tenant"]:::aws
  EXT["<b>extraction</b><br/>Java 17 Lambda · per-tenant sync"]:::aws
  SES["<b>SES</b><br/>notification emails"]:::aws
  DB[("<b>MySQL jbwst</b><br/>RDS Proxy · IAM auth<br/>44 tables · 10 stored procs")]:::db
end

subgraph EXTS["External services"]
  direction LR
  STAPI["<b>ServiceTitan API</b><br/>api.servicetitan.io"]:::ext
  JBWAPI["<b>JB Warranties API</b><br/>public-api.jbwarranties.com"]:::ext
  WKAPI["<b>Workiz API</b>"]:::ext
  Portal["<b>JB&Associates portal</b><br/>claims iframes"]:::ext
end

Browser --> CF
Browser -->|REST + JWT| APIGW --> Lambdas
EB --> Lambdas
Lambdas --> SES
Lambdas <--> DB
Lambdas -->|getTenantInfo| SQS --> EXT
EXT <--> DB
EXT <-->|invoices in · tags & claims out| STAPI
Lambdas <-->|locations · verification| STAPI
Lambdas <-->|agreements · claim status| JBWAPI
Lambdas --> WKAPI
Browser -.-> Portal

classDef ui fill:#dbeafe,stroke:#3b82f6,color:#1e3a8a,stroke-width:1.5px
classDef aws fill:#fff7ed,stroke:#f59e0b,color:#7c2d12,stroke-width:1.5px
classDef db fill:#dcfce7,stroke:#22c55e,color:#14532d,stroke-width:1.5px
classDef ext fill:#f3e8ff,stroke:#a855f7,color:#581c87,stroke-width:1.5px

jbwui wizard → POST /newdealeronboard (jbwnewonboarding) → verifies ServiceTitan credentials and required tags/custom fields (JBWClientServiceTitanVerification) or Workiz account → stores tenant config in tblTenant/tblDealerOnboard → kicks off an initial data match (jbwagreementsdatamatch).

GET /agreementsync/{dealer}/spcall (UI “Sync Data” button, per tenant) → jbwagreementsdatamatch pulls JBW agreements → tblJBWAgreementData and ST locations → tblSTLocationData (Workiz: job locations → tblWorkizLocationData) → stored proc jbw_st_dataSync matches them → UI grid (Map Location / Pending Approval / Approved) → PUT /agreementsync/approve.

PUT /agreements/{id}/equipmentsync (jbwservicetitansagreementspull) builds the payload from tblAgreement + tblAgreementEquipments and POSTs to the JBW agreements API; writes back jbwAgreementId, PDF URL, warranty dates, status SAVED.

Per-tenant ServiceTitan sync (the extraction loop)

Section titled “Per-tenant ServiceTitan sync (the extraction loop)”

EventBridge Scheduler → getTenantInfo → one SQS message per active tenant (tenantId, dealerId, ST OAuth creds, batchId, syncStartDate) → extraction Java Lambda: pulls invoices/contacts/equipment into the DB, pushes PAID agreements (isSentToST=0) and ready claims (isReadyForST=1) into ServiceTitan as tags/custom fields/notes/job updates → audit row in tblTransmissionDetails(+History), visible in the UI Transaction Log.

UI creates potential claims → dealer files in the JB&Associates iframe → PATCH /claims/stupdate (jbwclaimsstupdate) pulls the JB360ClaimStatus view from the JBW API, updates tblClaims, and flags rows for the next extraction run to push into ServiceTitan jobs (Filed/Paid/Declined tags + custom fields).

ProductionStaging/Dev
UIjbw360.jbwarranties.com, prod API Gateway stagestage.env build → dev API Gateway stage
Cognito (portal)pool per .env/samconfigseparate stage pool
ServiceTitango.servicetitan.comintegration.servicetitan.com
SAM stackjbw-prod-api (only stack in samconfig.toml)none defined in repo

Git history is release-based (“January Release”, “Spring Release Changes”, “Renewal Functionality changes”), roughly quarterly.

  • Auth is attached out-of-band: the SAM template deploys API Gateway with no authorizer; a manually-managed Lambda (jbwapiauthorizerupdate) attaches the Cognito authorizer after each deploy. See jbw-sam. (Live check 2026-07-07: the authorizer IS currently attached on prod and dev — see 05-deployed-infrastructure.md.)
  • DB credentials: production paths use RDS IAM auth (no passwords); but plaintext dev creds exist in extraction/src/main/resources/application.yml.
  • Hardcoded secrets: ServiceTitan app key + JBW API token in jbw-sam/template.yaml/samconfig.toml; Workiz token in handler source; AES key in jbwui/src/utils/encryption.js. No SSM/Secrets Manager in the SAM stack.
  • SQL injection surface: both jbw-sam handlers and parts of extraction build SQL by string interpolation.
  • Template drift: ~30 Lambda source dirs are not in template.yaml (console-managed); the template also has an indentation bug that breaks validation (see jbw-sam caveats).