Skip to main content
Use gokapso/hermes-agent-plugin when a Hermes Agent gateway needs to receive Kapso webhooks, turn inbound WhatsApp messages into Hermes MessageEvents, and send replies through Kapso. Use @kapso/whatsapp-cloud-api directly when you are building your own app and do not need Hermes platform routing.

Installation

Requires:
RequirementUse
Hermes Agent gatewayRuns the Kapso platform adapter.
Kapso API keySends WhatsApp replies through Kapso.
Connected Kapso WhatsApp numberSender number for replies and cron delivery.
Public HTTPS URLReceives Kapso webhooks. Tailscale Funnel works well.
Node.js/npmNeeded only when setup installs or uses the Kapso CLI.
hermes plugins install gokapso/hermes-agent-plugin --enable
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:
pip install aiohttp

Setup

Recommended setup:
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:
https://<your-funnel-host>/kapso/webhook
Non-interactive setup:
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:
hermes gateway restart
hermes gateway status
Check setup:
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:
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
VariableRequiredUse
KAPSO_API_KEYYesSends outbound WhatsApp messages.
KAPSO_WEBHOOK_SECRETYes for signed webhooksVerifies signed Kapso webhooks. Generated by setup if missing.
KAPSO_PHONE_NUMBER_IDRecommendedDefault sender number ID for replies, manual sends, and cron delivery.
KAPSO_HOME_CHANNELNoDefault WhatsApp recipient for deliver=kapso cron jobs.
KAPSO_ALLOWED_USERSNoComma-separated WhatsApp user IDs allowed to talk to the bot.
KAPSO_ALLOW_ALL_USERSNoSet true for development-only open access.
KAPSO_WEBHOOK_URLNoPublic webhook URL saved by setup for status and debugging.
KAPSO_BASE_URLNoDefaults to https://api.kapso.ai/meta/whatsapp.
KAPSO_GRAPH_VERSIONNoDefaults to v24.0.
KAPSO_HOSTNoWebhook bind host. Defaults to 0.0.0.0.
KAPSO_PORTNoWebhook port. Defaults to 8648.
KAPSO_WEBHOOK_PATHNoWebhook path. Defaults to /kapso/webhook.
For unsigned local fixtures only:
KAPSO_VERIFY_WEBHOOK_SIGNATURES=false
Allow a WhatsApp user later:
hermes kapso setup --allowed-users 15551234567 --no-prompt
hermes gateway restart
For local testing only:
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:
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:
SettingValue
ScopeWhatsApp phone number
Endpoint URLhttps://<your-funnel-host>/kapso/webhook
Eventswhatsapp.message.received
Kindkapso
Payload versionv2
SecretSame value as KAPSO_WEBHOOK_SECRET
Use a public HTTPS URL. localhost and private network URLs cannot receive Kapso Cloud webhooks. Health check:
curl http://localhost:8648/health

Send and receive

Inbound Hermes sessions use encoded chat IDs:
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:
15551234567
Or include the sender phone number ID explicitly:
<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:
~/.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:
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:
gateway:
  platforms:
    kapso:
      enabled: true
      extra:
        api_key: "kapso_..."
        webhook_secret: "shared webhook secret"
        phone_number_id: "1234567890"

Troubleshooting

IssueCheck
Plugin installed but no webhooks arriveRun hermes gateway restart, check hermes gateway status, and confirm the public URL points to /kapso/webhook.
Webhook returns 401KAPSO_WEBHOOK_SECRET does not match the Kapso webhook secret, or the signature header is missing.
Outbound sends fail with missing phone number IDSet KAPSO_PHONE_NUMBER_ID or send to <phone_number_id>:15551234567.
Bot should not answer everyoneSet KAPSO_ALLOWED_USERS and do not set KAPSO_ALLOW_ALL_USERS=true outside local development.
Images do not reach the agentTail 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 transcribeConfirm audio was cached under ~/.hermes/audio_cache or ~/.hermes/cache/audio, then check the Hermes STT provider config.
Kapso CLI is missingRun hermes kapso install-cli or npm install -g @kapso/cli.

Source

GitHub repository

Source code, plugin metadata, and issue tracker for the Kapso Hermes Agent plugin.