Overview
Connects a Vitals7 user's WHOOP account, stores OAuth tokens in DynamoDB
vitals-di-tokens (key {cognitoSub}#whoop), and writes recovery, sleep,
cycle and workout readings to user_vitals.
http://localhost:8084WHOOP API:
https://api.prod.whoop.com/developer (v2)Webhook URL:
https://dev-whoop-api.vitals7.com/webhook
Authentication
OAuth 2.0. The state parameter carries the Vitals7 Cognito user id, and
offline scope is required to receive a refresh token.
app_redirect=vitals7://… for the mobile app.WHOOP_REDIRECT_URI and WHOOP_WEBHOOK_URL exactly, and the webhook
Model Version must be v2.
API Endpoints
deep=1 also probes DynamoDB.{"cognitoUserId":"…","days":30}. Fetches and writes to user_vitals.{"cognitoUserId":"…"}. Revokes WHOOP access (stopping webhooks) and deletes the token row.Webhooks
WHOOP posts {user_id, id, type, trace_id} for
sleep.updated, sleep.deleted, recovery.updated,
recovery.deleted, workout.updated and workout.deleted.
Each request is signature-checked, de-duplicated by trace_id, answered
204 immediately, then synced on a debounced per-user queue.
/webhook/whoop). GET/HEAD return 200 for health checks.Signature:
base64(HMAC_SHA256(X-WHOOP-Signature-Timestamp + rawBody, WHOOP_CLIENT_SECRET))
compared against X-WHOOP-Signature. Requests older than
WHOOP_WEBHOOK_MAX_SKEW_MS (default 5 min) are rejected.
Because deliveries can be missed, a reconciliation pass also runs
every 60 min —
set WHOOP_RECONCILE_INTERVAL_MIN.
Data mapping
| WHOOP | vitalType | units |
|---|---|---|
| recovery.resting_heart_rate | heart-rate | bpm |
| recovery.hrv_rmssd_milli | heart-rate-variability | ms |
| recovery.spo2_percentage | oxygen-saturation | % |
| recovery.skin_temp_celsius | body-temperature | °F |
| recovery.recovery_score | recovery-score | % |
| sleep stage summary (light+SWS+REM) | sleep-hours | hours |
| sleep.respiratory_rate | respiratory-rate | breaths/min |
| cycle.strain | strain | score |
| cycle / workout average_heart_rate | heart-rate | bpm |
| cycle / workout kilojoule | total-calories | kcal |
| workout.distance_meter | distance | km |
| workout duration | exercise-duration | minutes |
| body.weight_kilogram | weight | lbs |
| body.height_meter | height | inches |
| weight + height | bmi | kg/m² |
score_state !== 'SCORED'). Naps are excluded
from sleep-hours unless WHOOP_INCLUDE_NAPS=1.
Examples
# Connect
open "http://localhost:8084/login?state=YOUR_COGNITO_SUB"
# Status
curl "http://localhost:8084/api/whoop/status?userId=YOUR_COGNITO_SUB"
# Manual sync
curl -X POST "http://localhost:8084/api/whoop/sync" \
-H "Content-Type: application/json" \
-d '{"cognitoUserId":"YOUR_COGNITO_SUB","days":30}'
# Reconcile everyone now
curl -X POST "http://localhost:8084/api/whoop/reconcile"
# Disconnect
curl -X POST "http://localhost:8084/api/whoop/disconnect" \
-H "Content-Type: application/json" \
-d '{"cognitoUserId":"YOUR_COGNITO_SUB"}'