Command Packets (CMD_TO_STRAP)
Most commands use this 12-byte format:
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa0800a823 |
| 0x05 | 1 | Packet Count | Increments with each command (not validated) |
| 0x06 | 1 | Category | Command category (see Command Catalog) |
| 0x07 | 1 | Value | Command-specific value (usually 0x00 or 0x01) |
| 0x08 | 4 | Checksum | CRC-32 with custom parameters |
Example: Heart Rate Broadcast On
Offset: 00 01 02 03 04 05 06 07 08 09 0a 0b
Data: aa 08 00 a8 23 08 0e 01 6c 93 54 74
└─────┬─────┘ │ │ │ └─────┬─────┘
Header │ │ │ Checksum
│ │ Value (0x01 = On)
│ Category (0x0e = Heart Rate)
Packet Count
Used for alarm and batch data commands (24-byte format):
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa10005723 |
| 0x05 | 1 | Packet Count | Increments with each command |
| 0x06 | 1 | Category | Command category |
| 0x07 | 1 | Flag | Usually 0x01 for these commands |
| 0x08 | 4 | Payload | Unix timestamp or batch number (little-endian) |
| 0x0c | 8 | Padding | Always 00 00 00 00 00 00 00 00 |
| 0x14 | 4 | Checksum | CRC-32 with custom parameters |
Example: Set Alarm for 7:00 AM
Offset: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
Data: aa 10 00 57 23 6d 42 01 d0 36 65 66 00 00 00 00 00 00 00 00 f6 2d eb 81
└─────┬─────┘ │ │ │ └─────┬─────┘ └──────┬──────┘ └─────┬─────┘
Header │ │ │ Unix Time Padding Checksum
│ │ Flag (0x01)
│ Category (0x42 = Alarm)
Packet Count
Special format with magic bytes (24-byte format):
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa10005723 |
| 0x05 | 1 | Packet Count | Increments with each command |
| 0x06 | 1 | Category | 0x19 (Erase command) |
| 0x07 | 8 | Magic | fe fe fe fe fe fe fe fe |
| 0x0f | 1 | Padding | 0x00 |
| 0x10 | 4 | Checksum | CRC-32 with custom parameters |
Response Packets
Heart Rate Data (DATA_FROM_STRAP)
Real-time heart rate data during activity tracking:
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa1800ff2802 |
| 0x05 | 4 | Unix Time | Timestamp (little-endian) |
| 0x09 | 2 | Unknown | Purpose unknown |
| 0x0b | 1 | Heart Rate | BPM value |
| 0x0c | 1 | RR Count | Number of RR intervals |
| 0x0d | 8 | RR Data | RR interval data |
| 0x15 | 2 | Unknown | Purpose unknown |
| 0x17 | 4 | Checksum | CRC-32 with custom parameters |
Example: Heart Rate 66 BPM
Header Unix S HR RR RR data Checksum
aa1800ff2802 ad896566 f065 42 01 67060000000000000101 3ba00d4d
aa1800ff2802 ae896566 f860 43 00 00000000000000000101 5025f793
aa1800ff2802 af896566 085c 42 00 00000000000000000101 add7df13
These packets are received every second when activity tracking is enabled via category 0x03 commands.
Batch Data Indicator (DATA_FROM_STRAP)
Indicates available batch data for retrieval:
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa1c00ab31 |
| 0x05 | 1 | Packet Count | Sequential counter |
| 0x06 | 1 | Flag | Always 0x02 |
| 0x07 | 4 | Unix Time | Timestamp (little-endian) |
| 0x0b | 6 | Unknown | Purpose unknown |
| 0x11 | 4 | Batch Number | Batch identifier (little-endian) |
| 0x15 | 8 | Unknown | Purpose unknown |
| 0x1d | 4 | Checksum | CRC-32 with custom parameters |
Example
unix unknown batch N checksum
aa1c00ab31 18 02 f65c7066 804043000000 2e470100 04000000000000 7f873cf3
aa1c00ab31 19 02 fb5c7066 704143000000 2e470100 04000000000000 f277ceb0
aa1c00ab31 1a 02 005d7066 684243000000 2e470100 04000000000000 6b7b573f
The batch number must be extracted and used in a category 0x17 command to retrieve the corresponding data.
Historical Data (DATA_FROM_STRAP)
Large packets containing historical metrics (96 bytes):
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa5c00f02f0c |
| 0x05 | 2 | Unknown | Purpose unknown |
| 0x07 | 2 | Sequence | Sequential value |
| 0x09 | 2 | Unknown | Purpose unknown |
| 0x0b | 4 | Unix Time | Timestamp (little-endian) |
| 0x0f | 8 | Unknown | Purpose unknown |
| 0x17 | 1 | Heart Rate | BPM value |
| 0x18 | 1 | RR Count | Number of RR intervals |
| 0x19 | 8 | RR Data | RR interval data |
| 0x21 | 65 | Unknown | Complex sensor data (unanalyzed) |
| 0x62 | 4 | Checksum | CRC-32 with custom parameters |
Example (first 31 bytes shown)
Header Time? Unix? Something HR RR RR data
aa5c00f02f0c 07 8bb7 0900 c8326966 e03c8054cc01 58 01 b902000000000000
aa5c00f02f0c 07 8cb7 0900 c9326966 f0378054cc01 58 01 b502000000000000
aa5c00f02f0c 07 8db7 0900 ca326966 f8328054cc01 58 02 b802b90200000000
The remaining 65 bytes contain unanalyzed sensor data. Their structure and meaning are not yet fully understood.
Event Data (EVENTS_FROM_STRAP)
Event notifications with timestamps:
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 | Header | Fixed value: aa2400fa30 or aa10005730 |
| 0x05 | 1 | Counter | Sequential counter |
| 0x06 | 2 | Event Type | Event identifier |
| 0x08 | 4 | Unix Time | Timestamp (little-endian) |
| 0x0c | varies | Payload | Event-specific data |
| -4 | 4 | Checksum | CRC-32 with custom parameters |
Example: Long Events
header unix checksum
aa2400fa30 b0 0300 2e316966 901f 140002 e900 0000 e90e 0000 01 01 0f 03 01 00 2f 01 000000 699d4a60
aa2400fa30 64 0300 6a316966 d02e 140002 f100 0000 ed0e 0000 01 01 01 04 01 00 2e 01 000000 2d6beb1e
aa2400fa30 28 0300 a6316966 703d 140002 f900 0000 f00e 0000 01 01 37 04 01 00 2d 01 000000 50e4148e
Example: Short Events
header unix checksum
aa10005730 5b 2100 3f326966 6854 0000 b0b2435b
aa10005730 65 2200 45326966 a866 0000 093b5aa6
aa10005730 66 1800 48326966 3012 0000 ef5360f0
The Unix timestamps in event packets do not always correspond to activity start/end times. Their exact purpose requires further investigation.
Checksum Calculation
All packets use CRC-32 with the following custom parameters:
import struct
from crccheck.crc import Crc32Base
def calculate_checksum(data):
"""
Calculate Whoop 4.0 custom CRC-32 checksum.
Args:
data: bytes or bytearray of packet data (excluding checksum)
Returns:
4-byte checksum as bytes (little-endian)
"""
crc = Crc32Base
crc._poly = 0x4C11DB7
crc._reflect_input = True
crc._reflect_output = True
crc._initvalue = 0x0
crc._xor_output = 0xF43F44AC
output_int = crc.calc(data)
return struct.pack("<I", output_int)
# Example usage
packet_data = bytearray.fromhex("aa0800a823080e01")
checksum = calculate_checksum(packet_data)
full_packet = packet_data + checksum
print(f"Packet: {full_packet.hex()}")
# Output: aa0800a823080e016c935474
CRC Parameters
| Parameter | Value |
|---|
| Polynomial | 0x4C11DB7 (CRC-32) |
| Initial Value | 0x0 |
| Reflect Input | True |
| Reflect Output | True |
| XOR Output | 0xF43F44AC |
| Byte Order | Little-endian |
This checksum was reverse-engineered using crcbeagle. See the source README for the full reverse engineering process.
BLE Characteristics
All packet communication uses these custom BLE characteristics:
| UUID | Name | Handle | Properties | Description |
|---|
61080002-8d6d-82b8-614a-1c8cb0f8dcc6 | CMD_TO_STRAP | 0x0010 | Write | Send commands to device |
61080003-8d6d-82b8-614a-1c8cb0f8dcc6 | CMD_FROM_STRAP | 0x0012 | Notify | Receive command responses |
61080004-8d6d-82b8-614a-1c8cb0f8dcc6 | EVENTS_FROM_STRAP | 0x0015 | Notify | Receive event notifications |
61080005-8d6d-82b8-614a-1c8cb0f8dcc6 | DATA_FROM_STRAP | 0x0018 | Notify | Receive data streams |
61080007-8d6d-82b8-614a-1c8cb0f8dcc6 | MEMFAULT | 0x001b | Notify | Debug/crash data |
All characteristics are part of the custom service UUID: 61080001-8d6d-82b8-614a-1c8cb0f8dcc6