Skip to main content

Overview

The Whoop 4.0 supports setting a single alarm that triggers the device’s vibration motor. Alarms are set by sending a packet to CMD_TO_STRAP with a Unix timestamp indicating when the alarm should ring.

Packet Structure

Alarm packets are 24 bytes long with the following structure:
OffsetSizeFieldDescription
0x005 bytesHeaderAlways aa10005723
0x051 bytePacket CountIncrements with each packet (not validated)
0x062 bytesFlagsAlways 4201 for alarms
0x084 bytesUnix TimeLittle-endian Unix timestamp for alarm
0x0C4 bytesPaddingAlways 00000000
0x104 bytesChecksumCRC-32 with custom parameters

Alarm Examples

Here are actual alarm packets captured from the Whoop app:
aa10005723 6d 4201 d0366566 00000000 f62deb81  # 7:00 Exact time
aa10005723 6e 4201 0c376566 00000000 1023ccef  # 7:01 Exact time
aa10005723 6f 4201 207d6566 00000000 fea1e060  # 12:00 Exact time
aa10005723 70 4201 50116566 00000000 f226a8bd  # 4:20 Exact time
aa10005723 81 4201 f07e6666 00000000 7037c2a4  # Peak 06:20
aa10005723 82 4201 f07e6666 00000000 7151203d  # Perform 06:20
aa10005723 83 4201 f07e6666 00000000 b18eaefc  # In the Green 06:20

CRC-32 Checksum

The checksum uses a custom CRC-32 configuration:
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)

Setting an Alarm

Using Python

import time
import struct

# Set alarm for 10 seconds from now
unix_time = int(time.time()) + 10
unix_hex = struct.pack('<I', unix_time).hex()

# Build packet (without checksum)
packet = f"aa10005723704201{unix_hex}00000000"

# Calculate checksum
checksum_int, checksum_bytes, _ = my_crc(bytearray.fromhex(packet))
checksum_hex = checksum_bytes.hex()

# Complete packet
full_packet = f"{packet}{checksum_hex}"

print(f"Alarm packet: {full_packet}")

Using gatttool

Sending alarm commands via gatttool works intermittently. The device may not respond reliably to every command.
# Replace XX:XX:XX:XX:XX:XX with your device's MAC address
# Replace the hex value with your calculated packet
sudo gatttool -i hci0 -t random -b XX:XX:XX:XX:XX:XX \
  --char-write -a 0x0010 -n aa10005723704201126d6566000000003d59d8fd

Characteristic Details

  • UUID: 61080002-8d6d-82b8-614a-1c8cb0f8dcc6 (CMD_TO_STRAP)
  • Handle: 0x0010
  • Properties: Write only

Sleep Goal Alarms

The Whoop app implements “sleep in the green” alarms by:
  1. Monitoring sleep data locally on the phone
  2. Calculating when the user reaches their sleep goal
  3. Sending a new alarm packet with the updated timestamp
This logic is handled entirely by the app, not the device.

Limitations

  • Only one alarm can be set at a time
  • No recurring alarm support on the device level
  • Alarm state cannot be read back from the device
  • The packet count field is not validated

Build docs developers (and LLMs) love