Integration

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.

§ 1

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.pingwa entity for one-way alerts;
  • a pingwa.ask service that sends a question with up to 3 buttons and hands the answer back via response_variable (no timeout tuning, no .content parsing);
  • a signature-verified inbound webhook: it registers the HA webhook, checks the X-Pingwa-Signature HMAC for you, and fires a pingwa_reply event 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.

§ 2

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
# 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
# secrets.yaml
pingwa_bearer: "Bearer pw_your_key_here"
§ 3

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
# 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"]}'
§ 4

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
# 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"
§ 5

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 a secret (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_id is b0/b1/… for a tapped button (null for free text). HA's built-in platform: webhook trigger fires on that POST and your automation acts on it.
  • A voice message adds a media object {kind:"audio", voice:true, mime_type, url} (and body is empty). GET media.url to 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 a media_player or 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.

§ 6

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:

§ 7

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.

☕ Support pingwa

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.

Sponsor on GitHub →

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.