Slicelytics monogram

Compare WHOOP API Responses

You build on WHOOP's API v2. A recovery.updated webhook arrives for a cycle you already stored, and the recovery you fetch now has a score that wasn't there an hour ago, or a different one. Slicelytics puts the two responses side by side and lists every field that changed. The responses stay on your computer.

See Which Recoveries Changed

Slicelytics diff of two WHOOP recovery responses: record 0 went from PENDING_SCORE to SCORED and gained its score, and record 2's recovery_score went from 71 to 77

The sample is one member's week of recoveries from GET /v2/recovery, one per cycle and newest first, fetched at 07:15 and again at 08:30 on 13 October. It's the same member and morning as the sleep sample of the wearable API guide, so each recovery's sleep_id is a sleep in that sample. Turn on Expand in the Diff view to see the changes:

  1. Record 0, the recovery after last night: score_state went from PENDING_SCORE to SCORED, and its score object appeared, with a recovery_score of 68. WHOOP includes score only once a recovery is scored.
  2. Record 2, the night of 10 October: at 06:02 UTC the member corrected that sleep's wake time, and its recovery_score went from 71 to 77. updated_at moved from 11 October to 13 October; created_at didn't.

Record 5 is UNSCORABLE in both responses, with no score, and the other four didn't change. Both responses are synthetic, written to WHOOP's v2 recovery model. The sample opens without an account.

What a WHOOP Webhook Tells You

A WHOOP webhook names the record that changed, not what changed in it. In the sample, the wake-time correction would have sent a sleep.updated and a recovery.updated, both with the sleep's ID:

{
  "user_id": 48213,
  "id": "9d2a7f40-1c6b-4e93-a5d8-6f0b3e2c8d71",
  "type": "recovery.updated",
  "trace_id": "5b0e7c2d-3f91-4a6e-8d14-c7a29f6e0b53"
}

In v2 webhooks, id is a UUID. For recovery.updated and recovery.deleted it is the ID of the sleep the recovery belongs to, its sleep_id, not the cycle. To fetch the recovery, get the sleep from /v2/activity/sleep/{sleepId}, then the recovery from /v2/cycle/{cycleId}/recovery with the sleep's cycle_id.

sleep.updated, workout.updated and recovery.updated each cover both a new record and a change to one, so the same ID can arrive several times. Save each response you fetch under its own name, and you can compare any two later. WHOOP retries a failed delivery five times over about an hour.

Make WHOOP Send an Update

WHOOP's docs describe how to trigger webhooks with your own account: log a short activity for yesterday, and you get a workout.updated; edit a past sleep's start or end time by a minute, and you get a sleep.updated and a recovery.updated. Fetch the records before and after, and Compare shows you exactly which fields your handler will see change. Reverting the edit afterwards sends webhooks too.

If You Still Have v1 Data

WHOOP no longer supports API v1 and no longer publishes v1 webhooks. In v2, sleeps and workouts have UUIDs instead of integer IDs, and recovery webhooks carry the sleep's UUID instead of the cycle ID. If your database still holds v1 IDs, GET /v1/activity-mapping/{activityV1Id} returns the v2 UUID; WHOOP intends it for a one-time migration, not for every request.

To check a migration, open a response you stored from v1 next to the v2 response for the same record. The Diff view shows the new ID, and any field your v1 parser read that is now named or nested differently.

Fetch the Same Range Both Times

The collection endpoints return up to 25 records per page, with a next_token when there are more, and sort recoveries by the start of their sleep, newest first. Compare matches records by position, so pass the same start and end to both requests, or a new recovery at the top shifts every other one. The wearable API guide has the sleep side of the same morning, with the curl commands.

For other providers, see Compare Oura API Responses, Compare Garmin Health API Data and Compare Terra Webhooks, for WHOOP data that reaches you through an aggregator.

Common Questions

Why is score missing from some records?

WHOOP includes score only when score_state is SCORED. While a sleep, cycle or recovery is PENDING_SCORE or UNSCORABLE, the record has no score object, so a comparison shows the whole object appearing once it is scored.

Why did a recovery change after a sleep edit?

WHOOP's webhook docs say that editing a past sleep's start or end time sends both a sleep.updated and a recovery.updated: the recovery is scored again from the edited sleep. Fetch both and compare each with the version you stored.

Can I compare a sleep with a recovery?

You can open them, but Compare matches fields by name, and the two models share only a few, such as user_id and cycle_id. Compare two responses from the same endpoint, and match a recovery to its sleep by sleep_id.

Does it work with workouts and cycles?

Yes. Any JSON response opens: /v2/activity/workout, /v2/cycle, a single record, or a whole page with its records list. See Compare Wearable API Responses for sleeps.

Does the data leave my computer?

No. Slicelytics reads the files in your browser and uploads nothing. If you share a comparison, the data travels in the part of the link after #, which browsers never send to a server, so treat the link like the data. Check WHOOP's API terms before you share a member's data with anyone.