A Django app for the WHOOP API (v2), backed by django-healthdatamodel.
Project description
django-whoop
A reusable Django app for the official WHOOP API (v2). Handles the WHOOP OAuth 2.0 flow, fetches cycles, sleep, recovery, and workouts from api.prod.whoop.com, and persists them through django-healthdatamodel so the same storage and query layer serves Apple Health, Fitbit, Google Health, Garmin, Oura, and WHOOP side-by-side.
v0.2.0 is a ground-up rewrite. Versions ≤ 0.1.3 used WHOOP's unofficial password-grant API (
api-7.whoop.com), which no longer works, and stored data in their own tables. There is no migration path from the old models — uninstall the old app, delete its tables, and start fresh.
Install
pip install django-whoop
Add both this app and django-healthdatamodel to INSTALLED_APPS, then run migrations:
INSTALLED_APPS = [
...
"healthdatamodel",
"whoop",
]
python manage.py migrate
The model uses settings.AUTH_USER_MODEL so it works with any custom user model.
Include the URLs:
path("whoop/", include("whoop.urls")),
Configuration
WHOOP_CLIENT_ID = "..." # from the WHOOP developer dashboard
WHOOP_CLIENT_SECRET = "..."
WHOOP_REDIRECT_URI = "https://your-app.example.com/whoop/callback/"
# Optional:
WHOOP_DEFAULT_SCOPES = [...] # default: all read scopes + offline
WHOOP_CONNECT_SUCCESS_URL = "/" # default: /admin/
Create the app at the WHOOP developer dashboard (any WHOOP member can). See docs/whoop/oauth.md for the flow details — notably, the offline scope is what makes WHOOP issue a refresh token, and WHOOP rotates the refresh token on every refresh.
Storage
This app does not define Record / Workout tables — those live in django-healthdatamodel. The whoop.ingest module maps WHOOP API responses to healthdatamodel.schemas.RecordInput and WorkoutInput, then persists them via healthdatamodel.ingest. Read the data back with healthdatamodel.query.*.
The only model defined here is WhoopConnection: per-user OAuth tokens, granted scopes, connection status, and last sync timestamp.
What maps where (details + caveats in the whoop/ingest.py docstring):
| WHOOP | healthdatamodel |
|---|---|
| Cycle strain | Record type WHOOPStrain |
| Cycle kilojoules (total energy) | Record ACTIVE_CALORIES (kcal) |
| Sleep stage totals | Record sleep-analysis entries (synthetic sequential intervals) |
| Sleep respiratory rate | Record HK respiratory rate |
| Recovery score / RHR / HRV / SpO2 / skin temp | Record (WHOOPRecoveryScore + HK types) |
| Workout (sport, energy, distance, HR zones) | Workout + metadata entries |
| Body measurement (height, weight) | Record HK height / body mass |
Webhooks
Point the webhook URL in the developer dashboard at /whoop/webhooks/. Every delivery is HMAC-verified against WHOOP_CLIENT_SECRET; authenticated events emit the whoop.signals.event_received signal. Wire a handler to whoop.webhooks.process_event (inline or via a queue) to fetch + ingest the changed resource:
from django.dispatch import receiver
from whoop.signals import event_received
from whoop.webhooks import process_event
@receiver(event_received)
def on_event(sender, payload, **kwargs):
process_event(payload)
Documentation
The WHOOP API documentation is summarized as Markdown under docs/whoop/ so it's grep-able offline: oauth.md, api.md (endpoints + payload shapes from the OpenAPI spec), webhooks.md.
Try it on your own data
The repo includes a runnable demo Django project at demo/ that takes you
through the full OAuth flow and syncs your WHOOP data into healthdatamodel.
1. Set up a WHOOP developer app (one-time)
At https://developer-dashboard.whoop.com/ create a team, then an app:
- Redirect URL:
http://localhost:8000/whoop/callback/(exact match, trailing slash included). - Scopes: enable all the read scopes plus
offline(read:recovery,read:cycles,read:sleep,read:workout,read:profile,read:body_measurement,offline). - Copy the Client ID and Client Secret.
You'll authenticate as your own WHOOP account — no review process needed for personal use.
2. Run the demo
uv sync
uv run python manage.py migrate
uv run python manage.py createsuperuser
export WHOOP_CLIENT_ID=...
export WHOOP_CLIENT_SECRET=...
uv run python manage.py runserver
Open http://localhost:8000/, sign in with the superuser you just created, then:
- Click Connect WHOOP → consent at WHOOP → land back on the homepage
with a
WhoopConnectionsaved for your user. - Pick a window and click Sync now to fetch and persist records.
- Browse the resulting rows at
/admin/healthdatamodel/record/(and.../workout/).
If you'd rather drive sync from the terminal:
uv run python manage.py sync_whoop --user <your-username> --days 7
Development
uv sync --group dev
uv run pytest tests/ -v
uv run pre-commit run --all-files
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_whoop-0.2.0.tar.gz.
File metadata
- Download URL: django_whoop-0.2.0.tar.gz
- Upload date:
- Size: 64.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
696356891c42c0619864ed26602aee98206c1d24a970689ed60575ccbe56e028
|
|
| MD5 |
eb0271b822c1175d1d195dee7501fa24
|
|
| BLAKE2b-256 |
ce9b1559f72f85a23c2efd2d9053f888b63209a3ad2ffc16666acadfdedd44a2
|
Provenance
The following attestation bundles were made for django_whoop-0.2.0.tar.gz:
Publisher:
ci.yml on django-health/django-whoop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_whoop-0.2.0.tar.gz -
Subject digest:
696356891c42c0619864ed26602aee98206c1d24a970689ed60575ccbe56e028 - Sigstore transparency entry: 2129984966
- Sigstore integration time:
-
Permalink:
django-health/django-whoop@328e2e3144db9c47996e68dd5c03e4835037e1a2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/django-health
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@328e2e3144db9c47996e68dd5c03e4835037e1a2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_whoop-0.2.0-py3-none-any.whl.
File metadata
- Download URL: django_whoop-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2bf1fc2d1cbfda53dc561ccc6ea0f0eec2b53ae3ad275f919cf7e8337940c5a9
|
|
| MD5 |
8208ea35bd61215254bd351ea67cd051
|
|
| BLAKE2b-256 |
8d62a04a8744be79ce1a74dc3c72d219682321c5173ba15b6cfd6710f049530e
|
Provenance
The following attestation bundles were made for django_whoop-0.2.0-py3-none-any.whl:
Publisher:
ci.yml on django-health/django-whoop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_whoop-0.2.0-py3-none-any.whl -
Subject digest:
2bf1fc2d1cbfda53dc561ccc6ea0f0eec2b53ae3ad275f919cf7e8337940c5a9 - Sigstore transparency entry: 2129985015
- Sigstore integration time:
-
Permalink:
django-health/django-whoop@328e2e3144db9c47996e68dd5c03e4835037e1a2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/django-health
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@328e2e3144db9c47996e68dd5c03e4835037e1a2 -
Trigger Event:
push
-
Statement type: