---
title: "Connect a Bluetooth Heart Rate Monitor to Your App (2026)"
canonical: "https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor"
cluster: "Connected Devices"
primary_query: "connect a bluetooth heart rate monitor to an app"
last_reviewed: "2026-08-14"
description: "One BLE integration covers chest straps and armbands from any vendor: scan for service 0x180D, subscribe to 0x2A37, read 0x2A38 for placement."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"Connect a Bluetooth Heart Rate Monitor to Your App (2026)\", AIFitnessAPI, https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor"
---

# Connect a Bluetooth Heart Rate Monitor to Your App (2026)

> A conforming Bluetooth heart rate monitor exposes the standardized Heart Rate service, so one integration works across straps and armbands from any vendor. The Bluetooth SIG's public assigned-numbers registry lists the Heart Rate service as 0x180D, Heart Rate Measurement as 0x2A37 and Body Sensor Location as 0x2A38. The flow is the same everywhere: scan filtered on the service UUID, connect, discover the service and its characteristics, enable notifications on the measurement characteristic, and read the body sensor location once for context. Parse the measurement value against the Heart Rate Service specification published by the Bluetooth SIG rather than against a copied snippet. Wrist and chest readings can differ because placement and sensor type differ, so record the source with every sample instead of assuming the two are interchangeable.

- Canonical: https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor
- Last reviewed: 2026-08-14
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "Connect a Bluetooth Heart Rate Monitor to Your App (2026)", AIFitnessAPI, https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor

---

A chest strap, an optical armband and a gym-branded band from three different vendors can all drive the same code path in your app. That is not luck. Heart rate was standardized early as a GATT service, so a conforming monitor advertises the same service and exposes the same measurement characteristic whatever the badge on the front says. The Bluetooth SIG's public assigned-numbers registry lists the Heart Rate service as **0x180D**, the Heart Rate Measurement characteristic as **0x2A37**, and Body Sensor Location as **0x2A38**.

## The standard profile is the business case

Every other route into heart rate costs a per-vendor integration: an account system, an OAuth dance, a webhook endpoint, a rate limit, and a support burden that grows with every brand your users own. Those integrations are still worth having — [wearable data APIs](/fitness-apis/wearable-data-apis) exist for good reasons — but they answer a different question. A cloud API tells you what a user did yesterday. The BLE profile tells you what their heart is doing this second, with no account, no network and no vendor relationship.

The practical split, and this is our judgement rather than anyone's rule: if the feature is live — a workout screen, a zone alarm, an interval timer, a class leaderboard — implement 0x180D once and stop shopping for SDKs. If the feature is retrospective, go through the platform stores or the vendor APIs, because a live radio link is a terrible way to reconstruct history.

## The flow, conceptually

**Scan filtered by service UUID.** Ask the platform's central to scan for peripherals advertising 0x180D instead of scanning for everything and matching on names. Name matching breaks the first time a vendor ships a firmware revision with a different suffix, and an unfiltered scan is slower to a usable result and worse for battery.

**Connect, then discover.** Once connected, discover the Heart Rate service on the peripheral and then its characteristics. Handles are not stable identifiers across devices or firmware, so resolve everything by UUID.

**Subscribe to notifications on 0x2A37.** The measurement characteristic is delivered by notification: the monitor pushes values on its own cadence rather than answering polls. Design for that cadence to be irregular and for it to stop without warning — a strap loses skin contact when a shirt goes over it, and a peripheral that has nothing to say looks identical to one that has walked out of range.

**Read Body Sensor Location once.** Treat 0x2A38 as an optional read you perform after connecting, and carry on gracefully if it is not there.

**Treat disconnects as normal, not exceptional.** Straps drop, batteries die mid-interval, and users walk to the water fountain. Decide up front whether a gap means pause the session, hold the last value, or mark the range unknown — the third is usually the honest answer, and it keeps a bad reading out of stored history.

## What Body Sensor Location is and is not

It reports where the monitor claims to sit: provenance and a UI label, not a calibration factor. Store it with the samples so later reconciliation has something to reason about, and never use it to adjust a number.

## Wrist data and strap data are different measurements

We publish no accuracy figures here, because we have not measured them. What is safe to say is structural: wrist optical sensors and chest straps differ in both placement and sensor type, so treating their outputs as one interchangeable stream is a modelling error regardless of which you think is better. Measure for your own use case, record the source with every sample, and let the reconciliation layer decide — see [deduplicating health data](/architecture/deduplicate-health-data) and [heart rate APIs](/data/heart-rate-api).

