Skip to main content

Toll Sync Architecture

HostMetrics supports automatic toll transaction syncing from three US toll providers.

Supported Providers

ProviderRegionColumns
NTTATexas (Dallas/Fort Worth)Transaction Date, Transaction Time, Plaza/Gantry Name, Roadway, Amount, Tag Number, Vehicle
EZPassNortheast USDate, Time, Plaza, Agency, Amount, Tag ID, Plate
SunPassFloridaDate, Time, Plaza, Road, Amount, Transponder, Plate Number

System Architecture

Manual CSV Import Flow

Date Handling (Critical)

Toll dates are stored as plain date + text time fields — NOT as UTC timestamps. Why? Toll agencies report transactions in their operating timezone (e.g., NTTA uses CST). Since the host is typically in the same timezone as the agency, the dates are already correct for trip matching. The Rule: Never use toISOString() when parsing toll dates. It converts to UTC and can shift the date by ±1 day.
// CORRECT - preserves local date
const parsed = new Date(dateStr);
const y = parsed.getFullYear();
const m = String(parsed.getMonth() + 1).padStart(2, "0");
const d = String(parsed.getDate()).padStart(2, "0");
txDate = `${y}-${m}-${d}`;

// WRONG - shifts to UTC, may change the date
txDate = new Date(dateStr).toISOString().split("T")[0]; // DON'T DO THIS

Trip-to-Toll Matching

Tolls are matched to trips by comparing dates:
  1. Trip trip_end date (converted from UTC to user’s business timezone using formatDateTime())
  2. Toll transaction_date (stored as-is in agency local time)
  3. Both are compared as YYYY-MM-DD strings
This works because both dates are effectively in the same local timezone (the host’s market).

Lambda Worker Architecture

lambda/
├── toll-sync-dispatcher/
│   └── handler.ts         # Triggered by cron, dispatches per-account jobs
├── toll-sync-worker/
│   ├── handler.ts          # Main worker entry point
│   ├── matcher.ts          # Trip-to-toll matching logic
│   └── scrapers/
│       ├── base.ts         # Scraper interface
│       ├── ntta.ts         # NTTA website scraper
│       └── index.ts        # Scraper registry
└── serverless.yml          # Serverless framework deployment config
The dispatcher queries toll_accounts for active accounts and invokes a worker Lambda for each one. Workers scrape the toll portal, parse transactions, and insert them directly into Supabase.