Profile
Back to NewsBack
GitHub Trending 26 min
Reader Mode
aldinokemal/go-whatsapp-web-multidevice: GOWA - WhatsApp REST API with support for UI, Multi Account, Webhooks, and MCP, and Chatwoot. Built with Golang for efficient memory use.

aldinokemal/go-whatsapp-web-multidevice: GOWA - WhatsApp REST API with support for UI, Multi Account, Webhooks, and MCP, and Chatwoot. Built with Golang for efficient memory use.

11 hours ago

GoWA Logo

Go WhatsApp — Built for Efficient Memory Use

GitHub Sponsors</a> Greptile: The War on Bugs</a>

Support GOWA

Does GOWA save you development time or power your business? Help sustain the project through GitHub Sponsors.

Sponsorship helps make time for:

  • Investigating bugs and keeping up with WhatsApp and whatsmeow changes.
  • Improving regression tests, documentation, and integration examples.
  • Reviewing contributions and preparing releases.
Monthly sponsorship helps make ongoing maintenance more sustainable. Individuals and teams are welcome.

Sponsor GOWA on GitHub →

Not ready to sponsor? Reproducible bug reports, documentation improvements, and sharing GOWA also help.


!release version !Build Image !Binary Release

ARM, AMD64, and MCP Support

Download:

Documentation

Read the detailed GOWA documentation for setup, authentication, device management, webhooks, examples, and the API reference. The documentation tracks merged main, including changes after v9.7.0. Check the release notes and GET /app/info when using a pinned binary or container tag.

n8n Community Node

  • n8n package
  • Go to Settings → Community Nodes, enter @aldinokemal2104/n8n-nodes-gowa, and select Install.

Breaking Changes

  • v6
- REST mode requires rest instead of . - Example: ./whatsapp rest instead of ./whatsapp. - MCP mode required mcp. - Example: ./whatsapp mcp.
  • v7
- Starting with version 7.x, binaries are built with GoReleaser and can be downloaded from the latest release.
  • v8
- Multi-device support: You can now connect and manage multiple WhatsApp accounts simultaneously in a single server instance. - New Device Management API: New endpoints under /devices manage multiple devices. - Device scoping required: All device-scoped REST API calls now require either: - X-Device-Id header, or - device_id query parameter. - If only one device is registered, it is used as the default. - WebSocket device selection: Connect to /ws?device_id= for the device middleware. The current WebSocket hub broadcasts dashboard events to all authenticated subscribers. This parameter does not isolate events between accounts; use per-device webhooks for application integrations. - Remote UI support: CORS allows the Authorization and X-Device-Id headers, so a standalone web UI (for example, gowa-ui) hosted on another origin can call the API directly. GET /app/info exposes the version and media size limits. Because browsers cannot set headers on WebSocket connections, pass /ws?device_id=&authorization= when Basic Auth is enabled (use TLS—the credential is visible in the URL). - Webhook payload changes: All webhook payloads now include a top-level device_id field identifying which device received the event:
{
      "event": "message",
      "device_id": "[email protected]",
      "payload": { ... }
    }
  • v9
- MCP and API are unified under rest: MCP is no longer a separate mode or process. Run ./whatsapp rest to serve both the REST API and MCP; MCP is available at /mcp (no standalone mcp subcommand). See MCP Server (Model Context Protocol) for migration details. - UI moved to a separate repository: The web dashboard is no longer bundled in this repo. It now lives at aldinokemal/gowa-ui and ships as a single self-contained gowa-ui.html. The server downloads the latest dashboard release at startup, verifies its SHA-256 digest, caches it under storages/ui/, and serves it at /. See Web dashboard (gowa-ui) for the APP_UI_* settings, supply-chain pinning, and air-gapped deployment.

Features

  • Send WhatsApp messages through the HTTP API. See docs/openapi.yaml for details.
  • MCP (Model Context Protocol) server support — Integrate with AI agents and tools using a standardized protocol.
  • Optional MCP OAuth 2.1 — Connect remote MCP clients that cannot supply a Basic Auth header. See
MCP OAuth.
  • Mention users in message text and image, video, or file captions:
- @phoneNumber - Example: Hello @628974812XXXX, @628974812XXXX
  • Ghost mentions (mention all) — Mention group participants without showing @phone in the message text.
- Pass phone numbers in the mentions field to mention users without a visible @ in the message. - Use the special keyword @everyone to automatically mention all group participants.
  • Post WhatsApp status updates.
  • Mark incoming audio messages and voice notes as played.
  • Scheduled sends — Schedule supported send types once or on a daily, weekly, or monthly repeat.
