Your sensors, on WhatsApp.
Greenhouse too cold, water tank full, garage door opened at
3am — the board POSTs, your phone buzzes. One HTTPS request to
POST /v1/notify from whatever the thing runs: Arduino C++ on an
ESP32, MicroPython, or a cron line on a Raspberry Pi. No app to install on
the phone, no linked device, no phone-number pairing — it's the official
Meta Cloud API underneath.
ESP32 / Arduino (C++)
WiFiClientSecure + HTTPClient,
both in the ESP32 Arduino core — no extra libraries. Call
notify() from wherever your sketch decides something's worth a
buzz. Get the key by sending join on WhatsApp (details at
the bottom).
#include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #define PINGWA_KEY "pw_your_key_here" // call after WiFi.begin(...) has connected void notify(const String &text) { WiFiClientSecure client; // Trade-off, stated plainly: setInsecure() still encrypts the // connection but skips certificate validation — fine for a // greenhouse ping, wrong for anything secret. The proper fix is // client.setCACert(root_ca) with the root CA PEM in your sketch. client.setInsecure(); HTTPClient http; http.begin(client, "https://pingwa.dev/v1/notify"); http.addHeader("Authorization", "Bearer " PINGWA_KEY); http.addHeader("Content-Type", "application/json"); int code = http.POST("{\"text\": \"🌡️ greenhouse 4°C\"}"); http.end(); // code == 200 → queued; negative → connection/TLS error }
MicroPython
urequests ships with standard MicroPython
builds for the ESP32 — import urequests just works, no
mip install needed. The json= argument handles the
body and the Content-Type header for you.
import urequests
def notify(text):
r = urequests.post(
"https://pingwa.dev/v1/notify",
json={"text": text},
headers={"Authorization": "Bearer pw_your_key_here"},
)
r.close()
notify("💧 tank full — pump stopped")
Raspberry Pi — or anything with a shell
If it runs cron and curl, it can tap you. The
|| true matters: a ping that fails (WiFi blip, our outage) must
never break the job it's reporting on.
0 22 * * * /home/pi/backup.sh && curl -sf -X POST https://pingwa.dev/v1/notify -H "Authorization: Bearer $PINGWA_KEY" -H "Content-Type: application/json" -d '{"text": "🍓 pi backup done"}' || true
For threshold sensors, the trap is alert-per-reading: the
tank crosses 90% and every 5-minute poll fires another message. The
Idempotency-Key header fixes it server-side — the same key with
the same body is honored for 24 hours, so replays are absorbed and nothing
is re-sent. Key it on the state plus the day and you get one alert
per state-change per day, straight from a dumb polling loop:
#!/bin/sh
level=$(cat /var/lib/tank/level) # your sensor read here
if [ "$level" -gt 90 ]; then
curl -sf -X POST https://pingwa.dev/v1/notify \
-H "Authorization: Bearer $PINGWA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: tank-high-$(date +%F)" \
-d '{"text": "💧 tank above 90% — check the pump"}' || true
fi
One rule to respect: keep the body constant for a given key. A different body under the same key is rejected with a 409 and nothing is sent — so don't interpolate the live reading into the alert text, or the second poll's "91%" vs the first's "90.4%" turns your dedupe into a silent drop.
Coming from CallMeBot?
Respect where it's due
CallMeBot has carried the maker crowd's WhatsApp pings for years — free for personal use, proven in a thousand forum threads, and run as a labor-of-love hobby project. If all you need is a one-way ping to yourself with zero budget, CallMeBot works — this page isn't here to tell you otherwise.
What's different here
- Official Meta Cloud API — no linking your personal number to a third-party gateway; messages come from a registered business number.
- Two-way —
POST /v1/asksends a question with buttons and blocks until you tap; the board can ask, not just tell (see FAQ). - Hosted service with an SLA — a status page, uptime commitments, and someone on the hook when it breaks.
- Agent-native docs — llms.txt means your coding agent can wire this in without you reading anything.
Pick by what you're protecting: a hobby ping, either. Something you'd be annoyed to miss — that's what the paid path is for.
FAQ
ESP32 send WhatsApp message without CallMeBot?
That's § 1 — a plain HTTPS POST to /v1/notify with a
Bearer key, over the official Meta Cloud API. No phone-number pairing, no
linked device, no waiting for an activation message: send
join once, put the key in your sketch, POST.
How many messages does the free tier cover?
30 paid messages a month, and your replies inside the 24h window are
free. That's plenty for events — and the discipline is the point: sensor
chatter belongs behind thresholds and the
Idempotency-Key pattern from § 3, not raw readings. If your
board sends more than 30 paid messages a month, it's not alerting you,
it's logging at you.
Does an ESP8266 work too?
Same HTTP, same endpoint — the 8266's Arduino core has
WiFiClientSecure and HTTPClient as well. Two
caveats from the smaller chip: heap is tight, so keep the JSON body
short; and TLS on the 8266 usually forces setInsecure() (or
a fingerprint pin) in practice — the § 1 trade-off comment applies
double.
Can the board ask me something?
Yes — POST /v1/ask sends the text with up to three
buttons and long-polls for your tap; the response tells your code which
button (b0/b1/b2) you pressed.
"Frost expected — run the heater?" is one request. Most sensor work only
needs notify, so we'll leave it at that here — the full contract is in
llms.txt.
Scan the code and get instant access. It opens WhatsApp with “join” ready to send, and your API key comes straight back.
The snippets above are the honest shape of the integration, not lab-verified on every board revision — pin versions and test on your own hardware before trusting a greenhouse to it.