Plugin Catalog
The ten official plugins maintained in the openwa-plugins marketplace repository. Each entry lists what the plugin does, the permissions it declares, its configuration keys, and the minimum OpenWA version it requires.
Every plugin here is an extension that runs sandboxed in a worker thread and hooks message:received (some hook more). For the runtime model and the permission system, see Plugin Architecture. To install and configure a plugin, see Plugins Overview.
Summary
| Plugin | Does | Permissions | Status | Requires |
|---|---|---|---|---|
| after-hours | Away reply outside business hours | messages:send | stable | ≥ 0.6.2 |
| chat-flow | Stateful numbered-menu bot | messages:send | stable | ≥ 0.7.0 |
| chatwoot-adapter | Two-way Chatwoot inbox sync + handover | net:fetch, conversation:send, webhook:ingress, engine:read | beta | ≥ 0.8.7 |
| faq-bot | Keyword/regex auto-reply | messages:send | stable | ≥ 0.6.1 |
| group-translate | In-group auto-translation | messages:send, engine:read, net:fetch | stable | ≥ 0.7.0 |
| gsheets-logger | Logs message events to a Google Sheet | (none) | stable | ≥ 0.6.1 |
| http-action | Run REST API calls from chat commands | net:fetch, conversation:send | beta | ≥ 0.8.7 |
| supabase-otp-hook | Deliver Supabase phone OTPs over WhatsApp | webhook:ingress, messages:send | beta | ≥ 0.8.16 |
| typebot-connector | Run a Typebot flow as the bot brain | net:fetch, conversation:send | beta | ≥ 0.8.2 |
| voice-transcription | Voice notes → text webhook event | net:fetch, messages:send | beta | ≥ 0.7.0 |
Requires is the minOpenWAVersion. All ten are documented at the versions published in the marketplace catalog; column values come from each plugin's manifest.json.
messages:send lets the plugin send and reply to messages. conversation:send is the integration-SDK capability that relays messages back into a chat (used by the adapters/connectors that own their own send path). engine:read lets a plugin read engine state such as group info. net:fetch lets it make outbound HTTP calls through the host's SSRF-guarded fetch, restricted to hosts in the manifest's net.allow list. webhook:ingress lets a plugin expose a verified inbound URL (signed by the caller — for example Chatwoot HMAC or Standard Webhooks) that the host authenticates before the plugin runs. A plugin that declares no permissions can only read hook events.
after-hours
Replies with a configurable away message to messages received outside business hours.
| Field | Value |
|---|---|
| Identifier | after-hours |
| Version | 0.1.3 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.2 (tested 0.8.1) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/after-hours |
What it does. Holds a per-weekday business-hours schedule interpreted in a configurable IANA timezone. When a message arrives outside the open window for that day, it sends the configured away message as a quoted reply, throttled to at most once per chat per cooldownSec. Group chats are ignored unless respondInGroups is set. A malformed schedule or unknown timezone fails fast and shows as ERROR in the dashboard rather than misbehaving silently.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
schedule | yes | — | JSON object mapping mon..sun to "HH:MM-HH:MM" (24-hour, open < close) or null for closed. An absent day is closed. |
timezone | no | UTC | IANA timezone the schedule is interpreted in, e.g. Asia/Jakarta. |
awayMessage | yes | — | Reply sent outside business hours. |
cooldownSec | no | 3600 | Minimum seconds between after-hours replies to the same chat. 0 replies every time. |
respondInGroups | no | false | Whether to reply in group chats. |
Example schedule:
{ "mon": "09:00-17:00", "tue": "09:00-17:00", "wed": "09:00-17:00",
"thu": "09:00-17:00", "fri": "09:00-17:00", "sat": "09:00-13:00", "sun": null }
chat-flow
An interactive, stateful auto-reply: a trigger word opens a greeting plus a numbered menu, and replies walk a configurable menu tree.
| Field | Value |
|---|---|
| Identifier | chat-flow |
| Version | 1.0.6 |
| Status | stable |
| Requires OpenWA | ≥ 0.7.0 (tested 0.8.1) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/chat-flow |
What it does. A trigger word (or any message, if trigger is empty) sends a greeting and a numbered menu. The user's reply selects an option; selections traverse a menu tree of arbitrary depth, where leaf nodes end the flow. State is kept per (session, chat) and expires after 15 minutes of inactivity; re-sending the trigger restarts an active flow. A reply that matches no option re-sends the current menu. The plugin is session-scoped (sessionScoped) and ships a visual flow editor (configUi) the dashboard opens in a sandboxed frame, so the tree can be designed without hand-editing JSON.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
greeting | yes | — | The greeting plus menu sent when the flow starts. |
options | no | — | The menu tree: an array of { key, text, options? } nodes that nest arbitrarily. |
trigger | no | — | Word that starts the flow (case-insensitive). Empty means any message starts it. |
respondInGroups | no | false | Whether to run in group chats. |
Example options tree:
{
"trigger": "menu",
"greeting": "Hi! Reply with a number:\n1. Pricing\n2. Support",
"options": [
{ "key": "1", "text": "Plans start at Rp100.000/mo." },
{ "key": "2", "text": "Support — reply with a number:\n1. Billing\n2. Technical",
"options": [
{ "key": "1", "text": "Billing: billing@example.com" },
{ "key": "2", "text": "A ticket has been created — we'll reply shortly." }
] }
]
}
chatwoot-adapter
Two-way sync between a WhatsApp session and a Chatwoot inbox, with human handover — relay WhatsApp into Chatwoot, send agent replies back, and silence other OpenWA bots on that chat while a human agent is in control.
| Field | Value |
|---|---|
| Identifier | chatwoot-adapter |
| Version | 0.5.3 |
| Status | beta |
| Requires OpenWA | ≥ 0.8.7 (tested 0.8.7) |
| Type | extension |
| Permissions | net:fetch, conversation:send, webhook:ingress, engine:read |
| Hooks | message:received, message:sent |
| Repository | OpenWA-plugins/chatwoot-adapter |
What it does. Binds one Chatwoot account (apiToken + accountId + inboxId) to a WhatsApp session. Inbound WhatsApp messages (1:1 and groups, with media) are relayed into a Chatwoot API-channel inbox as incoming messages; agent replies (outgoing, non-private) are sent back to WhatsApp. Assigning a human agent in Chatwoot triggers handover — other OpenWA bots stop auto-replying on that chat until the conversation is unassigned. Chatwoot contacts are keyed on the WhatsApp JID, so matching is stable across WhatsApp's @lid migration, and a group maps to one synthetic contact with sender-prefixed messages. Inbound and outbound are serialized by a per-chat lock, so a cold-start burst can't create duplicate contacts or conversations; both directions are idempotent.
Agent replies and conversation-status changes arrive on an account-level Chatwoot webhook that OpenWA verifies (HMAC) before the adapter sees it — agent-bot / inbox webhooks are unsigned and are not supported.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
baseUrl | yes | — | Chatwoot base URL, e.g. https://app.chatwoot.com. Host must be added to net.allow (or auto-allowed via allowConfigHosts). |
apiToken | yes | — | Chatwoot agent-bot API token. Stored redacted. |
accountId | yes | — | Numeric Chatwoot account id. |
inboxId | yes | — | Numeric id of the API-channel inbox. |
relayGroups | no | true | Relay group chats as well as 1:1. |
relayMedia | no | true | Upload media from WhatsApp as Chatwoot attachments. |
relayOwnMessages | no | true | Relay messages the account itself sends from the linked phone. |
backfillLimit | no | 0 | On start, backfill at most this many recent chats per session. |
backfillAllOnce | no | false | Backfill every chat once on first boot. |
The adapter verifies the webhook HMAC signature against the instance's ingress secret, so that secret must equal the secret Chatwoot shows on the webhook's edit form — and Chatwoot generates the secret itself, so it can only be copied into OpenWA. You need Chatwoot v4.12.0 or newer, the first release whose account-level webhooks carry a per-webhook secret and a timestamped signature (X-Chatwoot-Timestamp). Create the webhook at Integrations → Webhooks, subscribed to message_created and conversation_updated, then mint the instance with POST /api/integration/plugins/chatwoot-adapter/instances, passing the Chatwoot webhook secret as secret.
Do not mint from the dashboard: its instance form has no secret field and auto-generates a random one that can never match Chatwoot's — every agent reply then fails HMAC verification with a 401. The secret cannot be edited after minting; to change it, delete the instance and re-mint.
faq-bot
Auto-replies to inbound messages from configurable keyword or regex rules.
| Field | Value |
|---|---|
| Identifier | faq-bot |
| Version | 0.1.7 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.1 (tested 0.8.1) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/faq-bot |
What it does. Matches each inbound message against an ordered list of rules. Each rule is contains, exact (both case-insensitive), or regex (compiled with the i flag). The first matching rule wins, and its reply is sent as a quoted reply. If nothing matches and fallbackReply is set, the fallback is sent, throttled per chat by fallbackCooldownSec. Group chats are ignored unless respondInGroups is set. An invalid regex rule is skipped with a warning; a structurally invalid rules value fails fast and shows as ERROR in the dashboard.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
rules | yes | — | JSON array of { mode, pattern, reply } rules, where mode is contains, exact, or regex. |
fallbackReply | no | "" | Reply sent when no rule matches. Empty stays silent. |
fallbackCooldownSec | no | 600 | Minimum seconds between fallback replies to the same chat. 0 replies every time. |
respondInGroups | no | false | Whether to reply in group chats. |
Example rules:
[
{ "mode": "contains", "pattern": "harga", "reply": "Harga mulai Rp100.000. Ketik 'menu' untuk detail." },
{ "mode": "exact", "pattern": "menu", "reply": "1) Harga 2) Jam buka 3) Lokasi" },
{ "mode": "regex", "pattern": "^/start", "reply": "Selamat datang! Ada yang bisa kami bantu?" }
]
group-translate
Auto-translates group messages between participants' languages via a LibreTranslate backend, controlled in-chat with
/trcommands.
| Field | Value |
|---|---|
| Identifier | group-translate |
| Version | 1.0.6 |
| Status | stable |
| Requires OpenWA | ≥ 0.7.0 (tested 0.8.1) |
| Type | extension |
| Permissions | messages:send, engine:read, net:fetch |
| Hooks | message:received |
| Repository | OpenWA-plugins/group-translate |
What it does. Learns each group member's language from what they type (or pins it with /tr setlang), then posts a combined reply translating each message into the other languages present. Everything is managed in-chat with /tr commands: read-only commands are open to anyone; state-changing commands are admin-gated, resolved via ctx.engine.getGroupInfo (hence engine:read). Translation is disabled until an admin runs /tr on. Outbound translate calls go through the host's SSRF-guarded ctx.net.fetch, so the backend host must also appear in the manifest's net.allow allowlist (it ships allowing localhost:7001). A per-call timeout plus a circuit breaker back off a slow or flaky backend instead of stalling the chat.
The host denies any outbound host not in this plugin's manifest net.allow. Before packaging, set net.allow to the host:port of your libretranslateUrl (it ships allowing localhost:7001). For plugins, this per-plugin net.allow allowlist replaces the old SSRF_ALLOWED_HOSTS host-allowlist guidance from the core modules — the manifest is what opens a host. The host-level loopback guard is separate: a localhost/127.0.0.1 backend still needs SSRF_ALLOWED_HOSTS set on the host (the SSRF guard blocks loopback by default), exactly as the voice-transcription entry notes.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
libretranslateUrl | yes | http://localhost:7001 | Base URL of your LibreTranslate instance. Its host:port must also be in the manifest net.allow. |
libretranslateApiKey | no | — | Secret API key, if your instance requires one. Redacted on read. |
timeoutMs | no | 4000 | Per-call timeout. Keep at or below the host hook budget (5000 ms). |
commandPrefix | no | /tr | The in-chat command prefix. |
minLength | no | 2 | Minimum message length to translate. |
maxLength | no | 2000 | Maximum message length to translate. |
denyReply | no | false | Reply "admins only" when a non-admin runs a restricted command. |
In-chat commands (default prefix /tr):
| Command | Who | Effect |
|---|---|---|
/tr help | anyone | Show the command list. |
/tr status | anyone | Show whether translation is on and per-participant languages. |
/tr on · /tr off | admin | Enable or disable translation in this group. |
/tr setlang <code> [@user] | admin | Pin a participant's language. |
/tr auto [@user] | admin | Resume auto-learning a participant's language. |
/tr ignore · /tr unignore [@user] | admin | Skip or resume translating a participant. |
/tr grant · /tr revoke [@user] | admin | Delegate or remove control to a non-admin participant. |
gsheets-logger
Logs every WhatsApp message event to a Google Sheet via a service account — an append-only audit trail across all sessions.
| Field | Value |
|---|---|
| Identifier | gsheets-logger |
| Version | 0.2.3 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.1 (tested 0.8.1) |
| Type | extension |
| Permissions | (none) |
| Hooks | message:received, message:sent, message:failed, message:ack |
| Repository | OpenWA-plugins/gsheets-logger |
What it does. Writes one row per message event — across message:received, message:sent, message:failed, and message:ack — to a Google Sheet, using a fixed 14-column schema. It authenticates as a Google service account (JWT RS256) with no runtime dependencies. Writes are buffered and flushed in batches, with retain-on-failure (rows are kept and retried on a Sheets error), a 5000-row cap, and persistence to plugin storage so the buffer survives restarts. The plugin declares no permissions: it only reads hook events and writes to your sheet, never sending messages or reading contacts.
The 14 columns, one row per event:
timestamp | sessionId | event | direction | chatId | from | to | senderName | isGroup | type | body | messageId | ackStatus | error
Enabling the Google Sheets API on the project and sharing the spreadsheet with the service account (as Editor) are independent steps. Skipping either fails with a different 403 in the logs. See the plugin's README for the full setup walkthrough.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
serviceAccountJson | yes | — | Full service-account key JSON. Stored as a secret. Share the sheet with this account's client_email as Editor. |
spreadsheetId | yes | — | The ID from the sheet URL, between /d/ and /edit. |
sheetTab | no | Logs | Target tab name. The tab must already exist, with a header row of your choosing — the plugin appends data rows only. |
flushIntervalSec | no | 5 | Seconds between flushes. |
flushBatchSize | no | 20 | Flush early once this many rows are buffered. |
message:ack rowsThe message:ack rows fill the messageId and ackStatus columns and require OpenWA ≥ 0.6.1; older builds never emitted the hook.
http-action
Trigger safe REST API requests from WhatsApp commands and map JSON responses back to chat — connect WhatsApp to an existing HTTPS API without webhook middleware.
| Field | Value |
|---|---|
| Identifier | http-action |
| Version | 0.1.0 |
| Status | beta |
| Requires OpenWA | ≥ 0.8.7 (runtime test pending) |
| Type | extension |
| Permissions | net:fetch, conversation:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/http-action |
What it does. Each inbound message is matched against an ordered list of actions; the first matching action wins, and its trailing arguments are parsed (a double-quoted run is kept as one argument). A matched action issues a fixed-origin GET or JSON POST to a single HTTPS baseUrl — the path is server-relative and every interpolated segment is URL-encoded, so a message can never change the host or inject a path segment. The JSON response is rendered into a reply via {{response.field}} templates, with separate notFoundTemplate and errorTemplate. Direct chats are the default; group chats are ignored unless respondInGroups is set. One request runs per message, idempotent across WhatsApp redelivery (storage-backed dedup), with a per-chat cooldown and an off-dispatch handler so a slow upstream never stalls the inbound hook.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
baseUrl | yes | — | Single HTTPS origin every request targets. Auto-added to net.allow via allowConfigHosts. |
actions | yes | — | JSON array of action objects (shape below). |
authType | no | none | none, bearer, or apikey. |
authToken | cond. | — | Bearer token or API key, per authType. Stored redacted. |
apiKeyHeader | cond. | X-API-Key | Header name when authType is apikey. |
respondInGroups | no | false | Run in group chats. |
timeoutMs | no | 3000 | Per-request timeout. Min 500. |
cooldownSeconds | no | 3 | Per-chat cooldown between runs of the same action. |
Each action is { id, match: { type, value, caseSensitive? }, request: { method, path, query?, headers?, bodyTemplate? }, replyTemplate, notFoundTemplate?, errorTemplate? }. match.type is exact or prefix. Template variables: args.0, args.1, … plus message.id, message.body, chat.id, sender.id, sender.phone, sender.name, session.id, and response.<path> (the parsed JSON body).
supabase-otp-hook
Deliver Supabase Auth phone OTPs over WhatsApp. Supabase's Send SMS hook (Standard Webhooks-signed) is verified host-side, and the plugin sends the OTP via an OpenWA WhatsApp session — with synchronous feedback to Supabase.
| Field | Value |
|---|---|
| Identifier | supabase-otp-hook |
| Version | 0.1.0 |
| Status | beta |
| Requires OpenWA | ≥ 0.8.16 (tested 0.8.16) |
| Type | extension |
| Permissions | webhook:ingress, messages:send |
| Hooks | — (ingress-driven) |
| Repository | OpenWA-plugins/supabase-otp-hook |
What it does. Supabase calls the plugin's OpenWA ingress URL. The host verifies the Standard Webhooks signature against the instance secret (webhook-id / webhook-timestamp / webhook-signature, base64 HMAC-SHA256, constant-time, 5-minute replay window) — a bad signature returns 401 before the plugin runs. The host then runs a session-alive preflight that returns 503 on a dead WhatsApp session, and fast-acks Supabase with 200 on accept. The plugin parses { user: { phone }, sms: { otp } }, normalizes the phone to <digits>@c.us, and fires the WhatsApp send in the background (bounded to 5 s) so a timeout-induced retry can't duplicate the OTP. Events are ordered per user.id and deduped on webhook-id.
This is the canonical example of a plugin that owns an inbound URL rather than a chat hook: the webhook:ingress capability exposes the verified endpoint, and verification happens in the host (not the plugin) so a signature failure never reaches plugin code.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
appName | yes | — | Application name interpolated into the OTP message via {appName}. |
messageTemplate | no | {appName} | Your verification code is {otp} | Message body. Supports {appName} and {otp} placeholders. |
fallbackSessionId | no | — | Session to send through if the request doesn't name one. |
debug | no | false | Verbose logging. |
The instance secret you set in OpenWA is the same secret Supabase generates when you register the webhook URL — the host uses it to verify every delivery. OpenWA v0.8.16+ is required for the standard-webhooks signature scheme and the ingress preflight/response contract.
typebot-connector
Run a Typebot flow as the brain of a WhatsApp bot — inbound messages drive a Typebot chat session, and the bot's text, media, and numbered-choice replies come back to WhatsApp.
| Field | Value |
|---|---|
| Identifier | typebot-connector |
| Version | 0.1.0 |
| Status | beta |
| Requires OpenWA | ≥ 0.8.2 (tested 0.8.2) |
| Type | extension |
| Permissions | net:fetch, conversation:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/typebot-connector |
What it does. On each inbound message in scope, the plugin resumes (or starts) the contact's Typebot session over the live Chat API and renders the bot's reply bubbles back into WhatsApp. Text bubbles are converted to WhatsApp formatting; image, video, and audio bubbles are sent as media; a choice step is shown as a numbered list and the contact's numeric reply is mapped back to the option. Typed inputs (email, number, date, …) are re-asked on a bad value, and a file-input step accepts a photo or file the contact sends. The session resets when the flow ends or after the idle timeout, so the next message starts fresh. It runs sandboxed in the plugin worker and polls Typebot's Chat API over the host's SSRF-guarded ctx.net.fetch — no public URL or webhook is required. Typebot holds the session state; the plugin persists only the session id and the bot's expected next input.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
apiHost | yes | https://typebot.io | Typebot API host. https://typebot.io for Cloud, or your self-hosted URL. Auto-added to net.allow. |
publicId | yes | — | Typebot public ID from the bot's Share settings. |
apiToken | no | — | API token, only for a restricted/preview bot. Stored redacted. |
respondInGroups | no | true | Run in group chats. |
sessionTimeoutMinutes | no | 30 | Idle minutes before the session resets. |
passContactVariables | no | true | Pass waNumber, waName, waChatId as Typebot flow variables. |
mediaHost | no | — | Host serving Typebot media bubbles (when separate from apiHost). Auto-added to net.allow. |
voice-transcription
Transcribes inbound voice notes to text via an OpenAI-compatible speech-to-text backend and delivers a
message.transcriptionevent to your webhook.
| Field | Value |
|---|---|
| Identifier | voice-transcription |
| Version | 1.0.2 |
| Status | beta |
| Requires OpenWA | ≥ 0.7.0 (tested 0.8.1) |
| Type | extension |
| Permissions | net:fetch, messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/voice-transcription |
What it does. On each inbound voice note, runs speech-to-text against any OpenAI-compatible /v1/audio/transcriptions endpoint (self-hosted Speaches/faster-whisper, or hosted Groq/OpenAI) and POSTs a message.transcription event to your delivery webhook — so a bot or AI can read and reply to audio. Transcription runs off the message-delivery path as an un-awaited task: it never touches the message.received payload, never blocks delivery, and is not bound by the 5-second hook budget. It delivers completed (with the transcript), failed, or skipped status, so a consumer always knows a voice note arrived even when it can't be read. The plugin is in beta and disabled until enabled.
transcription.text is attacker-controlled speech; the event marks it untrusted: true. A downstream LLM responder must place it in a user role, never a system or trusted context — a caller can speak injection instructions a typist never would.
Delivered event (POSTed to deliveryWebhookUrl):
{
"event": "message.transcription",
"sessionId": "…",
"messageId": "<waMessageId>",
"chatId": "…@s.whatsapp.net",
"status": "completed",
"source": "speech-to-text",
"untrusted": true,
"transcription": { "text": "…", "language": "es", "provider": "faster-whisper", "model": "small" }
}
Correlate it to the original voice note by messageId. It arrives shortly after message.received, out of order — do not assume ordering. When deliverySecret is set, the body is HMAC-SHA256 signed in X-OpenWA-Signature: sha256=<hex>, the same scheme as core webhooks.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
sttBaseUrl | yes | — | OpenAI-compatible STT base URL (/v1/audio/transcriptions is appended). Host must be in net.allow; a localhost target also needs SSRF_ALLOWED_HOSTS on the host. |
sttApiKey | no | — | Bearer key for a hosted backend (Groq/OpenAI). Blank for a local Speaches instance. Stored redacted. |
model | no | small | Whisper model name, e.g. small, base, whisper-large-v3-turbo. |
language | no | (auto) | Optional BCP-47 hint. Blank auto-detects. |
provider | no | faster-whisper | Informational label recorded in the delivered event. |
timeoutMs | no | 20000 | Per-request STT timeout. Min 1000, max 30000. |
enabledMessageTypes | no | ["voice"] | Message types to transcribe. Add audio to also transcribe non-PTT audio (more cost). |
maxSizeBytes | no | 16777216 | Skip audio larger than this. |
maxPerHour | no | 60 | Best-effort per-session hourly transcription cap. |
deliveryWebhookUrl | cond. | — | Endpoint receiving the event. Host must be in net.allow. Optional if you only use chatDelivery. |
deliverySecret | no | — | HMAC-SHA256 signs the body in X-OpenWA-Signature. Stored redacted. |
deliveryTimeoutMs | no | 5000 | Delivery POST timeout. Min 1000, max 30000. |
chatDelivery | no | off | Also post the transcript into WhatsApp: off (webhook only), self (a note to your own number), or reply (quote-reply to the sender). |
This plugin ships net.allow for localhost, 127.0.0.1, api.groq.com:443, and api.openai.com:443. For any other STT or delivery host, add its host:port to net.allow and re-package. For a deeper how-to, see Voice Transcription.
Per-session config support
A sessionScoped plugin (the default) can carry per-session config overrides — two WhatsApp sessions under one plugin instance can run different settings. A plugin honors an override only if it re-reads ctx.config inside its hook rather than using a snapshot cached at enable. The status per plugin:
| Plugin | Per-session config | Notes |
|---|---|---|
| after-hours | ✅ Supported | All fields per session; takes effect on next message. |
| chat-flow | ✅ Supported | All fields per session; flow state is per (session, chat). |
| chatwoot-adapter | ✅ Supported | All fields per session — first-class multi-tenant shape. |
| faq-bot | ✅ Supported | All fields per session (different rule sets per number). |
| group-translate | ⚠️ Supported, with caveat | Config-signature caching; multi-backend isolation needs one instance per session. |
| gsheets-logger | ❌ Not supported | Single-buffer single-sink design; use one instance per session. |
| http-action | ✅ Supported | All fields per session (different endpoints/action sets). |
| supabase-otp-hook | ✅ Supported | All fields per session; applies to the instance's bound session. |
| typebot-connector | ✅ Supported | All fields per session; flow state is per (session, chat). |
| voice-transcription | ⚠️ Supported, with caveat | Config-signature caching; multi-backend isolation needs one instance per session. |
For how to write a plugin that honors overrides, see Building a Plugin.