- Add scheduled_at (RFC3339) and timezone (IANA) to a send request; recurrence, weekdays, day_of_month, end_at, and occurrence_limit control repeats. - Schedules survive restarts and wait for an offline device; list, pause, resume, or cancel them at /send/schedules. - A failed occurrence is retried under the same WhatsApp message id, so a send whose acknowledgment was lost is not retried under a fresh identity. Poll retries also reuse the persisted secret. New-protocol occurrences interrupted by a restart resume the same way; legacy attempts with unknown outcomes are skipped or failed. This does not guarantee exactly-once delivery.
  • Newsletter channels — Follow or unfollow channels, fetch metadata by JID or invite, query the channel directory, and download channel media.
- Read messages with /newsletter/messages, refresh counters with /newsletter/message-updates, and manage renewable live counter subscriptions with /newsletter/live-updates. - Live subscriptions are in memory. Subscribe again after a server restart; counter events arrive as newsletter.message webhooks.
  • Server metrics — Authenticated GET /app/metrics reports process, memory, disk, device counts, and storage metadata without a device header. It returns JSON, not Prometheus text.
  • Contact presence — POST /user/presence/subscribe subscribes to a contact's online/offline updates, delivered through the presence webhook event. Sending your own presence remains under /send/presence.
  • Avatar errors — GET /user/avatar distinguishes 404 AVATAR_NOT_SET from 403 AVATAR_HIDDEN. Use the error code before invalidating a cached image.
  • Send stickers — Automatically convert images to WebP sticker format.
- Supports JPG, JPEG, PNG, WebP, and GIF formats. - Automatically resizes images to 512×512 pixels. - Preserves transparency in PNG images. - Animated WebP stickers are supported but must meet WhatsApp requirements: - Exactly 512×512 pixels. - Less than 500 KB. - No more than 10 seconds long. - If an animated sticker does not meet these requirements, resize it before uploading with a tool such as ezgif.com.
  • Compress images before sending.
  • Compress videos before sending.
  • Customize the OS name shown as the linked device name in WhatsApp:
- --os=Chrome or --os=MyApplication
  • Basic Auth with multiple credentials:
- --basic-auth=kemal:secret,toni:password,userName:secretPassword - Short form: -b=kemal:secret,toni:password,userName:secretPassword
  • Subpath deployment support:
- --base-path="/gowa" allows deployment under a path such as /gowa.
  • Customizable port and debug mode:
- --port 8000 - --debug true
  • Automatic replies to incoming messages:
- --autoreply="Don't reply to this message"
  • Automatically mark incoming messages as read:
- --auto-mark-read=true
  • Automatically download media from incoming messages:
- --auto-download-media=false disables automatic media downloads (default: true). - See Media storage and cleanup for storage locations and manual retention commands.
  • Ignore downloading status media:
- --ignore-status-media=true disables downloading status media (default: false).
  • Automatically reject incoming calls:
- --auto-reject-call=true or WHATSAPP_AUTO_REJECT_CALL=true (see Webhook Payload for call events).
  • Configurable presence on connect:
- --presence-on-connect=unavailable or WHATSAPP_PRESENCE_ON_CONNECT=unavailable - available — Mark the account as online (suppresses phone notifications). - unavailable — Register the push name without going online (default; preserves phone notifications). - none — Skip presence entirely (the push name is not registered, so contacts may see - as the name).
  • Daily presence pulse:
- --presence-pulse-enabled=true or WHATSAPP_PRESENCE_PULSE_ENABLED=true (default: true). - --presence-pulse-interval=24h controls how often each connected device is pulsed. - --presence-pulse-duration=5m controls how long the account stays available before returning to unavailable.
  • Webhooks for received messages and other events:
- --webhook="http://yourwebhook.site/handler" - Short form: -w="http://yourwebhook.site/handler" - See Webhook Payload Documentation for details.
  • Per-device webhooks — Each device can have its own webhook URL and event filters.
- Set via API: PATCH /devices/:device_id/webhook with {"webhook_url": "https://device-webhook.site/handler"}. - Get via API: GET /devices/:device_id/webhook. - When a device has a custom webhook, events for that device are sent to the device-specific URL. - When no device webhook is set, events fall back to the global webhook (--webhook). - Set webhook_url to an empty string with PATCH to clear it and use the global webhook. - Set WHATSAPP_WEBHOOK_DEVICE_MERGE_GLOBAL=true (or --webhook-device-merge-global=true) to make a device webhook an addition instead of a replacement: the global --webhook URLs still receive the device's events (signed with the global secret, filtered by WHATSAPP_WEBHOOK_EVENTS) while the device URL keeps its own secret and event filter.
  • Webhook signatures — Webhook requests include an HMAC-SHA-256 signature in the X-Hub-Signature-256
header. Set a private key with --webhook-secret or WHATSAPP_WEBHOOK_SECRET. The old published default secret is not used. If no secret is configured, the process generates a random key and logs a warning; that key changes on restart, so receivers should not rely on it.