A watch is its own case. It may be reachable as a BLE peripheral, but on-device it is usually better served by the platform's live-workout API: on watchOS through a workout session, which Apple describes as "a session that tracks a person's workout" and documents as generating high-frequency heart rate samples for all workout sessions, and on Wear OS through Health Services. Both are covered in [Apple Watch live heart rate](/devices/apple-watch-live-heart-rate) and [Wear OS Health Services](/devices/wear-os-health-services). HealthKit and Health Connect are stores rather than live streams, so they are where the summaries land, not where the stream comes from.

## Where the wire format lives

The layout of the measurement value is defined in the Heart Rate Service specification published by the Bluetooth SIG. Parse against that document. If you are adapting a parser from a gist — most people are — diff it against the spec first, because a parser that works with the one strap on your desk is not a parser.

## Platform notes worth knowing before you start

On iOS, Apple's Core Bluetooth framework is the entry point, described in Apple's own documentation as being there to "communicate with Bluetooth low energy and BR/EDR ('Classic') Devices", with the central role handled by CBCentralManager. Since iOS 13 the Info.plist needs NSBluetoothAlwaysUsageDescription; iOS 12 and earlier used NSBluetoothPeripheralUsageDescription. Apple's warning is blunt: "Your app will crash if its Info.plist doesn't include usage description keys for the types of data it needs to access." Apple also documents "Don't subclass any of the classes of the Core Bluetooth framework. Overriding these classes isn't supported and results in undefined behavior." More in [iOS BLE fitness devices](/devices/ios-ble-fitness-devices).

On the web, the support matrix decides the feature for you. Per the caniuse dataset, Web Bluetooth is supported in Chrome 56+, Edge 79+, Opera 43+ and Samsung Internet 6.2+, and is not supported in Firefox at any version or in Safari on desktop or iOS. A browser-based strap feature therefore excludes every Safari user by construction. See [Web Bluetooth for fitness](/devices/web-bluetooth-fitness).

On radios: BLE is the default for new fitness-sensor development in 2026. The ANT+ program wind-down is reported rather than verified at source; [ANT+ versus Bluetooth](/devices/ant-plus-vs-bluetooth) sets out what is reported and what follows.

## Testing this without a strap on every desk

A physical device is the only reliable test target for BLE, so design a seam: put a narrow interface in front of the transport and let a recorded stream of notifications drive the pipeline in CI. Parsing, zone logic, session assembly and gap handling are then covered deterministically, and the device pass is reserved for what only a radio can prove. See [testing BLE fitness devices](/devices/testing-ble-fitness-devices), [mocking wearable data](/test/mock-wearable-data) and [device lab and CI](/test/device-lab-and-ci).

The same reasoning extends past heart rate: gym equipment uses the [fitness machine service](/devices/ftms-fitness-machine-service), and bike sensors have their own trio covered in [cycling power and cadence sensors](/devices/cycling-sensors-power-cadence).

## FAQ

### Which Bluetooth UUIDs does a heart rate strap use, and do I need one integration per brand?

One integration covers conforming monitors. The Bluetooth SIG's assigned-numbers registry lists the Heart Rate service as 0x180D, the Heart Rate Measurement characteristic as 0x2A37 and Body Sensor Location as 0x2A38. Because those numbers are standardized, a chest strap and an optical armband from different vendors present the same interface: scan filtered on 0x180D, connect, discover by UUID rather than by handle, and enable notifications on 0x2A37. Vendor SDKs and cloud APIs are still useful for history, sleep and recovery data, but they are a separate integration answering a separate question.

[Permalink](https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor#faq-1)

### What does the Body Sensor Location characteristic (0x2A38) actually tell my app?

It reports where the monitor says it sits on the body. Treat it as provenance and as a UI label, not as anything you compute with. Read it once after connecting, store it alongside the samples so that later reconciliation has something to reason about, and degrade gracefully if the characteristic is not present on a given device. The one thing not to do is use it as a correction factor. Adjusting a reported heart rate because of a claimed sensor position invents precision your app does not have.

[Permalink](https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor#faq-2)

### Why do a chest strap and a wrist watch report different heart rate for the same workout?

Because they are different measurements, not the same measurement done well and badly. Wrist optical sensors and chest straps differ in both placement and sensor type, so their outputs can diverge without either being broken. We publish no accuracy figures, because that comparison depends on the activity, the fit and the population, and you should measure it for your own use case rather than taking a number from a blog post. The engineering consequence is concrete: record the source with every sample, never merge the two streams into one undifferentiated series, and let your deduplication layer decide which source wins for a given interval.

[Permalink](https://aifitnessapi.com/devices/bluetooth-heart-rate-monitor#faq-3)
