Servers & devices

WhatsApp server

Set up and run the WhatsApp server that your Zender installation talks to, link WhatsApp accounts to it, and keep it reachable even when it can't get a public IP address of its own.

Admin → WA Servers, where each server is registered
Admin → WA Servers, where each server is registered
Linked WhatsApp accounts
Linked WhatsApp accounts

WhatsApp is not sent by the PHP application itself. It's sent by a standalone binary that you run as a separate process next to Zender, shipped ready to run in your release's WhatsApp/prebuilt/ folder. The PHP app talks to it over plain HTTP, authenticated with a shared secret; the binary owns the actual WhatsApp connections. This is the single most important piece of infrastructure to get right before you tell customers WhatsApp works — if it's down or misconfigured, every WhatsApp feature in the dashboard silently stops working.

Each running server is registered in the dashboard under Admin → WA Servers. A Zender installation can have more than one WhatsApp server registered — useful for spreading accounts across machines or assigning specific servers to specific subscription packages.

Choosing a binary

Three platforms ship as tested, ready-to-run binaries in WhatsApp/prebuilt/:

FilePlatform
titansys-whatsapp-linuxLinux x86-64
titansys-whatsapp-arm64Linux ARM64
titansys-whatsapp-macosmacOS (Apple Silicon)

None of these binaries has any system library dependencies to install on the target machine — copy the file over and run it.

Running it

Copy the binary to your server, make it executable, and start it with at minimum a secret key:

chmod +x titansys-whatsapp-linux
./titansys-whatsapp-linux --key="YOUR_SECRET_KEY" --port=8899

--key is the only required flag — it's the shared secret that Zender and this server use to authenticate requests to each other, and it must match the secret you enter for this server in the dashboard. --port defaults to 8899 if omitted; --host defaults to the wildcard 0.0.0.0. Run ./titansys-whatsapp-linux --help on the binary for the full flag list, including optional outbound proxy support (--proxy-host, --proxy-port, --proxy-protocol, --proxy-username, --proxy-password) and the ngrok tunnelling flags covered below.

You can keep the settings in a file instead of passing flags. Copy config.example.json (in WhatsApp/src/) to config.json beside the binary and fill in server.secret. Flags still win over the file where both are set.

Choose the secret properly — it is the entire authentication between Zender and this server. Anyone who knows it can send messages from, and read messages arriving at, every WhatsApp account linked to this server. Use a long random string, not a word or a short number. The server refuses to start on an obvious placeholder such as 123456 or changeme, but it cannot tell a weak secret of your own invention from a strong one.
The binary does not restart itself if it crashes or the process is killed, and a bare terminal session ends when you log out. Keep it running with one of the two methods below. If the WhatsApp server goes down unsupervised, every linked account stops sending and receiving until someone notices and starts it back up by hand.

Keeping it running

Two supported ways on Linux and macOS, depending on what's available on your server. Both survive a crash and a reboot; pick whichever you can install. On Windows, use pm2 with a Windows startup helper — the built-in pm2 startup and cron are Linux and macOS only.

With pm2 (recommended)

pm2 is a process manager installed through npm, so it needs Node.js on the server:

npm install -g pm2

Start the WhatsApp server under it. Everything after the bare -- is passed straight to the binary:

pm2 start ./titansys-whatsapp-linux --name whatsapp -- \
  --key="YOUR_SECRET_KEY" --host="0.0.0.0" --port=8899

Then have pm2 remember it and start it again at boot:

pm2 save
pm2 startup

pm2 startup does not install anything by itself — it prints a sudo … command tailored to your system. Copy that command, run it, and only then is the boot hook actually in place. Skip it and you get crash recovery but not reboot recovery.

pm2 status shows whether it's up, pm2 logs whatsapp shows its output, and pm2 restart whatsapp restarts it after you replace the binary with a newer one.

Current pm2 runs an extensionless binary directly, so no --interpreter flag is needed even though this is not a Node application. If yours instead tries to run it as JavaScript — you'll see a SyntaxError in pm2 logs — add --interpreter none to the pm2 start line. It also records the directory you started it from as the process's working directory, and the linked WhatsApp sessions are stored there, so start it from the folder the binary lives in.

Without pm2, using a script and cron

If you can't install pm2, a small script on a cron schedule does the same job. The dashboard writes this script for you with your secret, port, folder path and binary name already filled in — in Admin → WA Servers, click the green terminal-icon button (Setup) on the server's row, pick the Linux tab, and read it off steps 5 and 6. It looks like this:

#!/bin/bash

