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:
| Offset | Size | Field | Description |
|---|
| 0x00 | 5 bytes | Header | Always aa10005723 |
| 0x05 | 1 byte | Packet Count | Increments with each packet (not validated) |
| 0x06 | 2 bytes | Flags | Always 4201 for alarms |
| 0x08 | 4 bytes | Unix Time | Little-endian Unix timestamp for alarm |
| 0x0C | 4 bytes | Padding | Always 00000000 |
| 0x10 | 4 bytes | Checksum | CRC-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}")
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:
- Monitoring sleep data locally on the phone
- Calculating when the user reaches their sleep goal
- 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