WhatsApp automatiseren via de telefoon zelf (i.p.v. WhatsApp Web)
De meeste WhatsApp-automatiseringsprojecten gebruiken whatsapp-web.js of een vergelijkbare bibliotheek die een onzichtbare Chromium-browser start, inlogt op WhatsApp Web, en via daar berichten stuurt en ontvangt.
Dat werkt — tot het niet meer werkt. Sessies die om onbekende redenen droppen, een geblokkeerd telefoonnummer omdat WhatsApp Web-verkeer verdacht veel op een bot lijkt, of een browserproces dat na een paar dagen vastloopt. Herkenbaar?
Wij bouwden een alternatief: stuur de telefoon zelf aan, via USB.
Het idee
In plaats van een browser die doet alsof-ie een telefoon is, gebruiken we een echte telefoon — een Nexus 6P met WhatsApp erop, via USB verbonden met een server. Een Docker-container draait Appium met de UiAutomator2-driver, die via ADB de telefoon kan aansturen: scherm uitlezen, knoppen indrukken, tekst typen, notificaties opvangen.
Daar overheen ligt een dunne REST API (Express + Node.js) met eindpunten voor het versturen en ophalen van berichten.
De architectuur
Twee Docker-containers op één server:
| Container | Taak |
|---|---|
appium-adb | ADB-server + Appium 3 + UiAutomator2-driver. Beheert de USB-verbinding, past powersave-instellingen toe, en herstelt de verbinding als deze wegvalt. |
wa-appium-api | Express REST API (poort 3010). Biedt eindpunten voor chatlijst, berichten, en media. Ook de webhook-ontvanger voor notificaties. |
De telefoon (niet geroot) hangt aan USB. De container mount /dev/bus/usb
voor directe toegang. Een udev-regel zorgt dat de ADB-daemon de telefoon
mag zien, en een RSA-vingerafdruk op het telefoonscherm geeft eenmalig
toestemming.
Functionaliteiten
Inmiddels werkt het volgende:
| Functie | Hoe |
|---|---|
| Chatlijst lezen | Appium leest het WhatsApp-hoofdscherm uit (contactnaam, laatste bericht, tijdstip, ongelezen telling). |
| Berichten sturen | Via de zoekfunctie van WhatsApp wordt de juiste chat gevonden, tekst ingetypt en verstuurd. |
| Berichten ophalen | Een chat openen, de zichtbare berichten uitlezen, terug naar home. |
| Media versturen | Foto’s worden via adb push naar de telefoon gekopieerd, waarna een Android Intent (ACTION.SEND) het deelscherm opent en Appium het juiste contact laat selecteren. Geen broze navigatie door de galerij. |
| Real-time notificaties | Een kleine Kotlin-app op de telefoon luistert via NotificationListenerService naar binnenkomende WhatsApp-notificaties. Bij elk bericht (tekst of media) wordt een HTTP POST naar de API gestuurd. |
| Webhook fan-out | De API stuurt binnenkomende berichten door naar geregistreerde subscribers — andere services die op berichten willen reageren. |
| Job queue | Omdat er maar één telefoon is, gaan alle Appium/ADB-operaties door een centrale FIFO-wachtrij. Geen conflicten op de UI-sessie. |
Waarom dit beter is dan WhatsApp Web
| Aspect | WhatsApp Web (Puppeteer) | Telefoon (Appium) |
|---|---|---|
| Detectie | WhatsApp kan Web-sessies herkennen als bots en blokkeren | Ziet eruit als een normale telefoongebruiker |
| Stabiliteit | Browser-crash na dagen, sessie-drops, puppeteer-timeouts | USB-kabel erin = stabiel |
| Notificaties | Pollen of webhook via de API | Android NotificationListener = push |
| Root nodig | Nee | Nee |
De valkuil: UI-automation is traag
De grootste beperking is snelheid. Waar WhatsApp Web een API-call doet in
milliseconden, moet Appium het scherm uitlezen, elementen zoeken, wachten
op renders. Een simpele getChats kan 10-15 seconden duren.
Daarom gebruiken we twee lagen:
- Primair: de vertrouwde WhatsApp-Web-API (
wwebjs-api) voor snelle operaties - Fallback: de Appium-API als de Web-sessie wegvalt
En voor real-time notificaties hebben we de notification-listener-app, die geen Appium nodig heeft — die krijgt berichten binnen zodra het WhatsApp-scherm ze toont.
Open source
Het hele project staat op onze Forgejo:
Dockerfiles, de REST API (server.js), de Kotlin notification-listener-app,
en documentatie. Vrij te gebruiken, aan te passen, en vooral: te leren van.