---
title: "I run my server over WhatsApp — with Claude Code on the other side"
date: 2026-08-06
author: "Damjan Savić"
canonical: https://damjan-savic.com/en/knowledge/claude-on-whatsapp
language: en
keywords: "Claude Code, Automation, AI agents, Self-hosting, WhatsApp"
video: https://www.youtube.com/watch?v=sCUYUuk4WqE
---
# I run my server over WhatsApp — with Claude Code on the other side

**In short** — A voice message on WhatsApp, and the server answers: healthy, 5 of 5 containers running. A dashboard shows you numbers but cannot do anything. Here Claude Code already lives on the machine with real access, and the messaging app is only a door — Telegram and Slack are already in the same codebase.

---

A voice message on WhatsApp: "Hey Claude, how's my server doing?" The answer: server healthy, 5 of 5 containers running.

The problem behind it is unglamorous. Running [your own server](/en/knowledge/vercel-to-root-server) means SSH. SSH means a terminal, and a terminal means sitting at a desk. There are SSH clients for the phone; I have used them. Typing a `systemctl` command on a touchscreen on a train is not a workflow, it is a punishment.

The usual answer is a dashboard — Grafana, Uptime Kuma, whatever you like. Dashboards are fine. They show you numbers. But a dashboard cannot **do** anything. It tells you a container is down. It cannot restart it, it cannot read the log, and it cannot tell you why.

What I actually want is to ask a question in plain language and get an answer from something that can also act.

## What is running

Claude Code has been running on this server since the self-hosting video. Same setup, nothing new. So I use Claude Code to install the thing I then use to talk to Claude Code.

The bot itself is a project of mine and public on GitHub. It speaks Slack, Telegram and WhatsApp; only WhatsApp is in use here. Around it: PostgreSQL for session memory, so the bot remembers the conversation, and a router that sends simple questions to Haiku for a fast answer and hard ones to Opus.

Two points belong up front, because they mean money and risk respectively:

- **Voice messages go through OpenAI Whisper**, a separate paid API. Fractions of a cent per message, but not free.
- **The number I connect is a prepaid SIM**, bought specifically for this. Not my personal one. Why, below.

## Phase 1 · Install

```text
Clone this repository into my home directory and get it running on this
server.
```

First `npm install`. That pulls `whatsapp-web.js`, which pulls Puppeteer, which pulls an entire Chromium browser. On a fresh Ubuntu server the system libraries Chromium needs are not there.

One piece of advice that saves fifteen minutes: **do not guess the package list.** Start the service, read the log, install exactly what it names. That is two minutes instead of twenty.

### The 27 paths

Going through the repository, Claude Code found something I had not expected: **27 hardcoded paths.** Every one of them pointed at `/home/damjan`. My user on this server is called something else. The path to the Claude binary was hardcoded too, right in the runner module.

I wrote this project on a different machine and never noticed, because there the path was correct.

There were two ways out: create a user with the matching name and move on, or rewrite the paths properly. I had them rewritten.

That is the honest reason this phase is in the video. Not because `npm install` is interesting, but because this is exactly what happens when a project moves to a machine it was not written for. The lesson is banal and expensive: **write your paths as environment variables.** I did not, and it cost me a phase of this video.

### The split

Claude Code cannot run `sudo`. So it writes the configs and I run the privileged commands myself.

That is not a workaround, it is the correct way around. The agent writes, I execute.

## Phase 2 · Database

Install PostgreSQL and run the migrations. The first run returned an error; I handed the error to Claude, it prepared a follow-up migration and filled in the missing tables until all twelve matched the references in the code.

At the end it read `DB available: true` — no query had tripped the availability flag. Then the smoke-test rows came out again.

## Phase 3 · Pairing

The bot starts, PostgreSQL is connected, Slack cleanly disabled, WhatsApp initialising, the dashboard on port 3800.

Pairing runs over a QR code. It expires in seconds, so you generate a fresh one and scan immediately: on the phone, "link a device", scan, done. WhatsApp then shows a session from a Chrome browser — that is the session the bot uses.

The session is stored in a folder in the project, so a restart does not cost you the pairing. But: **the phone has to come online roughly every fourteen days**, or the link drops and you scan again.

### The honest part

