Skip to Content
WearablesData Sync & Jobs

Data Sync & Background Jobs

Wearable data synchronization is managed through Junction Health (Vital API) integration, deployed as a Render.com cron job.

Junction Health Integration

Loop uses Junction Health (powered by Vital API) as a unified wearable data aggregator. This provides:

  • Single API for 50+ wearable devices (Oura, Whoop, Apple Health, Fitbit, etc.)
  • Automatic OAuth token management
  • Standardized data format across all devices
  • Historical data backfill support

Sync Jobs

wearable-sync-daily (Render.com Cron)

Schedule: Daily at 3:00 AM UTC Platform: Render.com (auto-deployed from render.yaml) Source: apps/background-jobs/wearable-sync

Syncs wearable data from Junction/Vital API to patient-graph database.

Process:

  1. Read user IDs from JUNCTION_SYNC_USER_IDS environment variable
  2. For each user:
    • Fetch last 7 days of wearable data from Junction API (configurable via JUNCTION_SYNC_LOOKBACK_DAYS)
    • Includes: sleep, activity, heart rate, recovery metrics
    • Store raw readings in patient_graph.patient_wearable_readings
    • Upsert daily aggregates in patient_graph.patient_wearable_daily_stats
  3. Retry failed syncs up to 3 times with exponential backoff
  4. Log comprehensive sync summary (users synced, records stored, errors)

Configuration:

// apps/background-jobs/wearable-sync/index.ts export async function runWearableSyncJob(config: { junctionApiKey: string; patientGraphApiUrl: string; patientGraphApiKey: string; userIds: string[]; lookbackDays?: number; // Default: 7 environment?: "production" | "sandbox"; region?: "us" | "eu"; }): Promise<Result<{ results: UserSyncSummary[] }>>

Environment Variables:

  • JUNCTION_API_KEY - Vital API key
  • PATIENT_GRAPH_API_URL - Patient Graph API endpoint
  • PATIENT_GRAPH_API_KEY - API authentication key
  • JUNCTION_SYNC_USER_IDS - Comma-separated Vital user IDs (e.g., user_123,user_456)
  • JUNCTION_SYNC_LOOKBACK_DAYS - Days to sync (default: 7)
  • JUNCTION_ENV - production or sandbox
  • JUNCTION_REGION - us or eu

Deployment:

# Automatically deployed on push to main via render.yaml git push origin main # Check deployment status # Visit: https://dashboard.render.com → wearable-sync-daily

syncWhoopData

Schedule: Daily

Dedicated Whoop data sync job for recovery, strain, sleep, and workout data.

syncCgmReadings

Schedule: Every 30 minutes

Syncs CGM readings from Dexcom and Libre devices.

Process:

  1. Query all active CGM connections
  2. For each connection:
    • Refresh OAuth tokens if needed
    • Fetch glucose readings since lastSyncAt
    • Store in patient_glucose_readings
    • Update lastSyncAt on the connection record

aggregateWearableData

Schedule: Daily after sync jobs

Computes daily statistics from raw wearable readings:

  • Average, min, max for each metric type
  • Rolling 7-day and 30-day averages
  • Stores in patient_wearable_daily_stats

cleanupWearableData

Schedule: Weekly

Removes expired or redundant wearable data:

  • Raw readings older than 90 days (daily stats are retained)
  • Orphaned records from disconnected devices
  • Duplicate readings

Manual Sync

Users can trigger a manual sync from the consumer app:

curl -X POST "https://my.loop.health/api/patient-graph/wearables/sync" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "source": "oura" }'

Or for the generic wearable endpoint:

curl -X POST "https://my.loop.health/api/wearables/oura/sync" \ -H "Authorization: Bearer $CLERK_JWT"

Error Handling

ErrorHandling
Token expiredAuto-refresh; if refresh fails, mark connection as inactive
Rate limitedExponential backoff with retry
API unavailableRetry up to 3 times, then skip user
Invalid dataLog warning, skip individual reading
Network timeout30-second timeout, retry

Monitoring

Sync job health is monitored via:

  • Trigger.dev dashboard — Job status, duration, failure rates
  • Sentry — Error tracking and alerting
  • Patient eventswearable_sync events for audit trail