Generate a stable key once, for example with openssl rand -hex 32, and set it with: - --webhook-secret=""

  • Webhook payload documentation — For detailed schemas, security implementation, and integration examples,
see Webhook Payload Documentation.
  • Webhook event filtering — Filter which events are forwarded to your webhook with:
- --webhook-events="message,message.ack" (a comma-separated list), or - WHATSAPP_WEBHOOK_EVENTS=message,message.ack.

Available Webhook Events:

| Event | Description | |----------------------|-----------------------------------------------| | message | Text, media, contact, location messages | | message.reaction | Emoji reactions to messages | | message.revoked | Deleted/revoked messages | | message.edited | Edited messages | | message.ack | Delivery and read receipts | | message.deleted | Messages deleted for the user | | presence | Contact online/offline changes after presence subscription | | chat_presence | Typing and recording indicators from contacts | | group.participants | Group member join/leave/promote/demote events | | group.info | Group name (subject) changed | | group.joined | You were added to a group | | label.edit | WhatsApp label metadata changed | | label.association | Label applied to or removed from a chat | | newsletter.joined | You subscribed to a newsletter/channel | | newsletter.left | You unsubscribed from a newsletter | | newsletter.message | Live counter updates from an active /newsletter/live-updates subscription | | newsletter.mute | Newsletter mute setting changed | | call.offer | Incoming call received | | device.logged_out | The device was logged out, with the reason | | device.temporary_ban | WhatsApp temporarily banned the account | | device.reachout_timelock | WhatsApp restricted starting new chats |

If this setting is empty, all events are forwarded.

  • Webhook JID filtering
You can skip events for specific chats or senders (for example, mute all groups) before they are forwarded: - --webhook-ignore-jids="@g.us,[email protected]" (a comma-separated list), or - [email protected]. - Supports the @g.us / @s.whatsapp.net / @lid wildcards (match a whole address space) and exact JIDs. - This filters by conversation or sender and is independent of --webhook-events, which filters by event type. The Chatwoot integration has a separate CHATWOOT_IGNORE_JIDS setting.
  • Webhook TLS configuration
If you encounter TLS certificate verification errors when using webhooks (e.g., with Cloudflare tunnels or self-signed certificates):

tls: failed to verify certificate: x509: certificate signed by unknown authority

You can disable TLS certificate verification with: - --webhook-insecure-skip-verify=true, or - WHATSAPP_WEBHOOK_INSECURE_SKIP_VERIFY=true.

Security Warning: This option disables TLS certificate verification and should only be used in: - Development or testing environments. - Internal networks with self-signed certificates.

