wacli.me alpha

Self-hosting

The hosted service is a convenience, not a moat. Everything it does, you can run on a spare machine in about twenty minutes. This page is the honest version of that walkthrough, including the parts that are annoying.

What you need

RequirementWhy
A machine that stays awakeThe sync daemon must hold a connection; a laptop that sleeps loses history
Go 1.24+Builds the wacli binary
Node 20+Runs the gateway
~1 GB disk per busy accountSQLite store plus any media you download
A tunnel or reverse proxyOnly if you want to reach it from outside the machine

1 · Build the CLI

git clone https://github.com/openclaw/wacli
cd wacli
go build -tags sqlite_fts5 -ldflags="-s -w" -o ~/bin/wacli ./cmd/wacli
~/bin/wacli --version

The build tag matters. Without -tags sqlite_fts5 the store migration fails with no such module: fts5, and full-text search is gone. It is the single most common setup mistake.

2 · Link an account

wacli --store ~/wa-store auth

A QR code appears in the terminal; scan it in WhatsApp under Settings → Linked devices. The code expires after about a minute, so have the phone ready. Over SSH the block graphics are often unscannable — link from a real terminal, or render the code to an image first.

The store directory now holds device credentials. Anyone who copies it can read that account: chmod 700 it, and keep it off shared volumes and backups you do not control.

3 · Keep it synced

wacli --store ~/wa-store sync

This is a long-running process, not a cron job. It holds the store lock, streams new messages in, and backfills history. Without it running, your store freezes at whatever it last fetched — the second most common setup mistake.

Run it under whatever your system uses to keep services alive: a launchd agent on macOS, a systemd unit on Linux.

# /etc/systemd/system/wacli-sync.service
[Unit]
Description=wacli sync
After=network-online.target

[Service]
ExecStart=/home/you/bin/wacli --store /home/you/wa-store sync
Restart=always
RestartSec=10
User=you

[Install]
WantedBy=multi-user.target

4 · Run the gateway

npm install --prefix server
PORT=8787 HOST=127.0.0.1 node server/server.mjs

Tenants live in a JSON file that the process re-reads every few seconds, so adding an account needs no restart:

{
  "tenants": [
    { "id": "me",
      "token": "<openssl rand -hex 32>",
      "store": "/home/you/wa-store",
      "allowSend": false }
  ]
}
VariableDefaultMeaning
PORT8787Listen port
HOST127.0.0.1Bind address — keep it on loopback
WACLI_BIN./bin/wacliPath to the CLI
WACLI_ME_TENANTS./config/tenants.jsonTenant file

5 · Expose it — carefully

If the client runs on the same machine, stop here: http://127.0.0.1:8787/mcp is all you need, and nothing is reachable from outside.

To reach it from elsewhere, put a TLS terminator in front and leave the service on loopback. A Cloudflare tunnel does this without opening a port or exposing your home IP:

cloudflared tunnel --url http://127.0.0.1:8787

Never bind the service to 0.0.0.0 on a public network. The token is the only thing between the internet and your message history, and a plaintext HTTP hop hands it to anyone on the path.

Operational reality

Sessions drop. WhatsApp logs linked devices out now and then. Watch account_status; re-linking means another QR scan, in person.

Old CLI versions get refused. A client outdated error in the sync log means rebuilding from a current tag. Pin a version you tested.

One writer per store. The sync daemon holds the lock. Read commands are fine alongside it; a second writer is not.

Back up deliberately. The store is one directory. Copying it copies the credentials too — encrypt the backup or skip it.