pingwa + Home Assistant
Your smart home already knows when the doorbell rings, the alarm
trips, or the nightly backup fails. Give it a WhatsApp voice: a native HACS
integration with UI onboarding, or plain YAML with one
rest_command. Your pick.
Native integration (HACS)
The pingwa-hass integration is the quickest
path. Install it via HACS as a
custom repository
(github.com/grzgrzgrz3/pingwa-hass), add the integration, and
pick “Register on WhatsApp”. It walks you through sending a
short code to the pingwa number from your phone. No Meta account, no QR
linking, no API key copy-pasting (pasting an existing pw_ key
works too). You get:
- a
notify.pingwaentity for one-way alerts; - a
pingwa.askservice that sends a question with up to 3 buttons and hands the answer back viaresponse_variable(notimeouttuning, no.contentparsing); - a signature-verified inbound webhook: it registers the HA webhook,
checks the
X-Pingwa-SignatureHMAC for you, and fires apingwa_replyevent your automations can trigger on.
Requires Home Assistant 2024.12+; the
two-way part needs your HA reachable over public HTTPS (Nabu Casa or a
reverse proxy/tunnel). Prefer plain YAML you already know, or want zero
installs? Everything below works with a stock rest_command.
Add the rest_command
In configuration.yaml, wire an outbound notify to
POST /v1/notify. Get an API key by texting join to
pingwa on WhatsApp (see home), then store it in
secrets.yaml.
# configuration.yaml
rest_command:
pingwa_notify:
url: "https://pingwa.dev/v1/notify"
method: POST
headers:
Authorization: !secret pingwa_bearer
Content-Type: application/json
payload: '{"text": {{ message | tojson }} }'
# secrets.yaml
pingwa_bearer: "Bearer pw_your_key_here"
Add pingwa_ask for confirmations
The same pattern works for POST /v1/ask, which
sends a question and waits for you to tap a button on WhatsApp. Handy for
anything you want to confirm rather than just be told about. The real request
body is {"text": ..., "buttons": [...]} (a plain list of up to 3
short strings; omit buttons for a free-text reply instead).
The server holds the connection open for up to 90
seconds waiting for your reply, then returns 408, so
give the rest_command a matching timeout (HA's default is only
10s and would cut it off). A late reply isn't lost: it's still retrievable
from the inbox/reply endpoints below.
# configuration.yaml — add pingwa_ask under the SAME rest_command: # block as pingwa_notify (one rest_command: key holds all commands) pingwa_ask: url: "https://pingwa.dev/v1/ask" method: POST timeout: 95 headers: Authorization: !secret pingwa_bearer Content-Type: application/json payload: '{"text": {{ message | tojson }}, "buttons": ["Yes", "No"]}'
Example automations
Three common triggers, all under one automation:
key (or drop the list items into automations.yaml if that's how
you split your config). The alarm example shows the flagship two-way move:
capture the reply with response_variable and branch on which
button was tapped. rest_command hands back the HTTP response as
{status, content}, so the parsed JSON body lives under
.content. Reply buttons get stable ids in order: b0
is the first button ("Yes"), b1 the second
("No"). Read the id at reply.content.reply.button_id.
# configuration.yaml automation: # Doorbell pressed — one-way notify - alias: "Doorbell -> WhatsApp" trigger: - platform: state entity_id: binary_sensor.front_door_doorbell to: "on" action: - service: rest_command.pingwa_notify data: message: "Someone's at the front door 🔔" # Alarm triggered — ask, then act on the answer - alias: "Alarm triggered -> ask on WhatsApp" trigger: - platform: state entity_id: alarm_control_panel.home to: "triggered" action: - service: rest_command.pingwa_ask data: message: "🚨 Alarm triggered at home. Call the keyholder?" response_variable: reply # reply.content.reply.button_id is "b0" for "Yes", "b1" for "No" # (on a 408 timeout reply.status is 408 — falls to the default branch) - choose: - conditions: "{{ reply.content.reply.button_id == 'b0' }}" sequence: - service: script.call_keyholder default: - service: rest_command.pingwa_notify data: message: "No 'Yes' tap — not calling the keyholder." # Nightly backup failed — one-way notify - alias: "Backup failed -> WhatsApp" trigger: - platform: state entity_id: sensor.backup_status to: "failed" action: - service: rest_command.pingwa_notify data: message: "Nightly backup FAILED — check the logs"
Two-way: pingwa pushes replies into HA
Outbound (HA → your phone)
is the rest_command above. For inbound (your WhatsApp
reply → HA) pingwa can now PUSH each reply straight to a Home
Assistant webhook. No polling loop.
- Register your HA webhook URL once:
POST /v1/webhooks {"url": "https://ha.example.com/api/webhook/<id>"}. It returns asecret(whsec_…) shown only once; the URL must be public HTTPS (up to 5 per account). - On every inbound message pingwa POSTs a JSON body
{event, message_id, body, button_id, reply_to_message_id, wa_message_id, window_open, created_at}.button_idisb0/b1/… for a tapped button (null for free text). HA's built-inplatform: webhooktrigger fires on that POST and your automation acts on it. - A voice message adds a
mediaobject{kind:"audio", voice:true, mime_type, url}(andbodyis empty).GET media.urlto fetch the audio. No API key needed (the signature authorizes it), but the link is short-lived (~1h), so fetch promptly. Feed the URL to amedia_playeror download it in your automation. - Each request carries an
X-Pingwa-Signature: sha256=<hmac>header (HMAC-SHA256 of the raw body with your secret). HA's webhook trigger can't verify that HMAC on its own. Treat the raw endpoint as unauthenticated, keep the HA webhook id (the one in the URL) secret, and verify the signature in a template condition, or install the native integration, which registers the webhook and verifies the HMAC for you. Don't skip this on an internet-exposed HA. - If a delivery keeps failing, pingwa retries with backoff and
auto-deactivates the webhook after repeated exhausted attempts. Check
GET /v1/webhooks(active,failure_count) and re-create it once HA is reachable again.
Fallback: polling still works if you'd
rather HA ask. A rest sensor or a short-interval automation
can call GET /v1/inbox?since=<cursor> and react to new
messages the same way.
Why the official Cloud API, not a bridge
pingwa sends through the official Meta WhatsApp Cloud API on its own number, so wiring it into your home automation can't get your personal WhatsApp banned. That is a real distinction from two other kinds of tools people wire into Home Assistant today:
- Unofficial WhatsApp-Web bridges that link your own device by QR scan (the technique behind libraries like Baileys). Fast to set up, but they reverse-engineer WhatsApp's client protocol, violate its terms, and put your personal number at real risk of a permanent ban. See the full comparison.
- One-way webhook-only notifiers: great for a single alert, but they have no concept of a reply, so they can't turn "alarm triggered" into a tap-to-confirm question.
Sized for alerts, not alert spam
The free plan covers 30 paid-route notifications a month (replies inside your 24h WhatsApp window are free and unlimited). That's not built for streaming every state change in your house. It's built for the handful of things actually worth a phone buzz: someone's at the door, the alarm tripped, the backup failed. Keep high-frequency sensor chatter in HA's own logbook/notifications, and reserve pingwa for the low-volume, high-value pings.
Solo, self-funded, official Cloud API. If pingwa saves you a ban or a bridge headache, a coffee keeps the free tier free. Entirely optional, and it unlocks nothing.
Not affiliated with Home Assistant or the Home Assistant
Foundation. rest_command is a standard Home Assistant
integration; see their own docs for the full option set. pingwa is an
independent, self-hosted service. See about and
privacy.