if ! pgrep -f "titansys-whatsapp-linux" > /dev/null
then
  cd /your/whatsapp/server/folder
  ./titansys-whatsapp-linux --key="YOUR_SECRET_KEY" --host="0.0.0.0" --port="8899" &
fi

Save it as start-whatsapp.sh beside the binary, make it executable with chmod +x start-whatsapp.sh, then schedule it with crontab -e:

*/5 * * * * /your/whatsapp/server/folder/start-whatsapp.sh > /dev/null 2>&1
The pgrep guard means the script starts the server only when it isn't already running, so scheduling it every few minutes both restarts it after a crash and brings it back after a reboot.
The example above is written for titansys-whatsapp-linux. If you run a different binary, change the name in both places — the pgrep pattern and the command under it. On ARM64 that is titansys-whatsapp-arm64, on macOS titansys-whatsapp-macos. Changing only the command is worse than changing neither: the guard then never matches, so cron starts another copy every few minutes and they all fight over the same session files. The dashboard's generated copy already carries the right name for the architecture you picked, which is why copying it from there beats retyping this one.

Registering it in Zender

In Admin → WA Servers, click Add server and fill in the server's name, its URL and port (the same host and port the binary is listening on), and the secret — this must be exactly the value you passed to --key. You can also cap how many WhatsApp accounts this server accepts and restrict which subscription packages are allowed to use it.

Linking an account

Accounts are linked from Hosts → WhatsApp, not from the WA Servers admin screen — WA Servers only registers the server process itself. Click Add WhatsApp account, pick which registered server to use, and click Link. Zender asks the server to create a new session, the server generates a WhatsApp Web-style QR code, and the dashboard displays it for you to scan from your phone's WhatsApp app (Linked devices → Link a device). Once scanned, the session is established and persists in that server's own local session store — it is not stored by the PHP application or in Zender's database, so if you move an account to a different WhatsApp server it has to be linked again from scratch.

Running it without a public IP address, using ngrok

If you're hosting the WhatsApp server on a machine that isn't reachable from the internet — a home connection, a machine behind a router with no port forwarding, an Android phone (see below) — Zender still needs some public address to reach it on. The binary has built-in support for ngrok, a tunnelling service, so you don't have to buy or configure a public IP yourself.

Sign up for a free ngrok account and copy your authtoken from your ngrok dashboard. Then start the binary with the token and a comma-separated list of every Zender site that should be updated automatically (each one prefixed with whichever scheme, http or https, that site actually runs under):

./titansys-whatsapp-linux --key="YOUR_SECRET_KEY" \
  --ngroktoken="NGROK_AUTHTOKEN" \
  --ngrokserver="site-one.example.com,site-two.example.com"
With ngrok enabled, the binary registers its public tunnel URL with every listed Zender site automatically, over the same shared secret — you never have to look up or type in the server's URL and port by hand, and this works across multiple Zender sites at once as long as they all use the same secret key for this server.

Running it on an Android phone

Because the binary has no system library dependencies, you're not limited to renting a VPS to run it — an idle ARM64 Android phone works too, which is a cheap way to add WhatsApp capacity if you already have spare devices lying around. Install Termux (the F-Droid build, not the Play Store one) and use it to install a Ubuntu userland — a one-time setup that takes a single long command inside Termux, and gives you a normal Linux shell to copy the titansys-whatsapp-arm64 binary into and run exactly as you would on a server. Since a phone virtually never has a public IP, you'll also need ngrok, above, to make it reachable.

Android will kill background apps to save battery, and Termux is no exception. Keep Termux alive with its own wake-lock feature (or leave the screen on) for as long as you need the WhatsApp server running on that device — if Termux gets suspended, the binary stops with it and the phone's WhatsApp accounts go offline the same as any other unsupervised crash.

Building for Windows

Windows is the one platform without a ready-to-run binary in the release. The full source ships at WhatsApp/src/, so you can build it yourself. You'll need Go 1.25 or newer:

cd WhatsApp/src
make build-windows   # Windows x86-64  -> dist/titansys-whatsapp-win.exe
This target cross-compiles fine from a Linux or macOS host — you don't need a Windows machine to produce the binary, only to verify it runs. Unlike the three binaries in WhatsApp/prebuilt/, the Windows build is not pre-built or tested by Titan Systems as part of the release, so verify it on your own Windows hardware before relying on it.

Backing up sessions

Each linked account's WhatsApp session lives entirely on the server's own disk, independent of the PHP application. Before replacing or upgrading the binary, back up its session and storage directories rather than assuming an in-place upgrade is safe — there's no migration or rollback tooling built into the binary for its local session data, so a bad upgrade can mean re-linking every account on that server from scratch. Stop the process before copying its data directory to avoid backing up a database file mid-write.