All projects

WhatsApp Keyword Monitor & Listener — Android Apps

Two Android apps (notification-listener + accessibility-service) that push WhatsApp keyword hits to a webhook.

The goal

Watch WhatsApp for messages containing specified keywords and push matches to a webhook. Ship two versions: a light notification-listener (minimal permissions) and a deeper accessibility-service (full UI inspection with sender + phone extraction).

The solution

Two companion Android apps. WhatsAppListener uses NotificationListenerService with a keyword UI, per-device ID, deduplication and webhook POST. WhatsAppMonitor uses an AccessibilityService + Jetpack Compose + Room, walks WhatsApp's UI tree, extracts phone numbers with configurable country prefixes (1/972/…), persists via DAOs and reps, and exposes a Compose UI with navigation. A shared KeyStore project holds signing keys.

Highlights

  • Two permission tiers (light notification vs deep accessibility) in one family of apps
  • Centralized WhatsAppSelectors.kt survives WhatsApp updates with a one-file patch
  • Cross-path dedup (notification + UI crawl) keeps webhook consumers sane
  • Room DB + Compose UI on Monitor; lightweight Listener for low-risk deployment

The challenge

WhatsApp has no API for reading messages on Android — any scraping is adversarial.

Two apps for two trust tiers. Listener uses NotificationListenerService (minimal permissions, notification-only signal). Monitor uses AccessibilityService (full UI tree walk, phone number + sender + context — requires accessibility permission).

Raw phone numbers captured from WhatsApp UI need consistent normalization across regions.

setSupportedCountryPrefixes(prefixes) (default ['1','972']) strips non-digits and normalizes leading + / 0 / country code to emit a canonical E.164-ish number.

The Listener and Monitor can see the same message in different ways and would otherwise double-fire the webhook.

Deduplicator.kt uses a fingerprint (sender + trimmed text + timestamp bucket) so the same hit is emitted at most once per window.

WhatsApp's accessibility tree changes with app updates — hard-coded selectors break.

WhatsAppSelectors.kt centralizes every selector/resource-id so a WhatsApp version bump is a one-file patch.

Modern Android throttles and kills long-running services.

NotificationListenerService runs inside the system listener process (Doze-insensitive); accessibility runs on the OS's always-on accessibility runtime — both avoid foreground-service drain.

What was delivered

  • WhatsAppListener/ — NotificationListenerService app (WhatsAppNotificationListener, KeywordMatcher, WebhookClient, Deduplicator, KeywordAdapter, DeviceIdProvider, AppPreferences, MainActivity)
  • WhatsAppMonitor/ — AccessibilityService + Compose app (MonitorAccessibilityService, WhatsAppNavigator, WhatsAppSelectors, KeywordMatcher, CrawlController, WebhookClient; Room: AppDatabase, Dao, Entities, Repos, SettingsStore; UI: AppNav, MainVm, Screens, Theme)
  • KeyStore/ — signing-key management
  • Both apps at API 26+ / compile SDK 35 / Kotlin 1.9 / Compose

Results

2 + shared KeyStore

Apps

Notification / Accessibility

Permission tiers

HTTP webhook POST

Output

API 26+

Min SDK

What it taught me

  • Offering both a low-permission path (notification listener) and a high-capability path (accessibility) is more useful than picking one
  • Centralizing WhatsApp selectors in one file turns a WhatsApp update from a hunt into a one-file patch
  • Cross-path deduplication (notification + UI crawl) is mandatory — anything else spams the webhook consumer