Skip to main content

Overview

All packets sent to the Whoop 4.0 device include a 4-byte checksum for validation. The checksum is a custom CRC-32 variant with non-standard parameters.

Checksum Requirements

Packets sent without a valid checksum will be rejected by the device. The checksum must be calculated correctly for commands to work.
The checksum:
  • Is 4 bytes (32 bits) long
  • Appears at the end of every packet
  • Is in little-endian byte order
  • Covers all bytes before the checksum itself
  • Does not match any standard CRC-32 implementation (CRC-32, CRC-32/BZIP2, etc.)

Failed Attempts

Standard CRC calculators do not work. Testing with crccalc.com shows no matches:
Input: aa100057236d4201d036656600000000 (hex)
Expected: f62deb81

CRC-32:        [No match]
CRC-32/BZIP2:  [No match]
CRC-32/MPEG-2: [No match]
... etc

Reverse Engineering with crcbeagle

@colinoflynn created crcbeagle, a Python tool for reverse engineering CRC algorithms from sample data.

Installation

git clone https://github.com/colinoflynn/crcbeagle
cd crcbeagle
pip install -r requirements.txt

Analysis Script

Create examine.py with sample packets and their known checksums:
from crcbeagle.crcbeagle import CRCBeagle

crc = CRCBeagle()

def hex_to_int(hex):
    return [i for i in bytearray.fromhex(hex)]

# Sample packets (checksum removed)
data = """
aa100057236d4201d036656600000000f62deb81
aa100057236e42010c376566000000001023ccef
aa100057236f4201207d656600000000fea1e060
aa100057237042015011656600000000f226a8bd
""".split('\n')

data = [i for i in data if i]

# Extract checksums (last 8 hex chars = 4 bytes)
checksums = [i[len(i) - 8:] for i in data]

# Extract data (everything before checksum)
data = [i[:len(i) - 8] for i in data]

# Run the search
crc.search(
    [hex_to_int(i) for i in data],
    [hex_to_int(i) for i in checksums]
)

Running the Analysis

python examine.py

Output

crcbeagle will identify the CRC parameters and output working code:
import struct
from crccheck.crc import Crc32Base

crc = Crc32Base

def my_crc(message):
    crc._poly = 0x4C11DB7
    crc._reflect_input = True
    crc._reflect_output = True
    crc._initvalue = 0x0
    crc._xor_output = 0xF43F44AC
    output_int = crc.calc(message)
    output_bytes = struct.pack("<I", output_int)
    output_list = list(output_bytes)
    return (output_int, output_bytes, output_list)

Testing the CRC

# Test with known packet
m = [170, 16, 0, 87, 35, 109, 66, 1, 208, 54, 101, 102, 0, 0, 0, 0]
output = my_crc(m)
print(hex(output[0]))  # Should print: 0xf62deb81

CRC Parameters

The Whoop 4.0 uses these CRC-32 parameters:
ParameterValueDescription
Polynomial0x04C11DB7CRC-32 standard polynomial
Initial Value0x00000000Starting value
Reflect InputTrueBit-reverse each input byte
Reflect OutputTrueBit-reverse final CRC value
XOR Output0xF43F44ACNon-standard XOR mask (this is the key difference)
The custom XOR output value (0xF43F44AC) is what makes this CRC unique and incompatible with standard calculators.

Python Implementation

Complete implementation using the crccheck library:
import struct
from crccheck.crc import Crc32Base

class WhoopCRC:
    def __init__(self):
        self.crc = Crc32Base
        self.crc._poly = 0x4C11DB7
        self.crc._reflect_input = True
        self.crc._reflect_output = True
        self.crc._initvalue = 0x0
        self.crc._xor_output = 0xF43F44AC
    
    def calculate(self, data):
        """Calculate CRC for data (bytes or bytearray)"""
        crc_int = self.crc.calc(data)
        crc_bytes = struct.pack("<I", crc_int)  # Little-endian
        return crc_int, crc_bytes
    
    def calculate_hex(self, hex_string):
        """Calculate CRC for hex string (without spaces)"""
        data = bytearray.fromhex(hex_string)
        crc_int, crc_bytes = self.calculate(data)
        return crc_bytes.hex()

# Usage
whoop_crc = WhoopCRC()

# From hex string
packet_hex = "aa100057236d4201d036656600000000"
checksum_hex = whoop_crc.calculate_hex(packet_hex)
print(f"Checksum: {checksum_hex}")  # f62deb81

# From bytes
packet_bytes = bytearray.fromhex(packet_hex)
checksum_int, checksum_bytes = whoop_crc.calculate(packet_bytes)
print(f"Checksum int: {hex(checksum_int)}")  # 0xf62deb81
print(f"Checksum bytes: {checksum_bytes.hex()}")  # f62deb81

Building Complete Packets

Example: Creating a command with proper checksum
import struct
import time

def build_alarm_packet(unix_timestamp):
    """Build alarm packet with valid checksum"""
    # Packet header and data (without checksum)
    header = "aa10005723"
    packet_count = "70"  # Can be any value
    flags = "4201"
    unix_hex = struct.pack('<I', unix_timestamp).hex()
    padding = "00000000"
    
    # Combine packet data
    packet_data = f"{header}{packet_count}{flags}{unix_hex}{padding}"
    
    # Calculate checksum
    whoop_crc = WhoopCRC()
    checksum_hex = whoop_crc.calculate_hex(packet_data)
    
    # Complete packet
    return f"{packet_data}{checksum_hex}"

# Set alarm for 10 seconds from now
future_time = int(time.time()) + 10
packet = build_alarm_packet(future_time)
print(f"Packet to send: {packet}")

Validation Testing

Verify your implementation with these known packet/checksum pairs:
test_packets = [
    ("aa100057236d4201d036656600000000", "f62deb81"),
    ("aa100057236e42010c37656600000000", "1023ccef"),
    ("aa100057236f4201207d656600000000", "fea1e060"),
    ("aa100057237042015011656600000000", "f226a8bd"),
    ("aa0800a823070e00", "c7e40f08"),
    ("aa0800a823080e01", "6c935474"),
]

whoop_crc = WhoopCRC()

for data, expected_checksum in test_packets:
    calculated = whoop_crc.calculate_hex(data)
    status = "✓" if calculated == expected_checksum else "✗"
    print(f"{status} {data} -> {calculated} (expected: {expected_checksum})")
All tests should pass (✓) if your implementation is correct.

Alternative: Manual CRC Calculation

If you don’t want to use crccheck, here’s a manual implementation:
import struct

def whoop_crc32(data):
    """Manual CRC-32 calculation with Whoop parameters"""
    crc = 0x00000000  # Initial value
    poly = 0x04C11DB7
    
    for byte in data:
        # Reflect input byte
        byte = int('{:08b}'.format(byte)[::-1], 2)
        crc ^= byte
        
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ poly
            else:
                crc >>= 1
    
    # Reflect output
    crc = int('{:032b}'.format(crc)[::-1], 2)
    
    # XOR output
    crc ^= 0xF43F44AC
    
    return crc

# Test
data = bytearray.fromhex("aa100057236d4201d036656600000000")
checksum = whoop_crc32(data)
print(hex(checksum))  # 0xf62deb81
The manual implementation above is simplified and may not handle bit reflection correctly in all cases. Use the crccheck library for production code.

Build docs developers (and LLMs) love