npm package: @laburen/openclaw-plugin-whatsapp-api
Description: WhatsApp API channel plugin for OpenClaw
Latest version: 0.6.0

# WhatsApp API Plugin for OpenClaw

WhatsApp Cloud API channel for OpenClaw: your router receives Meta webhooks and forwards them to OpenClaw; replies go straight to the Meta Graph API.

## Install

```bash
openclaw plugins install @laburen/openclaw-plugin-whatsapp-api
```
## Setup

1. In [Meta for Developers](https://developers.facebook.com/), create or open an app with **WhatsApp** product enabled and note your **Phone number ID** and a long-lived **System User** or **Temporary** access token with `whatsapp_business_messaging` (and webhook permissions as required by your setup).
2. Point Meta’s webhook callback URL to **your** public HTTPS endpoint (router / reverse proxy). That service should forward the raw `POST` body to OpenClaw at the path you set as `webhookPath` (default below).
3. Set the same verify token / shared secret flow your deployment uses: configure `inboundSharedSecret` in OpenClaw and send it from your router as the header `x-openclaw-shared-secret` (recommended).

### Via OpenClaw Config

Minimal example — set secrets and IDs (use env-backed config in production; never commit tokens):

```bash
openclaw config set channels.whatsapp-api.enabled true
openclaw config set channels.whatsapp-api.inboundSharedSecret "your-internal-secret"
openclaw config set channels.whatsapp-api.outboundPhoneNumberId "123456789012345"
openclaw config set channels.whatsapp-api.outboundAccessToken "EAAG..."
```

Allow the plugin and enable its entry, then restart the gateway:

```bash
openclaw config set plugins.allow '["whatsapp-api"]'
openclaw config set plugins.entries.whatsapp-api.enabled true
openclaw gateway restart
```

## How It Works

- Registers channel id **`whatsapp-api`** and exposes an inbound **`POST`** route (default **`/webhook/whatsapp-api`**).
- Parses WhatsApp Cloud API webhook payloads (`entry[].changes[].value.messages[]`) and sends inbound text into the OpenClaw reply pipeline.
- Outbound replies are sent **directly** to Meta:

  `https://graph.facebook.com/{apiVersion}/{phoneNumberId}/messages`

  with `Authorization: Bearer <outboundAccessToken>`.

- Transient failures (`429`, `5xx`, timeouts) are retried according to `maxRetries`, `retryBackoffMs`, and `requestTimeoutMs`.

### WhatsApp 24-hour context window (reminder)

WhatsApp Cloud API lets you send **session** messages (normal replies) only within about **24 hours** of the user’s last inbound message. After that window, outbound traffic is limited to **approved templates** until the user writes again.

This plugin can nudge you before the window closes:

1. **Snapshot on inbound:** For each user message handled by the channel, a hook writes `<OpenClaw state dir>/whatsapp-api/last-inbound-message.json` with `from`, `content`, `timestamp`, channel/account/conversation ids when present, and **`notified: false`**. That reset means a new user message clears any previous “we already warned” state.
2. **Cron from the host:** On service start, the plugin runs `setup.sh` (shipped under `src/scripts/` in the npm package). The script registers an OpenClaw cron job that periodically invokes `check.sh` via the exec tool (see the job’s description in `setup.sh`).
3. **`check.sh` behavior:** It reads the JSON file, skips if missing or `notified` is already `true`, and compares elapsed time since `timestamp` to a threshold (default **23 hours**). If the threshold is met, it runs `openclaw message send` to the user’s phone (parsed from `from`) with a configurable warning text, then sets **`notified: true`** in the same file so the warning is not sent again until the next inbound message.

Useful environment variables for the scripts: `OPENCLAW_DIR` (defaults to `/home/<user>/.openclaw`), `THRESHOLD_MINUTES`, `WARNING_MESSAGE`, and `EVERY` for the cron interval when installing (default `30m`). To remove the job: `bash /path/to/node_modules/@laburen/openclaw-plugin-whatsapp-api/src/scripts/setup.sh --uninstall` (adjust path to your install).

**Inbound webhook (from your router to OpenClaw)**

| Item | Detail |
|------|--------|
| Method | `POST` |
| Path | Value of `webhookPath` |
| Header (recommended) | `x-openclaw-shared-secret: <inboundSharedSecret>` |
| Body | Raw WhatsApp Cloud webhook JSON forwarded by your router |

Responses: **`403`** if the shared secret does not match, **`400`** if the payload is invalid, **`200`** when accepted (processing continues asynchronously after ACK).

## Configuration

```json
{
  "plugins": {
    "allow": ["whatsapp-api"],
    "entries": {
      "whatsapp-api": {
        "enabled": true
      }
    }
  },
  "channels": {
    "whatsapp-api": {
      "enabled": true,
      "webhookPath": "/webhook/whatsapp-api",
      "inboundSharedSecret": "replace-with-internal-secret",
      "outboundPhoneNumberId": "123456789012345",
      "outboundAccessToken": "EAAG...",
      "outboundApiVersion": "v22.0",
      "requestTimeoutMs": 10000,
      "maxRetries": 2,
      "retryBackoffMs": 500,
      "dedupeTtlMs": 300000,
      "maxBodyBytes": 524288,
      "accounts": {
        "default": {
          "enabled": true
        }
      }
    }
  }
}
```

| Option | Description | Default |
|--------|-------------|---------|
| `plugins.entries.whatsapp-api.enabled` | Turn the plugin on or off | — |
| `channels.whatsapp-api.enabled` | Turn the channel on or off | — |
| `webhookPath` | HTTP path OpenClaw listens on for inbound webhooks | `/webhook/whatsapp-api` |
| `inboundSharedSecret` | Secret your router must send (e.g. header `x-openclaw-shared-secret`) | — |
| `outboundPhoneNumberId` | WhatsApp **Phone number ID** from Meta | — |
| `outboundAccessToken` | Bearer token for Graph API sends | — |
| `outboundApiVersion` | Graph API version segment in the URL | e.g. `v22.0` |
| `requestTimeoutMs` | HTTP timeout for outbound calls (ms) | `10000` |
| `maxRetries` | Retries on transient errors | `2` |
| `retryBackoffMs` | Base backoff between retries (ms) | `500` |
| `dedupeTtlMs` | Deduplication window for inbound events (ms) | `300000` |
| `maxBodyBytes` | Max accepted webhook body size (bytes) | `524288` |
| `accounts` | Per-account overrides (multi-tenant); keys are account ids | `{ "default": { "enabled": true } }` |

Per-account fields can override phone id, token, retries, etc. under `channels["whatsapp-api"].accounts.<accountId>`.

## Notes

- Outbound mode is **direct-to-Meta** only (no third-party relay in this plugin).
- Keep tokens out of logs and rotate them on a schedule.

## Links

- [WhatsApp Cloud API — Overview](https://developers.facebook.com/docs/whatsapp/cloud-api)
- [Graph API — WhatsApp messages](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages)
- [OpenClaw](https://github.com/openclaw/openclaw) (ecosystem reference)