A Cloudflare tunnel does not make an unverified outbound TLS connection safe. For production environments, use a valid TLS certificate (for example, from Let's Encrypt) instead of disabling verification.

Configuration

To store WhatsApp sessions and keys in managed PostgreSQL, follow the Neon setup guide.

Configuration is initialized from flags, then most environment settings are applied. A set environment value can therefore override the matching flag. The .env file only fills variables not already set in the process environment.

Explicit flags win for MCP OAuth settings, --allow-unauthenticated, --trusted-origins, --chatwoot-webhook-allow-unsigned, and --webhook-all-device-receipts. Configure each option in one place to avoid conflicting values. For example, WHATSAPP_WEBHOOK_INSECURE_SKIP_VERIFY=true overrides --webhook-insecure-skip-verify=false.

Environment Variables

To use environment variables:

  1. From the repository root, copy the example file: cp src/.env.example src/.env.
  2. Update the values in src/.env as needed.
  3. Alternatively, set the same variables in the process environment.

Available Environment Variables

| Variable | Description | Default | Example | |-----------------------------------------|---------------------------------------------------------------|----------------------------------------------|-----------------------------------------------| | APP_PORT | Application port | 3000 | APP_PORT=8080 | | APP_HOST | Host address to bind the server | 0.0.0.0 | APP_HOST=127.0.0.1 | | APP_DEBUG | Enable debug logging | false | APP_DEBUG=true | | APP_OS | OS name (device name in WhatsApp) | GOWA | APP_OS=MyApp | | APP_BASIC_AUTH | Basic authentication credentials. Required for a non-loopback bind, with a username and password in every entry; loopback stays open for local dev | - | APP_BASIC_AUTH=user1:pass1,user2:pass2 | | APP_ALLOW_UNAUTHENTICATED | Expose the API with no Basic Auth on a non-loopback bind (insecure; for reverse proxies) | false | APP_ALLOW_UNAUTHENTICATED=true | | APP_TRUSTED_ORIGINS | Extra browser origins for state-changing requests (request host and specific CORS origins are included). A reverse proxy that rewrites Host needs proxy_set_header Host $host, or APP_TRUSTED_PROXIES with X-Forwarded-Host, or the public origin listed here. App and extension origins such as capacitor://localhost are matched as written. null is never trusted: gowa-ui opened from a file:// URL cannot make changes or open /ws, so serve it from gowa (the default) or from an http(s) origin listed here | - | APP_TRUSTED_ORIGINS=https://ui.example.com | | APP_BASE_PATH | Base path for subpath deployment | - | APP_BASE_PATH=/gowa | | APP_TRUSTED_PROXIES | Trusted proxy IP ranges for reverse proxy | - | APP_TRUSTED_PROXIES=0.0.0.0/0 | | APP_CORS_ALLOWED_ORIGINS | Allowed CORS origins (any origin when empty). A wildcard (*, the default) does not authorize state-changing requests or /ws; a dashboard on a separate origin must be listed in APP_TRUSTED_ORIGINS or here as a specific origin | - | APP_CORS_ALLOWED_ORIGINS=https://ui.example.com | | APP_UI_ENABLED | Serve the downloaded gowa-ui dashboard | true | APP_UI_ENABLED=false | | APP_UI_AUTO_UPDATE | Download and periodically refresh the latest dashboard | true | APP_UI_AUTO_UPDATE=false | | APP_UI_REPO | GitHub repository containing gowa-ui releases | aldinokemal/gowa-ui | APP_UI_REPO=my-org/gowa-ui | | APP_UI_ASSET_NAME | Dashboard release asset filename | gowa-ui.html | APP_UI_ASSET_NAME=gowa-ui.html | | APP_UI_UPDATE_INTERVAL | Interval between dashboard update checks | 3h | APP_UI_UPDATE_INTERVAL=6h | | APP_UI_GITHUB_TOKEN | Optional GitHub token for a higher API rate limit | - | APP_UI_GITHUB_TOKEN=github_pat_xxx | | APP_UI_ASSET_SHA256 | Optional SHA-256 pin for the dashboard asset | - | APP_UI_ASSET_SHA256= | | MCP_ENABLED | Serve the streamable HTTP MCP endpoint at /mcp | true | MCP_ENABLED=false | | MCP_OAUTH_ENABLED | Enable OAuth 2.1 authentication for MCP | false | MCP_OAUTH_ENABLED=true | | MCP_OAUTH_ISSUER_URL | Public HTTPS OAuth issuer URL | - | MCP_OAUTH_ISSUER_URL=https://gowa.example.com | | MCP_OAUTH_RESOURCE_URL | Optional canonical public MCP URL | Derived from issuer and base path | MCP_OAUTH_RESOURCE_URL=https://gowa.example.com/mcp | | MCP_OAUTH_DB_URI | SQLite URI for OAuth clients, codes, and token hashes | file:storages/oauth.db | MCP_OAUTH_DB_URI=file:storages/oauth.db | | DB_URI | Database connection URI. A Postgres URI without sslmode (or PGSSLMODE) uses verify-full for remote hosts and disable for loopback targets (localhost, 127.0.0.1, ::1) or unix sockets; set sslmode=require to keep unverified TLS. | file:storages/whatsapp.db | DB_URI=postgres://user:pass@host/db | | DB_KEYS_URI | Optional database URI for encryption/session key cache. Leave blank to use DB_URI; avoid in-memory storage in production because restarts can lose WhatsApp session state. A Postgres URI gets the same sslmode default as DB_URI. | - | DB_KEYS_URI=file:storages/whatsapp-keys.db?_foreign_keys=on | | CHAT_STORAGE_MAX_OPEN_CONNS | Maximum concurrent SQLite connections for chat storage | 5 | CHAT_STORAGE_MAX_OPEN_CONNS=10 | | WHATSAPP_AUTO_REPLY | Auto-reply message | - | WHATSAPP_AUTO_REPLY="Auto reply message" | | WHATSAPP_AUTO_MARK_READ | Auto-mark incoming messages as read | false | WHATSAPP_AUTO_MARK_READ=true | | WHATSAPP_AUTO_DOWNLOAD_MEDIA | Auto-download media from incoming messages | true | WHATSAPP_AUTO_DOWNLOAD_MEDIA=false | | WHATSAPP_IGNORE_STATUS_MEDIA | Ignore downloading status media (status@broadcast) | false | WHATSAPP_IGNORE_STATUS_MEDIA=true | | WHATSAPP_AUTO_REJECT_CALL | Auto-reject incoming WhatsApp calls | false | WHATSAPP_AUTO_REJECT_CALL=true | | WHATSAPP_WEBHOOK | Webhook URL(s) for events (comma-separated) | - | WHATSAPP_WEBHOOK=https://webhook.site/xxx | | WHATSAPP_WEBHOOK_SECRET | Webhook HMAC secret; generate one with openssl rand -hex 32. Empty or a published value (secret, super-secret-key, your-private-key) is replaced by a random per-process key | - | WHATSAPP_WEBHOOK_SECRET= | | WHATSAPP_WEBHOOK_INSECURE_SKIP_VERIFY | Skip TLS verification for webhooks (insecure) | false | WHATSAPP_WEBHOOK_INSECURE_SKIP_VERIFY=true | | WHATSAPP_WEBHOOK_EVENTS | Whitelist of events to forward (comma-separated, empty = all) | - | WHATSAPP_WEBHOOK_EVENTS=message,message.ack | | WHATSAPP_WEBHOOK_IGNORE_JIDS | JIDs/wildcards to skip when forwarding (comma-separated) | - | [email protected] | | WHATSAPP_WEBHOOK_ALL_DEVICE_RECEIPTS | Forward all linked-device receipts; consumers must handle duplicates and out-of-order updates | false | WHATSAPP_WEBHOOK_ALL_DEVICE_RECEIPTS=true | | WHATSAPP_WEBHOOK_DEVICE_MERGE_GLOBAL | Per-device webhook adds to the global URLs instead of replacing them | false | WHATSAPP_WEBHOOK_DEVICE_MERGE_GLOBAL=true | | WHATSAPP_ACCOUNT_VALIDATION | Enable account validation | true | WHATSAPP_ACCOUNT_VALIDATION=false | | WHATSAPP_PRESENCE_ON_CONNECT | Presence on connect: available, unavailable, or none | unavailable | WHATSAPP_PRESENCE_ON_CONNECT=unavailable | | WHATSAPP_PROXY | Outbound proxy for the WhatsApp WebSocket (SOCKS5/HTTP/HTTPS) | - | WHATSAPP_PROXY=socks5://user:pass@host:1080 | | WHATSAPP_PRESENCE_PULSE_ENABLED | Enable daily available/unavailable presence pulse | true | WHATSAPP_PRESENCE_PULSE_ENABLED=false | | WHATSAPP_PRESENCE_PULSE_INTERVAL | Interval between presence pulses | 24h | WHATSAPP_PRESENCE_PULSE_INTERVAL=24h | | WHATSAPP_PRESENCE_PULSE_DURATION | Duration to stay available during each pulse | 5m | WHATSAPP_PRESENCE_PULSE_DURATION=5m | | CHATWOOT_ENABLED | Enable Chatwoot integration | false | CHATWOOT_ENABLED=true | | CHATWOOT_URL | Chatwoot instance URL | - | CHATWOOT_URL=https://app.chatwoot.com | | CHATWOOT_API_TOKEN | Chatwoot API access token | - | CHATWOOT_API_TOKEN=your-api-token | | CHATWOOT_ACCOUNT_ID | Chatwoot account ID | - | CHATWOOT_ACCOUNT_ID=12345 | | CHATWOOT_INBOX_ID | Chatwoot inbox ID | - | CHATWOOT_INBOX_ID=67890 | | CHATWOOT_DEVICE_ID | WhatsApp device ID for Chatwoot (single-device/env fallback) | - | [email protected] | | CHATWOOT_ALLOWED_HOSTS | Allowlist of Chatwoot hosts for per-device configs (SSRF guard) | - | CHATWOOT_ALLOWED_HOSTS=app.chatwoot.com,chat.example.com | | CHATWOOT_IMPORT_MESSAGES | Enable message history sync to Chatwoot | false | CHATWOOT_IMPORT_MESSAGES=true | | CHATWOOT_DAYS_LIMIT_IMPORT_MESSAGES | Days of history to import | 3 | CHATWOOT_DAYS_LIMIT_IMPORT_MESSAGES=7 | | CHATWOOT_IMPORT_DB_URI | Direct Chatwoot PostgreSQL URI for history sync | - | CHATWOOT_IMPORT_DB_URI=postgresql://user:pass@host:5432/chatwoot_production?sslmode=disable | | CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE | Insert text placeholders for media rows during direct DB import | true | CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE=true | | CHATWOOT_IMPORT_MEDIA_WITH_REST | Upload direct-DB import media rows through Chatwoot REST | false | CHATWOOT_IMPORT_MEDIA_WITH_REST=true | | CHATWOOT_AUTO_CREATE | Auto-create or reuse the Chatwoot API inbox at startup | false | CHATWOOT_AUTO_CREATE=true | | CHATWOOT_INBOX_NAME | Inbox name used when auto-create is enabled | WhatsApp | CHATWOOT_INBOX_NAME=WhatsApp Support | | CHATWOOT_WEBHOOK_URL | Public GOWA Chatwoot reply webhook URL | - | CHATWOOT_WEBHOOK_URL=https://api.example.com/chatwoot/webhook | | CHATWOOT_WEBHOOK_SECRET | Secret for incoming Chatwoot webhooks (verifies Chatwoot's signature). Empty rejects calls | - | CHATWOOT_WEBHOOK_SECRET=channel-webhook-secret | | CHATWOOT_WEBHOOK_ALLOW_UNSIGNED | Accept Chatwoot webhooks when no secret is configured (insecure) | false | CHATWOOT_WEBHOOK_ALLOW_UNSIGNED=true | | CHATWOOT_WEBHOOK_SIGNATURE_TOLERANCE | Maximum age of a signed Chatwoot webhook's timestamp | 5m | CHATWOOT_WEBHOOK_SIGNATURE_TOLERANCE=2m | | CHATWOOT_REOPEN_CONVERSATION | Reopen resolved Chatwoot conversations for returning contacts | true | CHATWOOT_REOPEN_CONVERSATION=false | | CHATWOOT_CONVERSATION_PENDING | Create new Chatwoot conversations as pending | false | CHATWOOT_CONVERSATION_PENDING=true | | CHATWOOT_IGNORE_JIDS | JIDs or wildcards to exclude from Chatwoot forwarding | - | [email protected],[email protected] | | CHATWOOT_SIGN_MSG | Prefix Chatwoot agent replies with the agent name | false | CHATWOOT_SIGN_MSG=true | | CHATWOOT_SIGN_DELIMITER | Delimiter between Chatwoot agent signature and message body | \n\n | CHATWOOT_SIGN_DELIMITER=" - " | | CHATWOOT_FORWARD_EDITS | Mirror WhatsApp edits into Chatwoot threaded notes | true | CHATWOOT_FORWARD_EDITS=false | | CHATWOOT_FORWARD_DELETES | Mirror WhatsApp delete-for-everyone events into Chatwoot notes | true | CHATWOOT_FORWARD_DELETES=false | | CHATWOOT_MESSAGE_READ | Sync read state for linked WhatsApp/Chatwoot messages | false | CHATWOOT_MESSAGE_READ=true | | CHATWOOT_MESSAGE_DELETE | Delete linked opposite-side messages when deletion is reported | false | CHATWOOT_MESSAGE_DELETE=true |

Documentation:

  • For detailed webhook payload schemas, security implementation, and integration examples, see
Webhook Payload Documentation.
  • For the comprehensive Chatwoot integration guide, see
Chatwoot Integration Documentation.
  • For OAuth deployment and security details, see MCP OAuth.
Run ./whatsapp --help to see all command-line flags.

Requirements

System Requirements

  • Go 1.26.0 or later (when building from source)
  • FFmpeg (for media processing)

Supported Platforms

  • Linux (x86_64, ARM64)
  • macOS (Intel, Apple Silicon)
  • Windows (x86_64; WSL recommended)

Dependencies (without Docker)

  • macOS:
- brew install ffmpeg webp - export CGO_CFLAGS_ALLOW="-Xpreprocessor"
  • Linux:
- sudo apt update - sudo apt install ffmpeg webp - Install FFmpeg. - Install libwebp, then extract it and add its bin directory to PATH.
Note: The webp package provides cwebp (encoder), dwebp (decoder), and webpmux (frame extractor) tools.
FFmpeg is required for media processing. The libwebp tools (webpmux + dwebp) are used for animated WebP sticker support.

How to use

Basic

  1. Clone the repository: git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice.
  2. Open the cloned directory in a terminal.
  3. Run cd src.
  4. Run go run . rest.
  5. Open http://localhost:3000.

Docker

Docker avoids the need to install Go, FFmpeg, and libwebp directly on the host.

  1. Clone the repository: git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice.
  2. Open the cloned directory in a terminal.
  3. Copy the environment file: cp src/.env.example src/.env.
  4. Run docker compose up -d --build.
  5. Open http://localhost:3000.

Build your own binary

  1. Clone the repository: git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice.
  2. Open the cloned directory in a terminal.
  3. Run cd src.
  4. Build the binary:
- Linux and macOS: go build -o whatsapp - Windows (Command Prompt or PowerShell): go build -o whatsapp.exe
  1. Start the server:
- Linux and macOS: ./whatsapp rest - Windows: .\whatsapp.exe rest
  1. Open http://localhost:3000 in a browser.
Run ./whatsapp --help (or .\whatsapp.exe --help on Windows) to see all flags. Run ./whatsapp version to print only the version number (for example, 9.6.0).

Cross-compile for Raspberry Pi (ARM)

To build for a Raspberry Pi or another ARM device without a C toolchain (CGO), use the purego build tag. This selects a pure-Go SQLite implementation.

  1. Clone the repository: git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice.
  2. Open the cloned directory in a terminal.
  3. Run cd src.
  4. Build for Raspberry Pi Zero / 1 (ARMv6):
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -tags purego -o whatsapp-armv6
  1. Build for Raspberry Pi 2 / 3 / 4 (ARMv7 32-bit):
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -tags purego -o whatsapp-armv7
  1. Transfer the binary to your Pi, give it execution permission (chmod +x), and run it:
- If you built ARMv6: ./whatsapp-armv6 rest - If you built ARMv7: ./whatsapp-armv7 rest

MCP Server (Model Context Protocol)

MCP is not a separate mode or process — it's served by the REST server itself. Whenever ./whatsapp rest is running, the MCP endpoint is available at http://:/mcp (default http://localhost:3000/mcp) using the streamable HTTP transport. Disable it with MCP_ENABLED=false or --mcp-enabled=false (default: enabled).

Available MCP Tools

There are seven consolidated tools; agents choose behavior through a type/action argument instead of one tool per operation:

| Tool | type / action values | |--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| | whatsapp_send | text, image, video, audio, document, sticker, location, contact, poll, link, forward | | whatsapp_schedule | list, get, pause, resume, cancel | | whatsapp_message | react, edit, revoke, delete, mark_read, mark_played, star, unstar, download_media | | whatsapp_chat | list_chats, list_contacts, get_messages, archive | | whatsapp_group | create, join_with_link, leave, info, participants, add_participants, remove_participants, promote, demote, invite_link, set_name, set_topic, set_settings, join_requests, manage_join_requests | | whatsapp_app | status, login_qr, login_code, logout, reconnect | | whatsapp_newsletter | follow, unfollow, info, directory, messages, message_updates, live_updates_subscribe, live_updates_unsubscribe, live_updates_list |

whatsapp_send also accepts scheduled_at, timezone, recurrence, weekdays, day_of_month, end_at, and occurrence_limit to schedule the message instead of sending it now; manage the result with whatsapp_schedule.

Device selection

For multi-device deployments, the X-Device-Id header on the MCP client connection selects the device used by every tool call on that connection. If omitted, it falls back to the default device, just like REST. Any individual call can override it with an optional device_id argument.

MCP Configuration

Point your MCP client at the /mcp endpoint. It inherits the REST server's Basic Auth, so include the same Authorization header your REST calls use:

{
  "mcpServers": {
    "whatsapp": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Basic dXNlcjpzZWNyZXQ=",
        "X-Device-Id": "628123456789"
      }
    }
  }
}

