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
Metadata
Release files for django-whoop 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_whoop-0.2.1.tar.gz | 64.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_whoop-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 92.7 kB
Release files / django_whoop-0.2.1.tar.gz
| Download URL | django_whoop-0.2.1.tar.gz |
|---|---|
| Size | 64.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
11b6554e0de909a8f501a1ca2a0e3cbc559913e91051e365c5364b51c1ae8af3
|
|
BLAKE2b-256 checksum How to use checksums |
3e15614c5b75a39cc4451f8ca7b79879455ef9a0431e7b769264c54740cdfd9b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / django_whoop-0.2.1-py3-none-any.whl
| Download URL | django_whoop-0.2.1-py3-none-any.whl |
|---|---|
| Size | 28.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd90a835bb00d1cfc7375f38242be324b9537fd09301e782de994983d6d0b6f0
|
|
BLAKE2b-256 checksum How to use checksums |
81f7e1366a1c0f339d38745c30dbd914b265273e1510b803adc1da84c7b69ba9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logSecurity for django-whoop 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for django_whoop-0.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.