Skip to main content

django-whoop

CI

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 WhoopConnection saved 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)

Source distribution for django-whoop 0.2.1
File Size Uploaded Provenance
django_whoop-0.2.1.tar.gz 64.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-whoop 0.2.1
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page