How to Subscribe to Notifications and Indications in iOS Core Bluetooth

Punch Through 57

You’ve scanned for peripherals, connected, and discovered your services and characteristics. Now you want data to actually flow. Subscribing to notifications or indications is how you get there. It’s how you tell Core Bluetooth to call you whenever the peripheral has something new to say, rather than polling for it yourself.

If you’re coming from Android, your first instinct might be to write the Client Characteristic Configuration Descriptor directly. That’s how Android does it. iOS doesn’t let you. Core Bluetooth handles the CCCD write internally, and the API gives you a single clean method instead. Once you know that, the rest falls into place quickly.

If you haven’t completed service and characteristic discovery yet, How to Set Up iOS Core Bluetooth for Discovering Services and Characteristics from a Peripheral covers that step before you continue here.


Notifications vs. Indications in Core Bluetooth

Notifications and indications are functionally different at the BLE stack level. Indications require an acknowledgement from the central, notifications don’t. In Core Bluetooth, that distinction doesn’t surface in the API. The two are treated identically from the central side. If subscribed to a characteristic, you’ll get a call to peripheral(_:didUpdateValueFor:error:) for that characteristic any time its value changes and a notification or indication is sent from the peripheral. You subscribe and unsubscribe the same way regardless of which one the peripheral uses.


Check That the Characteristic Supports Notifications Before Subscribing

Before calling setNotifyValue, confirm the characteristic actually supports notifications or indications by checking its properties against .notify or .indiciate. Calling setNotifyValue on a characteristic that doesn’t support either will produce an error in didUpdateNotificationStateFor. A quick properties check before you subscribe saves a confusing callback.


Subscribing to iOS BLE Notifications with setNotifyValue

If a characteristic supports it, you may subscribe to notifications and indications by calling setNotifyValue(_ enabled: Bool, for characteristic: CBCharacteristic).

In Core Bluetooth, you may not write the value of the client characteristic configuration descriptor (CBUUIDClientCharacteristicConfigurationString), which is used under the surface by iOS (and explicitly in Android BLE development) to subscribe to or unsubscribe from notifications and indications on a characteristic. Instead, use setNotifyValue(_:for:).

// In main class
func subscribeToNotifications(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
    peripheral.setNotifyValue(true, for: characteristic)
}

Handling the didUpdateNotificationStateFor Callback

You will get a call to peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) each time you change the notification setting, whether subscribing or unsubscribing. This is where you confirm the subscription succeeded and handle any errors.

// In CBPeripheralDelegate class/extension
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        // Handle error
        return
    }
    // Successfully subscribed to or unsubscribed from notifications/indications on a characteristic
}

Receiving Data with didUpdateValueFor

Once subscribed, incoming values arrive through peripheral(_:didUpdateValueFor:error:) which is the same delegate callback used when you explicitly read a characteristic’s value. The callback doesn’t distinguish between a notification-triggered update and a read response, so if your implementation uses both, you’ll want to handle that in your callback logic.

/ In CBPeripheralDelegate class/extension
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        // Handle error
        return
    }
    guard let value = characteristic.value else {
        return
    }
    // Do something with data
}

Checking Subscription State with isNotifying

You can check the notification subscription state at any time by checking the characteristic’s isNotifying property. This is useful for defensive checks before subscribing, driving UI state, or confirming subscription status after a reconnection before assuming data will flow.


Unsubscribing from iOS BLE Notifications

To unsubscribe, call setNotifyValue(false, for: characteristic). The same didUpdateNotificationStateFor callback fires on unsubscribe as on subscribe, so your existing error handling covers both directions. You’ll generally want to unsubscribe explicitly rather than relying on disconnection to clean up state — it makes reconnection logic cleaner and avoids ambiguity about subscription state when you reconnect.


The Full Subscription Flow

That’s the complete picture for subscribing to notifications and indications in Core Bluetooth. Once data is flowing, Using Rx vs Delegates with CoreBluetooth is worth reading if you’re weighing whether the delegate pattern is the right architecture for handling it at scale. Or if you want the full iOS Core Bluetooth reference in one place, check out the iOS Core Bluetooth Ultimate Guide.

Share:

Punch Through
Punch Through
We’re a team of engineers who obsess over making connected things actually work — reliably, securely, and without the handwaving. From BLE to backend, we build the software and systems behind connected medical devices and custom connected products that can’t afford to fail.

Subscribe to stay up-to-date with our latest articles and resources.