> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kapso.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Hermes Agent

> Connect Hermes Agent to WhatsApp with Kapso.

Use `gokapso/hermes-agent-plugin` when a Hermes Agent gateway needs to receive Kapso webhooks, turn inbound WhatsApp messages into Hermes `MessageEvent`s, and send replies through Kapso.

Use [`@kapso/whatsapp-cloud-api`](/docs/whatsapp/typescript-sdk/introduction) directly when you are building your own app and do not need Hermes platform routing.

## Installation

Requires:

| Requirement                     | Use                                                    |
| ------------------------------- | ------------------------------------------------------ |
| Hermes Agent gateway            | Runs the Kapso platform adapter.                       |
| Kapso API key                   | Sends WhatsApp replies through Kapso.                  |
| Connected Kapso WhatsApp number | Sender number for replies and cron delivery.           |
| Public HTTPS URL                | Receives Kapso webhooks. Tailscale Funnel works well.  |
| Node.js/npm                     | Needed only when setup installs or uses the Kapso CLI. |

<CodeGroup>
  ```bash Public repo theme={null}
  hermes plugins install gokapso/hermes-agent-plugin --enable
  ```

  ```bash SSH theme={null}
  hermes plugins install git@github.com:gokapso/hermes-agent-plugin.git --enable
  ```
</CodeGroup>

The installer prompts only for `KAPSO_API_KEY`. Webhook secret, phone number, allowlist, and webhook creation happen in the setup command.

Hermes clones the plugin into `~/.hermes/plugins/kapso` and loads it on the next session or gateway restart.

If your Hermes environment does not already include `aiohttp`:

```bash theme={null}
pip install aiohttp
```

## Setup

Recommended setup:

```bash theme={null}
hermes kapso setup --install-cli --funnel-url https://<your-funnel-host>
```

The setup command can install the Kapso CLI, list connected WhatsApp numbers, ask which number Hermes should use, ask for your WhatsApp user ID, generate `KAPSO_WEBHOOK_SECRET`, and create the phone-number webhook in Kapso.

The created webhook points at:

```text theme={null}
https://<your-funnel-host>/kapso/webhook
```

Non-interactive setup:

```bash theme={null}
hermes kapso setup \
  --api-key "$KAPSO_API_KEY" \
  --phone-number-id "1041695002363992" \
  --home-channel "15551234567" \
  --allowed-users "15551234567" \
  --funnel-url "https://<your-funnel-host>" \
  --configure-webhook \
  --install-cli \
  --no-prompt
```

Restart the gateway after enabling the plugin or changing env vars:

```bash theme={null}
hermes gateway restart
hermes gateway status
```

Check setup:

```bash theme={null}
hermes kapso status
curl http://127.0.0.1:8648/health
curl https://<your-funnel-host>/health
```

## Environment

The setup command writes these values to `~/.hermes/.env`. Manual env vars also work:

```bash theme={null}
KAPSO_API_KEY=kapso_...
KAPSO_WEBHOOK_SECRET=shared_webhook_secret
KAPSO_PHONE_NUMBER_ID=1041695002363992
KAPSO_HOME_CHANNEL=15551234567
KAPSO_ALLOWED_USERS=15551234567
KAPSO_WEBHOOK_URL=https://<your-funnel-host>/kapso/webhook
```

| Variable                | Required                | Use                                                                    |
| ----------------------- | ----------------------- | ---------------------------------------------------------------------- |
| `KAPSO_API_KEY`         | Yes                     | Sends outbound WhatsApp messages.                                      |
| `KAPSO_WEBHOOK_SECRET`  | Yes for signed webhooks | Verifies signed Kapso webhooks. Generated by setup if missing.         |
| `KAPSO_PHONE_NUMBER_ID` | Recommended             | Default sender number ID for replies, manual sends, and cron delivery. |
| `KAPSO_HOME_CHANNEL`    | No                      | Default WhatsApp recipient for `deliver=kapso` cron jobs.              |
| `KAPSO_ALLOWED_USERS`   | No                      | Comma-separated WhatsApp user IDs allowed to talk to the bot.          |
| `KAPSO_ALLOW_ALL_USERS` | No                      | Set `true` for development-only open access.                           |
| `KAPSO_WEBHOOK_URL`     | No                      | Public webhook URL saved by setup for status and debugging.            |
| `KAPSO_BASE_URL`        | No                      | Defaults to `https://api.kapso.ai/meta/whatsapp`.                      |
| `KAPSO_GRAPH_VERSION`   | No                      | Defaults to `v24.0`.                                                   |
| `KAPSO_HOST`            | No                      | Webhook bind host. Defaults to `0.0.0.0`.                              |
| `KAPSO_PORT`            | No                      | Webhook port. Defaults to `8648`.                                      |
| `KAPSO_WEBHOOK_PATH`    | No                      | Webhook path. Defaults to `/kapso/webhook`.                            |