This is **not** the official WhatsApp Business API. It is Puppeteer driving a headless browser and presenting itself as WhatsApp Web. Meta's terms forbid that, and accounts do get banned for it.

That is exactly why there is a prepaid SIM on this and not my own number. If it gets banned, I lose a SIM card, not my messages. Anyone rebuilding this gets the SIM **before** starting, not after. Ten euros, and it is the difference between a project and a problem.

## Phase 5 · Talking

A test message, and the answer comes back: containers up, load 0.26, disk at 4 per cent.

The order is what stands out. A short answer arrives almost immediately, a longer one follows. That is the router: Haiku classifies the message. If it is simple, Haiku answers directly and it is finished. If it is complex, Haiku sends a fast acknowledgement so you are not staring at nothing, and Opus takes over behind it.

That two-stage design is the difference between this feeling like a chat and feeling like a submitted form.

![Diagram · The router: Haiku classifies, Opus takes over behind it](/media/website/article/figures/claude-on-whatsapp-router.avif)

A voice message takes the same route: the audio goes to Whisper, comes back as text, and runs through exactly the same pipeline as if I had typed it. The limit is 25 megabytes, which is minutes of speech — you will not hit it.

And then the thing I actually built it for: the server report. Docker containers and their status, disk on root and home, memory, nginx, uptime. That is a snapshot function running six commands on the machine and writing the result into the state table. It keeps 24 hours of history.

## What did not work

1. **27 hardcoded paths in a project I wrote myself.** Environment variables. Always.
2. **Chromium system libraries on a fresh server.** Predictable, annoying, five minutes.
3. **Remembering to turn the prepaid phone on every fourteen days.**

## Three rules

1. **The whitelist is your security model**, not a convenience feature. Whoever can write can act.
2. **Give the agent a working directory, not the whole disk.** The config has a setting for it. Use it.
3. **Anything that needs root, you run yourself.** The agent writes the config, you execute it. That split is what keeps this safe.

## Takeaway

The bot is not the interesting part. You could build something comparable with Telegram in an afternoon.

What makes it work is that Claude Code already lives on that server, with real access. The messaging app is just a door — Telegram and Slack are already in the same codebase. Same engine, different door.

The interface to your server does not have to be a terminal. It has to be something you already have open.

## Chapters of the recording

- [0:46 — Why a dashboard is not enough](https://youtu.be/sCUYUuk4WqE?t=46)
- [1:35 — The five phases and the setup](https://youtu.be/sCUYUuk4WqE?t=95)
- [2:24 — Chromium, and the 27 paths](https://youtu.be/sCUYUuk4WqE?t=144)
- [4:00 — Phase 2: the database](https://youtu.be/sCUYUuk4WqE?t=240)
- [4:49 — Pairing over a QR code](https://youtu.be/sCUYUuk4WqE?t=289)
- [5:39 — Why a prepaid SIM](https://youtu.be/sCUYUuk4WqE?t=339)
- [6:27 — The router: Haiku in front, Opus behind](https://youtu.be/sCUYUuk4WqE?t=387)
- [8:05 — Three rules](https://youtu.be/sCUYUuk4WqE?t=485)

## Common questions

### Why a prepaid SIM rather than your own number?

Because this is not the official WhatsApp Business API. It is Puppeteer driving a headless browser that presents itself as WhatsApp Web. Meta's terms forbid that, and accounts get banned for it. If the number is banned I lose a SIM card, not my messages. Ten euros, and it is the difference between a project and a problem.

### What is the two-model router for?

Haiku classifies every message. If it is simple, Haiku answers directly. If it is complex, Haiku sends an immediate acknowledgement so you are not staring at nothing, and Opus takes over behind it. That is the difference between "this feels like a chat" and "this feels like a submitted form".

### What was the most expensive surprise during installation?

27 hardcoded paths in a project I wrote myself — every one of them pointing at /home/damjan, and my user on this server is called something else. The path to the Claude binary was wired in too. I had never noticed, because on the machine I wrote it on the path was correct. The lesson is banal and expensive: paths belong in environment variables.

---

I run my server over WhatsApp — with Claude Code on the other side — https://damjan-savic.com/en/knowledge/claude-on-whatsapp
AI-generated content: https://damjan-savic.com/en/ai-transparency
© 2026 Damjan Savić. https://damjan-savic.com
