Overview
The Whoop 4.0 can broadcast real-time heart rate data over standard BLE Heart Rate Service (UUID 0x180D). This allows third-party apps and devices to read heart rate without using the Whoop app.
Broadcast Toggle Commands
Broadcast is controlled by sending toggle commands to CMD_TO_STRAP:
aa0800a823 07 0e 00 c7e40f08 # Off
aa0800a823 08 0e 01 6c935474 # On
aa0800a823 09 0e 00 cdc99102 # Off
Packet Structure
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 bytes | Header | Always aa0800a823 |
| 0x05 | 1 byte | Packet Count | Increments with each packet |
| 0x06 | 1 byte | Category | 0x0e for heart rate broadcast |
| 0x07 | 1 byte | State | 0x00 = Off, 0x01 = On |
| 0x08 | 4 bytes | Checksum | CRC-32 with custom parameters |
The packet count field is not validated by the device. You can use any value.
Enabling Broadcast
# Turn ON heart rate broadcast
sudo gatttool -i hci0 -t random -b XX:XX:XX:XX:XX:XX \
--char-write -a 0x0010 -n aa0800a823080e016c935474
# Turn OFF heart rate broadcast
sudo gatttool -i hci0 -t random -b XX:XX:XX:XX:XX:XX \
--char-write -a 0x0010 -n aa0800a823070e00c7e40f08
Using Python (bleak)
After enabling broadcast, you can read heart rate using the standard BLE Heart Rate Service:
import asyncio
from bleak import BleakClient, BleakScanner
HEART_RATE_SERVICE = "00002a37-0000-1000-8000-00805f9b34fb"
async def read_heart_rate(address):
device = await BleakScanner.find_device_by_address(address)
if not device:
print("Device not found")
return
async with BleakClient(device) as client:
def callback(sender, data):
# First byte contains flags
# Second byte is heart rate value
hr = int(data[1])
print(f"Heart Rate: {hr} bpm")
await client.start_notify(HEART_RATE_SERVICE, callback)
await asyncio.sleep(60) # Read for 60 seconds
await client.stop_notify(HEART_RATE_SERVICE)
# Run with your device MAC address
asyncio.run(read_heart_rate("XX:XX:XX:XX:XX:XX"))
Reading Heart Rate
Once broadcast is enabled, the device advertises on the standard Heart Rate Service:
- Service UUID:
0x180D (Heart Rate)
- Characteristic UUID:
00002a37-0000-1000-8000-00805f9b34fb (Heart Rate Measurement)
- Update Rate: ~1 second
The heart rate characteristic follows the Bluetooth SIG specification:
| Byte | Field | Description |
|---|
| 0 | Flags | Format and sensor contact status |
| 1 | HR Value | Heart rate in beats per minute (BPM) |
Device Visibility
The Whoop 4.0 only appears in BLE scans when heart rate broadcast is enabled. When broadcast is off, the device cannot be discovered by third-party apps.
Testing Broadcast State
# Try to find the device (will fail if broadcast is off)
python3 enable_notifications.py --address XX:XX:XX:XX:XX:XX \
00002a37-0000-1000-8000-00805f9b34fb
If you get “ERROR: could not find device”, broadcast is disabled.
App Behavior
The Whoop app cannot read the broadcast state from the device. If broadcast is enabled in the app, it periodically sends 0x01 commands to ensure it stays on.
Characteristic Details
CMD_TO_STRAP (Control)
- UUID:
61080002-8d6d-82b8-614a-1c8cb0f8dcc6
- Handle:
0x0010
- Properties: Write only
Heart Rate Measurement (Read)
- UUID:
00002a37-0000-1000-8000-00805f9b34fb
- Properties: Notify
Use Cases
- Third-party fitness apps: Connect Whoop to Strava, Zwift, etc.
- External displays: Show heart rate on bike computers or smartwatches
- Custom monitoring: Build your own heart rate logging system
- Integration: Use Whoop as a heart rate sensor for any app that supports standard BLE HR