For unsigned local fixtures only:

```bash theme={null}
KAPSO_VERIFY_WEBHOOK_SIGNATURES=false
```

Allow a WhatsApp user later:

```bash theme={null}
hermes kapso setup --allowed-users 15551234567 --no-prompt
hermes gateway restart
```

For local testing only:

```bash theme={null}
hermes kapso setup --allow-all-users --no-prompt
hermes gateway restart
```

## Kapso webhook

The adapter listens on `0.0.0.0:8648` and accepts `POST /kapso/webhook` by default.

With Tailscale Funnel:

```bash theme={null}
tailscale funnel reset
tailscale funnel --bg http://127.0.0.1:8648
tailscale funnel status
```

The setup command can create the webhook for you. If webhook creation fails, create a phone-number scoped Kapso webhook manually:

| Setting         | Value                                      |
| --------------- | ------------------------------------------ |
| Scope           | WhatsApp phone number                      |
| Endpoint URL    | `https://<your-funnel-host>/kapso/webhook` |
| Events          | `whatsapp.message.received`                |
| Kind            | `kapso`                                    |
| Payload version | `v2`                                       |
| Secret          | Same value as `KAPSO_WEBHOOK_SECRET`       |

Use a public HTTPS URL. `localhost` and private network URLs cannot receive Kapso Cloud webhooks.

Health check:

```bash theme={null}
curl http://localhost:8648/health
```

## Send and receive

Inbound Hermes sessions use encoded chat IDs:

```text theme={null}
kapso:<base64url(phone_number_id)>:<base64url(wa_id)>[:<base64url(conversation_id)>]
```

For manual sends or cron delivery, use a plain WhatsApp recipient when `KAPSO_PHONE_NUMBER_ID` is set:

```text theme={null}
15551234567
```

Or include the sender phone number ID explicitly:

```text theme={null}
<phone_number_id>:15551234567
```

Text replies are split at WhatsApp's 4096-character limit. Markdown links become `label (url)`, and `**bold**` becomes WhatsApp `*bold*`.

## Media

Inbound images are downloaded through Kapso, cached locally, and attached to Hermes `MessageEvent.media_urls` for native vision processing.

Inbound audio and voice notes are downloaded through Kapso, cached locally as audio media, and attached to `MessageEvent.media_urls` for Hermes STT. WhatsApp voice notes are cached as `.ogg`.

For local, no-key transcription:

```bash theme={null}
~/.hermes/hermes-agent/venv/bin/python -m pip install -U faster-whisper
hermes config set stt.provider local
hermes config set stt.local.model base
hermes gateway restart
```

For OpenAI transcription:

```bash theme={null}
printf '\nVOICE_TOOLS_OPENAI_KEY=sk-...\n' >> ~/.hermes/.env
hermes config set stt.provider openai
hermes config set stt.openai.model gpt-4o-mini-transcribe
hermes gateway restart
```

Other non-image media currently lands as captions or descriptive placeholders.

## YAML config

Environment variables are the easiest path. YAML config also works:

```yaml theme={null}
gateway:
  platforms:
    kapso:
      enabled: true
      extra:
        api_key: "kapso_..."
        webhook_secret: "shared webhook secret"
        phone_number_id: "1234567890"
```

## Troubleshooting

| Issue                                            | Check                                                                                                                                           |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Plugin installed but no webhooks arrive          | Run `hermes gateway restart`, check `hermes gateway status`, and confirm the public URL points to `/kapso/webhook`.                             |
| Webhook returns `401`                            | `KAPSO_WEBHOOK_SECRET` does not match the Kapso webhook secret, or the signature header is missing.                                             |
| Outbound sends fail with missing phone number ID | Set `KAPSO_PHONE_NUMBER_ID` or send to `<phone_number_id>:15551234567`.                                                                         |
| Bot should not answer everyone                   | Set `KAPSO_ALLOWED_USERS` and do not set `KAPSO_ALLOW_ALL_USERS=true` outside local development.                                                |
| Images do not reach the agent                    | Tail gateway logs for `cached inbound image`; confirm the webhook payload includes `kapso.mediaUrl`, `kapso.media_url`, or an image media `id`. |
| Voice notes do not transcribe                    | Confirm audio was cached under `~/.hermes/audio_cache` or `~/.hermes/cache/audio`, then check the Hermes STT provider config.                   |
| Kapso CLI is missing                             | Run `hermes kapso install-cli` or `npm install -g @kapso/cli`.                                                                                  |

## Source

<Card title="GitHub repository" icon="github" href="https://github.com/gokapso/hermes-agent-plugin">
  Source code, plugin metadata, and issue tracker for the Kapso Hermes Agent plugin.
</Card>