headers is optional: include Authorization only when Basic Auth is configured, and X-Device-Id only for multi-device setups.

OAuth for remote MCP clients

OAuth 2.1 is available for remote clients that cannot attach a Basic Auth header. It is disabled by default. A minimal configuration is:

APP_BASIC_AUTH=admin:replace-with-a-strong-password
MCP_ENABLED=true
MCP_OAUTH_ENABLED=true
MCP_OAUTH_ISSUER_URL=https://gowa.example.com

When OAuth is enabled, /mcp accepts either a Bearer token or the configured Basic Auth credentials. OAuth does not authenticate REST or UI routes. See MCP OAuth for client setup, reverse-proxy requirements, subpath behavior, and the security model.

Migrating from the standalone MCP mode

  • ./whatsapp mcp → ./whatsapp rest (MCP is now included automatically).
  • http://localhost:8080/sse → http://localhost:3000/mcp.
  • 40 granular tools → 5 consolidated tools (agents choose actions through the type/action field).

Production REST Server (Docker)

Using Docker Hub:

docker volume create whatsapp-storages
docker volume create whatsapp-statics
docker run --detach \
  --publish 3000:3000 \
  --name whatsapp \
  --restart always \
  --volume whatsapp-storages:/app/storages \
  --volume whatsapp-statics:/app/statics \
  aldinokemal2104/go-whatsapp-web-multidevice \
  rest --autoreply="Don't reply to this message, please"

