Skip to main content

Command Packets (CMD_TO_STRAP)

Standard Command Format

Most commands use this 12-byte format:
OffsetSizeFieldDescription
0x005HeaderFixed value: aa0800a823
0x051Packet CountIncrements with each command (not validated)
0x061CategoryCommand category (see Command Catalog)
0x071ValueCommand-specific value (usually 0x00 or 0x01)
0x084ChecksumCRC-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

Extended Command Format

Used for alarm and batch data commands (24-byte format):
OffsetSizeFieldDescription
0x005HeaderFixed value: aa10005723
0x051Packet CountIncrements with each command
0x061CategoryCommand category
0x071FlagUsually 0x01 for these commands
0x084PayloadUnix timestamp or batch number (little-endian)
0x0c8PaddingAlways 00 00 00 00 00 00 00 00
0x144ChecksumCRC-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

Erase Device Format

Special format with magic bytes (24-byte format):
OffsetSizeFieldDescription
0x005HeaderFixed value: aa10005723
0x051Packet CountIncrements with each command
0x061Category0x19 (Erase command)
0x078Magicfe fe fe fe fe fe fe fe
0x0f1Padding0x00
0x104ChecksumCRC-32 with custom parameters

Response Packets

Heart Rate Data (DATA_FROM_STRAP)

Real-time heart rate data during activity tracking:
OffsetSizeFieldDescription
0x005HeaderFixed value: aa1800ff2802
0x054Unix TimeTimestamp (little-endian)
0x092UnknownPurpose unknown
0x0b1Heart RateBPM value
0x0c1RR CountNumber of RR intervals
0x0d8RR DataRR interval data
0x152UnknownPurpose unknown
0x174ChecksumCRC-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:
OffsetSizeFieldDescription
0x005HeaderFixed value: aa1c00ab31
0x051Packet CountSequential counter
0x061FlagAlways 0x02
0x074Unix TimeTimestamp (little-endian)
0x0b6UnknownPurpose unknown
0x114Batch NumberBatch identifier (little-endian)
0x158UnknownPurpose unknown
0x1d4ChecksumCRC-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):
OffsetSizeFieldDescription
0x005HeaderFixed value: aa5c00f02f0c
0x052UnknownPurpose unknown
0x072SequenceSequential value
0x092UnknownPurpose unknown
0x0b4Unix TimeTimestamp (little-endian)
0x0f8UnknownPurpose unknown
0x171Heart RateBPM value
0x181RR CountNumber of RR intervals
0x198RR DataRR interval data
0x2165UnknownComplex sensor data (unanalyzed)
0x624ChecksumCRC-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:
OffsetSizeFieldDescription
0x005HeaderFixed value: aa2400fa30 or aa10005730
0x051CounterSequential counter
0x062Event TypeEvent identifier
0x084Unix TimeTimestamp (little-endian)
0x0cvariesPayloadEvent-specific data
-44ChecksumCRC-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

ParameterValue
Polynomial0x4C11DB7 (CRC-32)
Initial Value0x0
Reflect InputTrue
Reflect OutputTrue
XOR Output0xF43F44AC
Byte OrderLittle-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:
UUIDNameHandlePropertiesDescription
61080002-8d6d-82b8-614a-1c8cb0f8dcc6CMD_TO_STRAP0x0010WriteSend commands to device
61080003-8d6d-82b8-614a-1c8cb0f8dcc6CMD_FROM_STRAP0x0012NotifyReceive command responses
61080004-8d6d-82b8-614a-1c8cb0f8dcc6EVENTS_FROM_STRAP0x0015NotifyReceive event notifications
61080005-8d6d-82b8-614a-1c8cb0f8dcc6DATA_FROM_STRAP0x0018NotifyReceive data streams
61080007-8d6d-82b8-614a-1c8cb0f8dcc6MEMFAULT0x001bNotifyDebug/crash data
All characteristics are part of the custom service UUID: 61080001-8d6d-82b8-614a-1c8cb0f8dcc6

Build docs developers (and LLMs) love