# Notifications not firing

Background notification delivery via `window.webbleIOS.backgroundSync.registerCharacteristicNotifications` or `registerBeaconScanning` registers successfully, but no iOS notification ever appears on the Lock Screen. This page lists every cause.

## Symptom

```js
const registration = await window.webbleIOS.backgroundSync.registerCharacteristicNotifications({
  /* ... */
});
// Registration resolves, but the Lock Screen never shows a notification.
```

Or, after a beacon scan registration, no iOS banner appears even when a matching peripheral is in range.

## 1. Notification permission not granted

iOS gates notifications behind `UNUserNotificationCenter` authorization. Registration can succeed before permission is granted, but nothing will be delivered.

**Fix:** request permission explicitly before registering.

```js
const status = await window.webbleIOS.backgroundSync.requestPermission();
if (status !== 'granted') {
  // Direct user to Settings → Notifications → WebBLE
}
```

If the user previously denied, `requestPermission()` will return `'denied'` without showing the system prompt. The user must flip the toggle in **Settings → Notifications → WebBLE**.

## 2. WebBLE companion app not running

Background sync runs inside the companion app, not the extension. If the user never launched the app, registrations persist but nothing consumes them.

**Fix:** prompt the user to open the WebBLE app at least once. The app registers its background modes and begins servicing registrations from that point on.

## 3. Cooldown throttling

Every registration has a `cooldownSeconds` notification dedup interval (default 5 s, minimum enforced 5 s). If the underlying BLE value fires more frequently, most events are suppressed.

**Fix:** set a cooldown that matches your product's desired notification rate. Remember: this is **not** a polling frequency — the native companion app receives every underlying change in real-time. Cooldown only throttles the OS notification.

## 4. Template `url` is not same-origin

The `template.url` is validated against the registering page's origin at both registration time and at fire time. A cross-origin URL will cause the notification to be dropped silently on fire.

**Fix:** ensure `template.url` is same-origin. If you intend to deep-link to a third-party destination, redirect through your own origin.

## 5. Condition never matches

For characteristic notifications, the `condition` gates delivery. `operator: 'gt', threshold: 150` only fires when the decoded value exceeds 150. If you expect always-fire, use `operator: 'always'` or `operator: 'changed'`.

**Fix:** confirm the condition matches the values the peripheral actually emits.

```js
condition: { decode: 'uint8', operator: 'changed', threshold: 0 }
```

## 6. Device is in Focus / Do Not Disturb

iOS Focus modes can suppress notifications for specific apps. This is user-configurable and outside WebBLE's control.

**Fix:** the user adds WebBLE to their Focus allow-list, or disables the Focus mode.

## 7. Beacon advertisements not reaching iOS

For beacon scanning, iOS only hardware-filters on service UUIDs. If the beacon does not advertise a service UUID in its primary advertisement packet, it will not wake the scanner when the app is deeply backgrounded.

**Fix:** verify the beacon includes the service UUID in its advertisement (not only in a scan-response). Check the advertiser's configuration.

Also note: beacon delivery on iOS is optimized for quick catch-up when the app becomes foreground again, not for guaranteed real-time while fully backgrounded.

## Still stuck?

Open the companion app's diagnostics view, tail the event log while triggering the expected condition, and attach the output to an issue at [github.com/wklm/ioswebble-sdk/issues](https://github.com/wklm/ioswebble-sdk/issues).
