WAHA — Operations Workbook

WAHA NOWEB WhatsApp gateway running on Hostinger VPS (vps-h1). Manages the DE WhatsApp number (+49 1578 5573196) — receives driver/fleet messages, forwards to n8n for routing and AI processing.


Architecture

Hostinger VPS (72.60.32.61)
└── Container: waha                   image: devlikeapro/waha-plus (NOWEB)
    ├── port 13000                   WAHA API (internal)
    ├── volume: waha_data             WhatsApp session state (auth keys, etc.)
    └── environment:
        ├── WHATSAPP_DEFAULT_ENGINE=NOWEB
        ├── WAHA_API_KEY             API authentication key
        └── WAHA_HOOK_EVENTS        Webhook event subscriptions
 
WhatsApp Cloud ──→ WAHA :13000 ──→ n8n webhook ──→ wa-router workflow
                                   (HTTP POST)
 
Public HTTPS: waha2.vps-h1.infra.zintegrowana.online

Public URL: https://waha2.vps-h1.infra.zintegrowana.online
Compose file on server: /root/docker-compose.yml
Compose file in repo: hostinger/docker-compose.yml


Config Management

FileLocationIn repo?Contains secrets?
docker-compose.yml/root/docker-compose.ymlhostinger/docker-compose.ymlNo
waha_data volumeDocker volume on vps-h1Yes (WhatsApp session keys)
.env/root/.env on vps-h1.env.example onlyYes

Key environment variables

VariablePurpose
WAHA_API_KEYAll API requests must include X-Api-Key: ${WAHA_API_KEY}
WAHA_HMAC_SECRETHMAC secret for webhook signature verification in n8n

Deployment

Fresh install

# On vps-h1
docker compose -f /root/docker-compose.yml up -d waha
# 1. Generate QR code
curl -X POST https://waha2.vps-h1.infra.zintegrowana.online/api/sessions/default/start \
  -H "X-Api-Key: ${WAHA_API_KEY}"
 
# 2. Get QR code (base64 image)
curl https://waha2.vps-h1.infra.zintegrowana.online/api/screenshot \
  -H "X-Api-Key: ${WAHA_API_KEY}" -o /tmp/qr.png
 
# 3. Scan QR with +49 1578 5573196 WhatsApp app
# 4. Verify session status
curl https://waha2.vps-h1.infra.zintegrowana.online/api/sessions/default \
  -H "X-Api-Key: ${WAHA_API_KEY}"
# Expected: {"name":"default","status":"WORKING"}

Backup

What needs backing up

DataMethodScheduleDestination
waha_data volume (session keys)backup-hstgr.sh → WasabiNightly 02:30 UTCs3://p24-infra/waha/waha-session-YYYY-MM-DD.tar.gz.age

Note: If session backup is lost, a new QR scan is required (re-linking WhatsApp). This is a 5-minute manual operation but causes a brief service interruption.

Manual backup

# On vps-h1
docker compose -f /root/docker-compose.yml stop waha
docker run --rm -v waha_data:/data -v /tmp:/backup alpine \
  tar czf /backup/waha-data-$(date +%F).tar.gz -C /data .
docker compose -f /root/docker-compose.yml start waha
# Upload /tmp/waha-data-*.tar.gz to Wasabi manually

Restore

Target RTO: 15 minutes (mostly QR scan if session is lost)

Scenario 1: Container crash (session intact)

docker compose -f /root/docker-compose.yml up -d waha
# waha_data preserved — session resumes automatically

Scenario 2: Restore session from backup

# 1. Download latest session backup
aws s3 cp s3://p24-infra/waha/waha-session-YYYY-MM-DD.tar.gz.age /tmp/ \
  --endpoint-url https://s3.eu-central-2.wasabisys.com
 
# 2. Decrypt
age --decrypt -i ~/.config/age/key.txt /tmp/waha-session-YYYY-MM-DD.tar.gz.age \
  | tar xz -C /tmp/waha-restore/
 
# 3. Stop WAHA
docker compose -f /root/docker-compose.yml stop waha
 
# 4. Restore volume
docker run --rm -v waha_data:/data alpine rm -rf /data/*
docker run --rm -v waha_data:/data -v /tmp/waha-restore:/backup alpine \
  tar xzf /backup/waha-session-YYYY-MM-DD.tar.gz -C /data
 
# 5. Start WAHA
docker compose -f /root/docker-compose.yml start waha
 
# 6. Verify session WORKING
curl https://waha2.vps-h1.infra.zintegrowana.online/api/sessions/default \
  -H "X-Api-Key: ${WAHA_API_KEY}"

Scenario 3: Session expired / QR needed

# Stop and wipe session data
docker compose -f /root/docker-compose.yml stop waha
docker run --rm -v waha_data:/data alpine rm -rf /data/*
docker compose -f /root/docker-compose.yml start waha
# Then follow fresh QR scan procedure above

Healthcheck

Docker healthcheck: GET http://localhost:13000/api/health — defined in hostinger/docker-compose.yml

External probe: https://waha2.vps-h1.infra.zintegrowana.online/api/health via health-check.yml GH Action.

Manual check:

curl -H "X-Api-Key: ${WAHA_API_KEY}" \
  https://waha2.vps-h1.infra.zintegrowana.online/api/sessions/default
# Session status should be "WORKING"

Session status alert: health-check.yml checks WAHA /api/health — auto-opens GitHub issue if down.


Password Rotation

WAHA_API_KEY

# 1. Generate new key
NEW_KEY=$(openssl rand -hex 32)
 
# 2. Update /root/.env on vps-h1
ssh root@72.60.32.61 "sed -i 's/WAHA_API_KEY=.*/WAHA_API_KEY=${NEW_KEY}/' /root/.env"
 
# 3. Restart WAHA
docker compose -f /root/docker-compose.yml restart waha
 
# 4. Update GH Secret
gh secret set WAHA_API_KEY -b "${NEW_KEY}" -R radieu/p24-infra
 
# 5. Update .env.local on local workstation
 
# 6. Verify health-check.yml still works (it uses GH Secret WAHA_API_KEY)
 
# 7. Log rotation in docs/secrets-rotation-log.md

Troubleshooting

SymptomCauseFix
Session status STOPPED or FAILEDWhatsApp session expiredRe-scan QR code
No messages arrivingWebhook URL wrong in WAHAVerify WAHA_HOOK_URL points to n8n
403 Forbidden on API callsWAHA_API_KEY mismatchVerify key in /root/.env and caller
Container OOMToo many concurrent sessionsIncrease container memory limit
/api/sessions shows no sessionsWAHA started but no session createdPOST /api/sessions/default/start