Using GitHub Container Registry:

docker volume create whatsapp-storages
docker volume create whatsapp-statics
docker run --detach \
  --publish 3000:3000 \
  --name whatsapp \
  --restart always \
  --volume whatsapp-storages:/app/storages \
  --volume whatsapp-statics:/app/statics \
  ghcr.io/aldinokemal/go-whatsapp-web-multidevice \
  rest --autoreply="Don't reply to this message, please"

Production REST Server (Docker Compose)

Create a docker-compose.yml file with one of the following configurations.

Using Docker Hub:

services:
  whatsapp:
    image: aldinokemal2104/go-whatsapp-web-multidevice
    container_name: whatsapp
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - whatsapp_storages:/app/storages
      - whatsapp_statics:/app/statics
    command:
      - rest
      - --basic-auth=admin:admin
      - --port=3000
      - --debug=true
      - --os=Chrome
      - --account-validation=false

volumes: whatsapp_storages: whatsapp_statics:

Using GitHub Container Registry:

services:
  whatsapp:
    image: ghcr.io/aldinokemal/go-whatsapp-web-multidevice
    container_name: whatsapp
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - whatsapp_storages:/app/storages
      - whatsapp_statics:/app/statics
    command:
      - rest
      - --basic-auth=admin:admin
      - --port=3000
      - --debug=true
      - --os=Chrome
      - --account-validation=false

