Skip to content

Bug: KeyError: 'id' in RingEventListener._get_ring_event on a malformed push notification #552

Description

@dchornbaker

Bug: KeyError: 'id' in RingEventListener._get_ring_event on a malformed push notification

Environment

  • ring_doorbell version: 0.9.14 (current PyPI release)
  • Python version: 3.14
  • Installed via: Home Assistant ring integration (custom ring_doorbell dependency pin)
  • Home Assistant Core version: core-2026.8.2
  • Ring device model(s): floodlight_v2

Description

The FCM push listener crashes with an unhandled KeyError: 'id' when it
receives a notification whose data.event.ding object is present but does
not contain an id field. The exception propagates out of the
notification callback into firebase_messaging, which catches it at its
own layer and logs "Unexpected exception calling notification callback" —
so the process doesn't crash, but the event that triggered it is silently
dropped instead of being delivered as a RingEvent.

Traceback

Traceback (most recent call last):
  File "/usr/local/lib/python3.14/site-packages/firebase_messaging/fcmpushclient.py", line 456, in _handle_data_message
    self.callback(ret_val, msg.persistent_id, self.callback_context)
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/site-packages/ring_doorbell/listen/eventlistener.py", line 297, in _on_notification
    ring_event = self._get_ring_event(msg_data)
  File "/usr/local/lib/python3.14/site-packages/ring_doorbell/listen/eventlistener.py", line 323, in _get_ring_event
    event_id = int(event["ding"]["id"])
                   ~~~~~~~~~~~~~^^^^^^
KeyError: 'id'
2026-08-28 09:21:39.447 ERROR (MainThread) [firebase_messaging.fcmpushclient] Unexpected exception calling notification callback

(The traceback repeated twice in a row in the log, roughly a second
apart, for what appeared to be a single physical event — possibly a retry
or a duplicate push from Ring's side.)

Root cause (from reading ring_doorbell/listen/eventlistener.py, 0.9.14)

def _get_ring_event(self, msg_data: dict) -> RingEvent | None:
    if (android_config_str := msg_data.get("android_config")) is None or (
        data_str := msg_data.get("data")
    ) is None:
        _logger.debug(
            "Unexpected alert type in fcm message data.  Full message is:\n%s",
            json.dumps(msg_data),
        )
        return None

    android_config = json.loads(android_config_str)
    data = json.loads(data_str)
    event_category = android_config["category"]
    event_kind = PUSH_NOTIFICATION_KINDS.get(event_category, "Unknown")
    device = data["device"]
    event = data["event"]
    event_id = int(event["ding"]["id"])          # <-- crashes here
    created_at = event["ding"]["created_at"]
    create_seconds = parse_datetime(created_at).timestamp()
    return RingEvent(...)

The function already handles one "unexpected shape" case gracefully (missing
android_config/data → debug-log and return None), but the rest of the
parsing — event["ding"]["id"], event["ding"]["created_at"],
event["ding"]["subtype"] — assumes a fixed shape with no fallback. In
this case event["ding"] existed as a dict, so it got past data["event"],
but that dict didn't include id. I don't have the raw payload captured
(it isn't logged before the crash), so I can't say what event category or
payload shape triggered it — happy to attach the raw msg_data if I can
capture one with debug logging enabled and it recurs.

Expected behavior

A notification shape the library doesn't fully recognize should be logged
and skipped (as already happens for the missing android_config/data
case above), not raise an unhandled exception out of the callback.

Suggested fix

Wrap the event["ding"][...] access the same way the top of the function
already handles the missing android_config/data case — e.g. .get()
with a debug/warning log and return None on missing keys — rather than
direct indexing that raises.

Workaround in use

Monkeypatching RingEventListener._get_ring_event in a small Home
Assistant custom component to catch KeyError and log+skip instead of
raising, until this is fixed upstream.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions