WHOOP·DECODE

device link · architecture

← datastore dashboard
how we get data off the strap

A one-way radio link, drained in order, on a schedule.

The WHOOP 4.0 strap has no display and no internet. It quietly records your body about once a second into a two-week memory — even with no phone nearby. Everything below is how our app reaches across Bluetooth, pulls that history off, and turns it into the charts on the dashboard. No phone, no problem; the strap just keeps a buffer until we catch up.

01 · the link 02 · connect 03 · what we get 04 · the offload 05 · cadence 06 · data path
02

THE CONNECT HANDSHAKE

Every time the phone reaches the strap, it runs the same short opening sequence — exactly once per connection — to wake the link, agree on the time, and line up the history transfer. It mirrors what WHOOP’s own app does. Running it more than once (an early bug) jams the strap, so it’s strictly one pass, then the data flows.

1
Discover & connectfind the strap’s radio and open the BLE link.
2
Bonda confirmed handshake write — proves the secure pairing and unlocks the private channels.
3
Subscribetell the strap we want to listen on the responses, events, and bulk-data channels.
4
Say helloa small identity exchange the strap expects before it’ll cooperate.
5
Set the clockpush the current time so every record the strap logs is stamped correctly.
6
Quiet the firehoseturn off the raw high-rate research stream so it doesn’t hog the radio.
7
Read the rangeask how much unsynced history is waiting in the strap’s memory.
↓
Begin offloadstream the stored history across — see section 04.handshake runs once · then data flows
03

WHAT WE GET

Two kinds of information come off the strap. Live readings are streamed in the moment (only when you ask for them). The stored history — the real prize — is a two-week, roughly one-per-second log of your biometrics that the strap keeps whether or not a phone is around. Plus housekeeping: events, battery, and firmware info.

⤓

The 14-day biometric store  ·  primary source

The strap records ~1 sample/second into a rolling two-week memory, on-wrist, offline. On every sync we offload this log. It’s the backbone of everything on the dashboard. Each record carries:

Heart rate Heart-rate variability Blood oxygen (SpO₂) Skin temperature Respiratory rate Motion / gravity
logged continuously · ~1 Hz · retained ~14 days
♥

Live heart rate

Real-time beats-per-minute plus beat-to-beat timing, streamed as it happens.

opt-in · on demand
⚑

Events

The strap announces moments: boot, clock set, wrist on/off, and other state changes.

pushed when they happen
▮

Battery & firmware

Charge level, charging state, and the strap’s hardware / firmware version.

read on connect
∿

Derived on the server

Sleep, recovery, strain and daily summaries are computed from the stored signals after they land.

computed after sync
04

THE OFFLOAD — READ IN ORDER

The history transfer is a strict, in-order drain. We ask once; the strap streams its log oldest-first in chunks. The crucial detail: the strap only lets go of a chunk once we acknowledge it, and that acknowledgement moves a one-way read cursor forward — records behind it are considered delivered and can’t be served again. So the golden rule is save first, acknowledge second.

HISTORY START→ records→ CHUNK END↺ records→ CHUNK END→ COMPLETE
A
Requestone “send history” command kicks it off.
B
Streamthe strap pours records down the bulk channel, closing a chunk every so often.
C
Decode & savethe phone unpacks each chunk and writes it to local storage.
D
Acknowledge → trimonly now do we ack — which advances the strap’s read cursor past that chunk.
E
Repeat → COMPLETEloop until the strap signals it’s caught up.
save → then
acknowledge

The cursor is one-way: once a chunk is acked, the strap drops it for good. We never ack a chunk we haven’t durably stored.

Break that order and the cursor races past data that was never kept — a permanent gap. Keeping it is what makes the sync lossless.
05

CADENCE & SCHEDULING

We don’t sync constantly — that would drain both batteries. Instead a sync is triggered by a few events, each rate-limited by a cooldown so bursts don’t pile up. The strap keeps logging the whole time, so whenever the next sync fires, the two-week buffer has us covered.

⇄ On connect

Each time the phone (re)reaches the strap, it offloads whatever piled up.

first connect always runs

◷ Periodic timer

While connected, a background timer kicks a routine catch-up.

~15 min floor

▦ App foreground

Opening the app nudges a fresh sync so you see current data.

~90 s cooldown

⚑ Strap event

The strap can prompt its own sync by pushing an event.

~90 s cooldown

⤓ Manual “sync now”

A tap forces an immediate offload, no cooldown.

on demand

♥ Live HR

Real-time streaming runs only while you explicitly keep it on.

opt-in only
◉ always-on The strap logs ~1 sample/second to its 14-day memory regardless of the phone — uploads to the server then happen opportunistically and on a timer during each session. Miss a day? The next sync simply drains the backlog in order.
06

END-TO-END DATA PATH

Following a single reading from your wrist to this dashboard:

⌾STRAP
logs to 14-day flash, ~1 Hz, offline
📶BLE OFFLOAD
chunked & acked, in order
▤PHONE
decodes + saves to local store
⛁SERVER
uploaded to the datastore
◍DASHBOARD
charts + derived insights
WHOOP·DECODE — independent reverse-engineered client · device link architecture  ·  back to the datastore →