volumes: whatsapp_storages: whatsapp_statics:

Using environment variables with Docker Hub:

services:
  whatsapp:
    image: aldinokemal2104/go-whatsapp-web-multidevice
    container_name: whatsapp
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - whatsapp_storages:/app/storages
      - whatsapp_statics:/app/statics
    environment:
      - APP_BASIC_AUTH=admin:admin
      - APP_PORT=3000
      - APP_DEBUG=true
      - APP_OS=Chrome
      - WHATSAPP_ACCOUNT_VALIDATION=false

volumes: whatsapp_storages: whatsapp_statics:

Using environment variables with GitHub Container Registry:

services:
  whatsapp:
    image: ghcr.io/aldinokemal/go-whatsapp-web-multidevice
    container_name: whatsapp
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - whatsapp_storages:/app/storages
      - whatsapp_statics:/app/statics
    environment:
      - APP_BASIC_AUTH=admin:admin
      - APP_PORT=3000
      - APP_DEBUG=true
      - APP_OS=Chrome
      - WHATSAPP_ACCOUNT_VALIDATION=false

volumes: whatsapp_storages: whatsapp_statics:

Start the selected stack with docker compose up -d.

Production Server (Binary)

Download a binary from the releases page, then run it with the rest subcommand.

You may also fork or modify the source code.

Current API

MCP (Model Context Protocol) API

  • Served at /mcp by the REST server using streamable HTTP whenever MCP_ENABLED is true. With APP_BASE_PATH
set, the route is /mcp.
  • Available tools are listed in the "Available MCP Tools" section above.
  • Compatible with MCP-enabled AI tools and agents.

HTTP REST API

| Status | Operation | Method | URL | |----------|----------------------------------------|--------|-------------------------------------| | ✅ | Server Metrics | GET | /app/metrics | | ✅ | Subscribe Contact Presence | POST | /user/presence/subscribe | | Experimental | Send Buttons, Location Request, Carousel | POST | /send/interactive | | ✅ | Health Check | GET | /health | | ✅ | List Devices | GET | /devices | | ✅ | Add Device | POST | /devices | | ✅ | Get Device Info | GET | /devices/:device_id | | ✅ | Remove Device | DELETE | /devices/:device_id | | ✅ | Login Device (QR) | GET | /devices/:device_id/login | | ✅ | Login Device (Code) | POST | /devices/:device_id/login/code | | ✅ | Logout Device | POST | /devices/:device_id/logout | | ✅ | Reconnect Device | POST | /devices/:device_id/reconnect | | ✅ | Get Device Status | GET | /devices/:device_id/status | | ✅ | Get Device Webhook | GET | /devices/:device_id/webhook | | ✅ | Set Device Webhook | PATCH | /devices/:device_id/webhook | | ✅ | Log In with QR Code | GET | /app/login | | ✅ | Log In with Pairing Code | GET | /app/login-with-code | | ✅ | Passkey Pairing Status | GET | /app/passkey | | ✅ | Passkey Pairing Response | POST | /app/passkey/response | | ✅ | Confirm Passkey Pairing | POST | /app/passkey/confirm | | ✅ | Logout | GET | /app/logout | | ✅ | Reconnect | GET | /app/reconnect | | ✅ | Devices | GET | /app/devices | | ✅ | Connection Status | GET | /app/status | | ✅ | App Info (version, limits) | GET | /app/info | | ✅ | User Info | GET | /user/info | | ✅ | User Avatar | GET | /user/avatar | | ✅ | Change User Avatar | POST | /user/avatar | | ✅ | Change User Push Name | POST | /user/pushname | | ✅ | List My Groups* | GET | /user/my/groups | | ✅ | List My Newsletters | GET | /user/my/newsletters | | ✅ | Get My Privacy Settings | GET | /user/my/privacy | | ✅ | List My Contacts | GET | /user/my/contacts | | ✅ | Check WhatsApp User | GET | /user/check | | ✅ | Get Business Profile | GET | /user/business-profile | | ✅ | Send Message | POST | /send/message | | ✅ | Send Image | POST | /send/image | | ✅ | Send Audio | POST | /send/audio | | ✅ | Send File | POST | /send/file | | ✅ | Send Video | POST | /send/video | | ✅ | Send Sticker | POST | /send/sticker | | ✅ | Send Contact | POST | /send/contact | | ✅ | Send Link | POST | /send/link | | ✅ | Send Location | POST | /send/location | | ✅ | Send Poll / Vote | POST | /send/poll | | ✅ | Send Presence | POST | /send/presence | | ✅ | Send Chat Presence (Typing Indicator) | POST | /send/chat-presence | | ✅ | List

... (README truncated for length)

Chat with me