<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ whatsapp - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ whatsapp - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 19 May 2026 04:42:49 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/whatsapp/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Self-Hosted WhatsApp Bot with n8n and WAHA ]]>
                </title>
                <description>
                    <![CDATA[ WhatsApp is where your many of your customers likely already are. For support tickets, order updates, booking reminders, and lead qualification, a WhatsApp channel often converts several times better  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-self-hosted-whatsapp-bot-with-n8n-and-waha/</link>
                <guid isPermaLink="false">6a01e032fca21b0d4b2bb4c1</guid>
                
                    <category>
                        <![CDATA[ whatsapp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ n8n ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ self-hosted ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ אחיה כהן ]]>
                </dc:creator>
                <pubDate>Mon, 11 May 2026 13:57:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/28affe4d-9359-4cbb-a311-a2ee9d0829c0.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>WhatsApp is where your many of your customers likely already are. For support tickets, order updates, booking reminders, and lead qualification, a WhatsApp channel often converts several times better than email.</p>
<p>But the official WhatsApp Business Cloud API can be slow to onboard, template-restricted for proactive messages, and priced per conversation — which adds up fast at scale.</p>
<p>There's another path: you can run your own WhatsApp HTTP gateway on a small server, connect it to a workflow engine, and keep every message — inbound and outbound — inside infrastructure you control. No monthly conversation fees, no template approvals for routine replies, no third-party middleman holding your customer data.</p>
<p>In this tutorial, you'll build exactly that. By the end, you'll have a WhatsApp bot that:</p>
<ul>
<li><p>Receives every incoming message through a webhook</p>
</li>
<li><p>Routes messages through an n8n workflow</p>
</li>
<li><p>Replies automatically based on keywords, AI, or any API call you want</p>
</li>
<li><p>Runs entirely on your own server, using two open-source tools</p>
</li>
</ul>
<p>You'll use <strong>WAHA</strong> (WhatsApp HTTP API) as the gateway, and <strong>n8n</strong> as the workflow engine. Both run in Docker, both are free for self-hosting, and together they cover everything from a simple auto-reply to a full CRM integration.</p>
<h2 id="heading-table-of-contents">Table of contents</h2>
<ul>
<li><p><a href="#heading-what-youll-learn">What You'll Learn</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-a-note-on-which-whatsapp-account-to-use">A Note on Which WhatsApp Account to Use</a></p>
</li>
<li><p><a href="#heading-waha-vs-the-official-whatsapp-business-cloud-api">WAHA vs the official WhatsApp Business Cloud API</a></p>
</li>
<li><p><a href="#heading-part-1-understanding-waha">Part 1: Understanding WAHA</a></p>
</li>
<li><p><a href="#heading-part-2-running-waha-with-docker">Part 2: Running WAHA with Docker</a></p>
</li>
<li><p><a href="#heading-part-3-starting-a-whatsapp-session">Part 3: Starting a WhatsApp session</a></p>
</li>
<li><p><a href="#heading-part-4-running-n8n">Part 4: Running n8n</a></p>
</li>
<li><p><a href="#heading-part-5-creating-the-webhook-trigger-in-n8n">Part 5: Creating the Webhook Trigger in n8n</a></p>
</li>
<li><p><a href="#heading-part-6-wiring-waha-to-n8n">Part 6: Wiring WAHA to n8n</a></p>
</li>
<li><p><a href="#heading-part-7-building-the-first-auto-reply">Part 7: Building the first auto-reply</a></p>
</li>
<li><p><a href="#heading-part-8-a-second-example-proactive-booking-confirmations">Part 8: A Second Example — Proactive Booking Confirmations</a></p>
</li>
<li><p><a href="#heading-part-9-going-to-production">Part 9: Going to Production</a></p>
</li>
<li><p><a href="#heading-common-pitfalls">Common Pitfalls</a></p>
</li>
<li><p><a href="#heading-where-to-go-next">Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-what-youll-learn">What You'll Learn</h2>
<ul>
<li><p>How WAHA works under the hood and when to use it instead of the official Cloud API</p>
</li>
<li><p>How to run WAHA and n8n side by side with Docker Compose</p>
</li>
<li><p>How to scan the QR code and bind a WhatsApp account to your gateway</p>
</li>
<li><p>How to connect WAHA's webhook to an n8n workflow</p>
</li>
<li><p>How to build a keyword-based auto-reply bot</p>
</li>
<li><p>How to send proactive confirmations from a separate workflow</p>
</li>
<li><p>How to harden the setup for production (HTTPS, API keys, rate limits, Queue Mode)</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>A Linux server (any VPS works — 2 GB of RAM is enough for a small bot)</p>
</li>
<li><p>Docker and Docker Compose installed</p>
</li>
<li><p>A public hostname with DNS pointing at the server, or an ngrok tunnel for local testing</p>
</li>
<li><p>A WhatsApp account you're willing to dedicate to the bot (more on that below)</p>
</li>
<li><p>Basic familiarity with JSON and HTTP requests</p>
</li>
</ul>
<p>You don't need prior n8n experience. If you can drag a box and wire it to another box, you can build the flow.</p>
<h2 id="heading-a-note-on-which-whatsapp-account-to-use">A Note on Which WhatsApp Account to Use</h2>
<p>WAHA works by running an actual WhatsApp Web session inside a headless Chromium process. It logs in as a real account — the same way you would open web.whatsapp.com in your browser. Meta doesn't officially endorse this approach for commercial use at scale, and heavy volume from a single number can lead to a ban.</p>
<p>For that reason, use a dedicated number for the bot. Don't use your personal WhatsApp. Get a second SIM, eSIM, or a VoIP number that supports WhatsApp activation. Keep outbound volume reasonable, and you'll be fine for most small-business use cases.</p>
<p>If you plan to send thousands of marketing messages per day, switch to the official WhatsApp Business Cloud API — that's what it exists for. This tutorial is aimed at the middle ground: support bots, order updates, booking confirmations, and similar conversational flows where you need real-time control without enterprise pricing.</p>
<h2 id="heading-waha-vs-the-official-whatsapp-business-cloud-api">WAHA vs the official WhatsApp Business Cloud API</h2>
<p>Before writing any code, it helps to understand when each option is the right fit.</p>
<table>
<thead>
<tr>
<th>Dimension</th>
<th>WAHA (self-hosted)</th>
<th>WhatsApp Cloud API (Meta)</th>
</tr>
</thead>
<tbody><tr>
<td>Onboarding</td>
<td>Scan a QR code — ready in minutes</td>
<td>Business verification, app review — days to weeks</td>
</tr>
<tr>
<td>Cost</td>
<td>Server cost only</td>
<td>Per-conversation pricing</td>
</tr>
<tr>
<td>Template approval</td>
<td>Not needed</td>
<td>Required for proactive messages outside the 24-hour window</td>
</tr>
<tr>
<td>Session model</td>
<td>One WhatsApp Web session per Core container</td>
<td>Native API, no web session</td>
</tr>
<tr>
<td>Risk</td>
<td>Account ban possible at high unsolicited volume</td>
<td>Rate limits but no ban for normal use</td>
</tr>
<tr>
<td>Vendor lock-in</td>
<td>None — pure open source</td>
<td>Tied to Meta's API and pricing</td>
</tr>
<tr>
<td>Best for</td>
<td>Support bots, small-team workflows, internal tools</td>
<td>High-volume marketing, regulated industries, &gt;100k monthly messages</td>
</tr>
</tbody></table>
<p>Neither is strictly better. If you run a support team for a small business, WAHA is often the pragmatic choice. If you're a bank sending millions of transactional messages, you want the Cloud API. Many teams run both — WAHA for conversational support, Cloud API for bulk transactional traffic.</p>
<h2 id="heading-part-1-understanding-waha">Part 1: Understanding WAHA</h2>
<p>WAHA is an open-source project that wraps WhatsApp Web behind a clean REST API. You <code>POST /api/sendText</code> with a chat ID and a message, and WAHA sends it. You configure a webhook URL, and WAHA <code>POST</code>s to that URL every time a message arrives.</p>
<p>Under the hood, WAHA spawns a Chromium instance, opens WhatsApp Web, and uses an engine (<code>whatsapp-web.js</code>, <code>NOWEB</code>, or <code>GOWS</code>) to automate the session. Your code doesn't see any of that complexity — you just see an HTTP API.</p>
<p>The project ships in two flavors:</p>
<ul>
<li><p><strong>WAHA Core</strong> — free, MIT licensed, one active session per container, community support.</p>
</li>
<li><p><strong>WAHA Plus</strong> — commercial license, multi-session support, priority support, and access to advanced endpoints.</p>
</li>
</ul>
<p>For most developers building a single bot, Core is enough. You can always upgrade later.</p>
<p>Official docs live at <a href="https://waha.devlike.pro/">waha.devlike.pro</a>. Keep that open in another tab — we'll reference specific endpoints as we go.</p>
<h2 id="heading-part-2-running-waha-with-docker">Part 2: Running WAHA with Docker</h2>
<p>Create a fresh directory for the project:</p>
<pre><code class="language-bash">mkdir whatsapp-bot &amp;&amp; cd whatsapp-bot
</code></pre>
<p>Create a <code>docker-compose.yml</code> file:</p>
<pre><code class="language-yaml">services:
  waha:
    image: devlikeapro/waha:latest
    container_name: waha
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - WAHA_DASHBOARD_ENABLED=true
      - WAHA_DASHBOARD_USERNAME=admin
      - WAHA_DASHBOARD_PASSWORD=change-me-now
      - WHATSAPP_API_KEY=super-secret-key-change-me
      - WHATSAPP_DEFAULT_ENGINE=WEBJS
    volumes:
      - ./waha-sessions:/app/.sessions
</code></pre>
<p>A few things to notice:</p>
<ul>
<li><p>The dashboard username and password protect the web UI at <code>http://your-server:3000</code>. Always change the defaults before you expose the port publicly.</p>
</li>
<li><p><code>WHATSAPP_API_KEY</code> is the key every HTTP request to WAHA must include in the <code>X-Api-Key</code> header. Treat it like a database password.</p>
</li>
<li><p><code>WHATSAPP_DEFAULT_ENGINE=WEBJS</code> uses the mature <code>whatsapp-web.js</code> engine. WAHA also supports <code>NOWEB</code> and <code>GOWS</code> engines with different trade-offs — WEBJS is the safest default for a first deployment.</p>
</li>
<li><p>The volume mount persists the session across restarts. Without it, every container rebuild forces you to scan the QR code again.</p>
</li>
</ul>
<p>Start the container:</p>
<pre><code class="language-bash">docker compose up -d
docker compose logs -f waha
</code></pre>
<p>Within about 20 seconds WAHA finishes booting. Visit <code>http://your-server:3000</code> and log in with the dashboard credentials.</p>
<h2 id="heading-part-3-starting-a-whatsapp-session">Part 3: Starting a WhatsApp session</h2>
<p>WAHA calls each WhatsApp account a "session." You can have one session at a time on WAHA Core.</p>
<p>From the dashboard, click <strong>Start New Session</strong> and name it <code>default</code>. WAHA displays a QR code.</p>
<p>On your phone:</p>
<ol>
<li><p>Open WhatsApp.</p>
</li>
<li><p>Tap the three-dot menu (Android) or Settings (iOS).</p>
</li>
<li><p>Tap Linked Devices → Link a Device.</p>
</li>
<li><p>Point the camera at the QR code on your screen.</p>
</li>
</ol>
<p>Within a few seconds the dashboard shows <code>WORKING</code> status. Your session is live.</p>
<p>You can also do this over the API. Start the session (<code>default</code> is the session name, encoded in the URL path):</p>
<pre><code class="language-bash">curl -X POST http://your-server:3000/api/sessions/default/start \
  -H "X-Api-Key: super-secret-key-change-me"
</code></pre>
<p>The call is idempotent — if the session is already running, nothing happens.</p>
<p>Fetch the QR as a PNG:</p>
<pre><code class="language-bash">curl http://your-server:3000/api/default/auth/qr \
  -H "X-Api-Key: super-secret-key-change-me" \
  -H "Accept: image/png" \
  --output qr.png
</code></pre>
<p>Scan and you're in.</p>
<p>Test that the session works by sending a message to yourself:</p>
<pre><code class="language-bash">curl -X POST http://your-server:3000/api/sendText \
  -H "X-Api-Key: super-secret-key-change-me" \
  -H "Content-Type: application/json" \
  -d '{
    "session": "default",
    "chatId": "15555550123@c.us",
    "text": "Hello from WAHA!"
  }'
</code></pre>
<p>Replace <code>15555550123</code> with your own number (country code plus number, no <code>+</code>, no spaces, no dashes). The <code>@c.us</code> suffix marks it as an individual chat. Groups use <code>@g.us</code>.</p>
<p>If the message lands on your phone — congratulations. The gateway works.</p>
<h2 id="heading-part-4-running-n8n">Part 4: Running n8n</h2>
<p>Add an <code>n8n</code> service to your <code>docker-compose.yml</code> alongside WAHA:</p>
<pre><code class="language-yaml">services:
  waha:
    # ... existing config

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
      - GENERIC_TIMEZONE=UTC
    volumes:
      - ./n8n-data:/home/node/.n8n
</code></pre>
<p>Replace <code>n8n.example.com</code> with your real domain. For purely local testing, set:</p>
<pre><code class="language-yaml">- N8N_HOST=localhost
- N8N_PROTOCOL=http
- WEBHOOK_URL=http://localhost:5678/
</code></pre>
<p>If you want to test webhooks from your laptop without a server, run <code>ngrok http 5678</code> in another terminal and use the ngrok HTTPS URL as <code>WEBHOOK_URL</code>. n8n uses <code>WEBHOOK_URL</code> to tell external services where to POST — get this wrong and your webhooks will 404.</p>
<p>Start the stack:</p>
<pre><code class="language-bash">docker compose up -d
</code></pre>
<p>Visit <code>http://your-server:5678</code>. On the first visit, n8n walks you through creating an owner account (email and password). Every subsequent visit requires that login. For extra safety in production, put n8n behind a reverse proxy with an allow-list or an additional auth layer — we'll set that up later.</p>
<h2 id="heading-part-5-creating-the-webhook-trigger-in-n8n">Part 5: Creating the Webhook Trigger in n8n</h2>
<p>Click Create Workflow. You'll see an empty canvas.</p>
<p>Add a Webhook node and configure it:</p>
<ul>
<li><p><strong>HTTP Method</strong>: POST</p>
</li>
<li><p><strong>Path</strong>: <code>whatsapp</code> (this becomes part of the URL)</p>
</li>
<li><p><strong>Response Mode</strong>: Respond Immediately</p>
</li>
<li><p><strong>Response Data</strong>: First Entry JSON</p>
</li>
</ul>
<p>Click Listen for Test Event. n8n shows you two URLs: a test URL and a production URL. Copy the production URL. It looks like this:</p>
<pre><code class="language-plaintext">https://n8n.example.com/webhook/whatsapp
</code></pre>
<p>Not <code>webhook-test</code> — that one only fires while the editor is open. You want <code>webhook</code>.</p>
<h2 id="heading-part-6-wiring-waha-to-n8n">Part 6: Wiring WAHA to n8n</h2>
<p>WAHA can POST to a webhook on every WhatsApp event. Tell it where to send those events.</p>
<p>In the WAHA dashboard, open your session and set the webhook URL. Or do it over the API:</p>
<pre><code class="language-bash">curl -X PUT http://your-server:3000/api/sessions/default \
  -H "X-Api-Key: super-secret-key-change-me" \
  -H "Content-Type: application/json" \
  -d '{
    "config": {
      "webhooks": [
        {
          "url": "https://n8n.example.com/webhook/whatsapp",
          "events": ["message", "session.status"]
        }
      ]
    }
  }'
</code></pre>
<p>The <code>message</code> event fires on every inbound message. <code>session.status</code> fires when the session connects, disconnects, or reconnects — which is useful for alerting when your bot goes down.</p>
<p>Test it. From another phone, send a WhatsApp message to your bot's number. Head back to the n8n editor. Within a second or two the webhook node lights up with the event data.</p>
<p>The payload looks roughly like this:</p>
<pre><code class="language-json">{
  "event": "message",
  "session": "default",
  "payload": {
    "id": "false_15555550123@c.us_3EB0...",
    "from": "15555550123@c.us",
    "body": "Hello",
    "timestamp": 1713801234,
    "fromMe": false
  }
}
</code></pre>
<p>Everything you need is in <code>payload</code>: who sent it (<code>from</code>), what they said (<code>body</code>), and when (<code>timestamp</code>).</p>
<h2 id="heading-part-7-building-the-first-auto-reply">Part 7: Building the first auto-reply</h2>
<p>A bot that only listens is boring. Let's make it answer.</p>
<p>You'll build a tiny keyword router: if the user sends <code>hi</code> or <code>hello</code>, the bot greets them. If they send <code>price</code>, it sends a pricing message. Anything else gets a fallback.</p>
<p>After the Webhook node, add a Switch node.</p>
<p>Configure the Switch node:</p>
<ul>
<li><p><strong>Mode</strong>: Expression</p>
</li>
<li><p><strong>Value</strong>: <code>{{ $json.payload.body.toLowerCase().trim() }}</code></p>
</li>
<li><p>Add routing rules:</p>
<ul>
<li><p>Rule 1: equals <code>hi</code> — output 0</p>
</li>
<li><p>Rule 2: equals <code>hello</code> — output 0</p>
</li>
<li><p>Rule 3: equals <code>price</code> — output 1</p>
</li>
<li><p>Fallback output: 2</p>
</li>
</ul>
</li>
</ul>
<p>After the Switch, add three HTTP Request nodes, one per output.</p>
<p>Configure each HTTP Request node identically, except for the body text:</p>
<ul>
<li><p><strong>Method</strong>: POST</p>
</li>
<li><p><strong>URL</strong>: <code>http://waha:3000/api/sendText</code> (inside the Docker network you can reach WAHA by its service name. From outside use the full public URL)</p>
</li>
<li><p><strong>Send Headers</strong>: on</p>
<ul>
<li><p><code>X-Api-Key</code>: <code>super-secret-key-change-me</code></p>
</li>
<li><p><code>Content-Type</code>: <code>application/json</code></p>
</li>
</ul>
</li>
<li><p><strong>Send Body</strong>: on</p>
<ul>
<li><p><strong>Body Content Type</strong>: JSON</p>
</li>
<li><p><strong>Specify Body</strong>: Using JSON</p>
</li>
</ul>
</li>
</ul>
<p>For the greeting node, the JSON body is:</p>
<pre><code class="language-json">{
  "session": "default",
  "chatId": "={{ $('Webhook').item.json.payload.from }}",
  "text": "Hi! I'm the bot. Send 'price' to see pricing, or anything else for help."
}
</code></pre>
<p>For the pricing node:</p>
<pre><code class="language-json">{
  "session": "default",
  "chatId": "={{ $('Webhook').item.json.payload.from }}",
  "text": "Our plans start at $49/month. Reply 'sales' to talk to a human."
}
</code></pre>
<p>For the fallback:</p>
<pre><code class="language-json">{
  "session": "default",
  "chatId": "={{ $('Webhook').item.json.payload.from }}",
  "text": "I didn't catch that. Try 'hi' or 'price'."
}
</code></pre>
<p>The <code>={{ ... }}</code> syntax is an n8n expression — at runtime it pulls values from earlier nodes.</p>
<p>Connect the Switch outputs to their matching HTTP Request nodes. Save the workflow. Click Activate in the top-right.</p>
<p>Send <code>hi</code> to your bot from any phone. It should reply within a second.</p>
<p>Congratulations — you have a WhatsApp bot running entirely on your own infrastructure.</p>
<h2 id="heading-part-8-a-second-example-proactive-booking-confirmations">Part 8: A Second Example — Proactive Booking Confirmations</h2>
<p>Auto-reply is useful. Proactive outbound is where the value really compounds. Here's a second workflow that sends a booking confirmation whenever a new row lands in a database.</p>
<p>Create a second workflow in n8n. Use one of these triggers:</p>
<ul>
<li><p><strong>Schedule Trigger</strong> — poll a database every minute for new rows</p>
</li>
<li><p><strong>Webhook Trigger</strong> — listen for a notification from your booking system</p>
</li>
<li><p><strong>Database Trigger</strong> (Postgres, MySQL, Supabase) — react to inserts in real time</p>
</li>
</ul>
<p>For this example, use a Schedule Trigger set to every minute, followed by a Postgres <strong>Execute Query</strong> node that reads pending confirmations:</p>
<pre><code class="language-sql">SELECT id, customer_phone, service_name, booking_time
FROM bookings
WHERE confirmation_sent = false
LIMIT 20;
</code></pre>
<p>After the Postgres node, add an HTTP Request node pointing to the same WAHA <code>sendText</code> endpoint you used earlier. The body:</p>
<pre><code class="language-json">{
  "session": "default",
  "chatId": "={{ $json.customer_phone }}@c.us",
  "text": "Hi! Your booking for {{ \(json.service_name }} on {{ \)json.booking_time }} is confirmed. Reply 'change' to reschedule."
}
</code></pre>
<p>Finally, add a second Postgres node that marks the booking as sent:</p>
<pre><code class="language-sql">UPDATE bookings
SET confirmation_sent = true, confirmation_sent_at = NOW()
WHERE id = {{ $json.id }};
</code></pre>
<p>Activate the workflow. Every minute, n8n pulls pending bookings, sends a WhatsApp confirmation, and marks them done.</p>
<p>This pattern generalizes. Replace the SQL with a call to Shopify for order confirmations, Stripe for receipt messages, or Calendly for appointment reminders. The WhatsApp layer stays the same — only the source of truth changes.</p>
<h2 id="heading-part-9-going-to-production">Part 9: Going to Production</h2>
<p>The setup above works, but it's not yet production-ready. Here's what to harden before you point real customers at it.</p>
<h3 id="heading-1-put-everything-behind-https">1. Put Everything Behind HTTPS</h3>
<p>Never expose n8n or WAHA directly on plain HTTP. Put a reverse proxy in front. Caddy is the easiest choice because it handles Let's Encrypt automatically.</p>
<p>A minimal <code>Caddyfile</code>:</p>
<pre><code class="language-plaintext">n8n.example.com {
    reverse_proxy n8n:5678
}

waha.example.com {
    reverse_proxy waha:3000
}
</code></pre>
<p>Run Caddy as another service in the same Docker Compose. TLS certificates are issued and renewed automatically.</p>
<h3 id="heading-2-rotate-the-api-keys">2. Rotate the API Keys</h3>
<p>Don't ship <code>super-secret-key-change-me</code> to production. Generate a real key:</p>
<pre><code class="language-bash">openssl rand -hex 32
</code></pre>
<p>Put it in a <code>.env</code> file, reference it as <code>${WHATSAPP_API_KEY}</code> in <code>docker-compose.yml</code>, and add <code>.env</code> to your <code>.gitignore</code>.</p>
<h3 id="heading-3-rate-limit-outbound-messages">3. Rate-limit Outbound Messages</h3>
<p>WhatsApp bans accounts that send too many messages too fast. A safe outbound rate for a fresh number is well under 20 messages per minute. For bursty replies, add an n8n Wait node between sends, or queue outgoing messages through a small custom function node that sleeps between requests.</p>
<h3 id="heading-4-scale-n8n-with-queue-mode">4. Scale n8n with Queue Mode</h3>
<p>By default, n8n runs everything in a single process. That's fine for low volume. For higher throughput, switch to Queue Mode:</p>
<ul>
<li><p>Add a Redis container.</p>
</li>
<li><p>Run one <code>n8n</code> main container (the web UI and webhook receiver).</p>
</li>
<li><p>Run one or more <code>n8n-worker</code> containers that pull jobs from the queue.</p>
</li>
</ul>
<p>Queue Mode is documented at <a href="https://docs.n8n.io/hosting/scaling/queue-mode/">docs.n8n.io/hosting/scaling/queue-mode/</a>. Setup adds two environment variables (<code>EXECUTIONS_MODE=queue</code>, <code>QUEUE_BULL_REDIS_HOST=redis</code>) and decouples incoming webhooks from workflow execution. The webhook responds in milliseconds while workers chew through the queue in the background.</p>
<h3 id="heading-5-monitor-the-session">5. Monitor the Session</h3>
<p>WhatsApp Web sessions drop. The phone loses connection, WhatsApp rotates security tokens, or your server reboots. Catch those drops early.</p>
<p>Subscribe to the <code>session.status</code> webhook event in WAHA. When status becomes <code>FAILED</code> or <code>STOPPED</code>, route it to an n8n workflow that posts to Slack, sends an email, or pages you. The faster you know, the faster you recover.</p>
<p>For overall uptime, point something like Uptime Kuma at <code>GET /api/sessions/default</code> on WAHA. If WAHA reports <code>WORKING</code>, you're fine. Anything else triggers an alert.</p>
<h3 id="heading-6-back-up-the-sessions-volume">6. Back Up the Sessions Volume</h3>
<p>The <code>waha-sessions</code> directory contains the logged-in state. If you lose it, you have to scan the QR code again — possibly from a phone that's no longer handy. Back it up nightly. A simple cron job with <code>tar</code> and <code>rclone</code> to S3-compatible storage is plenty.</p>
<h3 id="heading-7-add-a-live-agent-handoff">7. Add a Live-Agent Handoff</h3>
<p>Not every conversation should stay with the bot. When a user types <code>human</code> — or when your intent classifier can't answer confidently — hand off to a real agent.</p>
<p>Chatwoot is a solid open-source option: it has a dedicated WhatsApp channel, agent inbox, team assignment, and conversation history. The handoff is an n8n branch that stops processing bot replies and forwards the message stream to Chatwoot's API.</p>
<h2 id="heading-common-pitfalls">Common Pitfalls</h2>
<p>A few issues catch almost everyone on their first production deploy.</p>
<h3 id="heading-webhooks-timing-out">Webhooks Timing Out</h3>
<p>WAHA gives your webhook a few seconds to respond. If your n8n workflow is slow (calling an LLM, hitting a remote API), the webhook times out and WAHA retries, potentially causing duplicate replies.</p>
<p>Fix: make the webhook return <code>200</code> immediately and offload the slow work. In n8n, set the Webhook node's Response Mode to <em>Using Respond to Webhook Node</em>, add a Respond to Webhook node as the first step with a <code>200</code> and empty body, then do the heavy lifting after that.</p>
<h3 id="heading-duplicate-messages">Duplicate Messages</h3>
<p>WAHA delivers the same <code>message</code> event more than once in edge cases (phone comes back online, session reconnects). Store the <code>payload.id</code> somewhere — Redis, a database, or n8n's static data store — and drop any ID you've already processed.</p>
<h3 id="heading-messages-arriving-out-of-order">Messages Arriving Out of Order</h3>
<p>The webhook is async, and n8n may parallelize executions. If ordering matters — for example, in a multi-step conversation — key a queue by the sender's <code>chatId</code> and process each sender serially.</p>
<h3 id="heading-sessions-disconnecting-after-a-phone-restart">Sessions Disconnecting After a Phone Restart</h3>
<p>Normal WhatsApp Web behavior. WAHA auto-reconnects, but occasionally the linked-devices list needs a manual refresh. If a session refuses to come back, stop the WAHA container, delete that session's folder under <code>waha-sessions/</code>, start the container again, and rescan the QR.</p>
<h3 id="heading-your-number-gets-banned">Your Number Gets Banned</h3>
<p>The single biggest cause is rate: a new number blasting hundreds of messages an hour gets flagged fast. Warm up a number slowly — send a normal, human-like volume for the first week. Don't send to strangers unsolicited. Prefer inbound-driven replies over outbound pushes wherever you can.</p>
<h3 id="heading-the-wrong-chat-id-format">The Wrong Chat ID Format</h3>
<p>WhatsApp individual chats use <code>&lt;number&gt;@c.us</code> and groups use <code>&lt;groupId&gt;@g.us</code>. Don't include the <code>+</code> or spaces in the number. If WAHA returns a 404 when sending, the chat ID is almost always the problem.</p>
<h2 id="heading-where-to-go-next">Where to Go Next</h2>
<p>You now have the foundation. The same two-service stack supports almost any bot you can imagine — you're only limited by what you can build in an n8n workflow.</p>
<p>Some natural next steps:</p>
<ul>
<li><p><strong>Plug in AI replies:</strong> Add an OpenAI or Anthropic node after the Webhook, pass the user's message through it with a short system prompt, and send the response back through WAHA. Cap conversation length to prevent runaway token usage.</p>
</li>
<li><p><strong>Integrate a CRM:</strong> Look up the caller's <code>chatId</code> in HubSpot, Pipedrive, or your own database before deciding how to reply. Segment responses by customer tier.</p>
</li>
<li><p><strong>Send proactive notifications:</strong> Appointment reminders, shipping updates, payment receipts, abandoned-cart nudges. Keep the content transactional and expected — unsolicited marketing blasts are the fastest way to a ban.</p>
</li>
<li><p><strong>Log every conversation:</strong> Add a Postgres or Supabase node after the Webhook to persist messages for analytics and customer history. Your future self (and your support team) will thank you.</p>
</li>
<li><p><strong>Add media handling:</strong> WAHA exposes <code>sendImage</code>, <code>sendFile</code>, and <code>sendVoice</code> endpoints. Teach the bot to accept photos for support tickets, or send invoices as PDFs directly inside the chat.</p>
</li>
</ul>
<p>The WhatsApp layer stays the same. Everything interesting happens upstream in the workflow.</p>
<p><em>If you want to see production examples of n8n and WAHA running at scale — or you need a similar automation built for your business — I'm the founder of Achiya Automation, where we ship WhatsApp, n8n, and Chatwoot integrations. You can find more at</em> <a href="https://achiya-automation.com"><em>achiya-automation.com</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build and Deploy a Production-Ready WhatsApp Bot with FastAPI, Evolution API, Docker, EasyPanel, and GCP ]]>
                </title>
                <description>
                    <![CDATA[ WhatsApp bots are widely used for customer support, automated replies, notifications, and internal tools. Instead of relying on expensive third-party platforms, you can build and deploy your own self- ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-and-deploy-a-production-ready-whatsapp-bot/</link>
                <guid isPermaLink="false">699877ac3dc17c4862f466c7</guid>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ whatsapp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google cloud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Raju Manoj ]]>
                </dc:creator>
                <pubDate>Fri, 20 Feb 2026 15:03:08 +0000</pubDate>
                <media:content url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5e1e335a7a1d3fcc59028c64/de480f02-206a-4325-b4c2-788d33d746b1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>WhatsApp bots are widely used for customer support, automated replies, notifications, and internal tools. Instead of relying on expensive third-party platforms, you can build and deploy your own self-hosted WhatsApp bot using modern open-source tools.</p>
<p>In this tutorial, you’ll learn how to build and deploy a production-ready WhatsApp bot using:</p>
<ul>
<li><p>FastAPI</p>
</li>
<li><p>Evolution API</p>
</li>
<li><p>Docker</p>
</li>
<li><p>EasyPanel</p>
</li>
<li><p>Google Cloud Platform (GCP)</p>
</li>
</ul>
<p>By the end of this guide, you will have a fully working WhatsApp bot connected to your own WhatsApp account and deployed on a cloud virtual machine.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-how-the-architecture-works">How the Architecture Works</a></p>
</li>
<li><p><a href="#heading-how-your-whatsapp-bot-works">How Your WhatsApp Bot Works</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-step-1-create-firewall-rules-on-gcp">Step 1: Create Firewall Rules on GCP</a></p>
</li>
<li><p><a href="#heading-step-2-create-a-virtual-machine-ubuntu-2204">Step 2: Create a Virtual Machine (Ubuntu 22.04)</a></p>
</li>
<li><p><a href="#heading-step-3-ssh-into-the-vm">Step 3: SSH into the VM</a></p>
</li>
<li><p><a href="#heading-step-4-install-docker">Step 4: Install Docker</a></p>
</li>
<li><p><a href="#heading-step-5-install-easypanel">Step 5: Install EasyPanel</a></p>
</li>
<li><p><a href="#heading-step-6-open-the-easypanel-dashboard">Step 6: Open the EasyPanel Dashboard</a></p>
</li>
<li><p><a href="#heading-step-7-deploy-evolution-api">Step 7: Deploy Evolution API</a></p>
</li>
<li><p><a href="#heading-step-8-connect-whatsapp">Step 8: Connect WhatsApp</a></p>
</li>
<li><p><a href="#heading-step-9-deploy-the-fastapi-bot">Step 9: Deploy the FastAPI Bot</a></p>
</li>
<li><p><a href="#heading-step-10-connect-the-webhook-telling-evolution-api-where-to-send-messages">Step 10: Connect the Webhook - Telling Evolution API Where to Send Messages</a></p>
</li>
<li><p><a href="#heading-step-11-final-test">Step 11: Final Test</a></p>
</li>
<li><p><a href="#heading-production-considerations">Production Considerations</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-the-architecture-works">How the Architecture Works</h2>
<p>Before we start installing anything, let’s understand how the system works.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770444944919/cbcd4016-c98d-4379-9c7c-2d22ab9e309a.png" alt="This diagram shows how a WhatsApp message flows from the user’s WhatsApp app, through WhatsApp servers, into a GCP VM (via firewall, Docker, and EasyPanel) where Evolution API receives it, triggers a FastAPI bot via webhook, processes the logic, and sends the reply back to the user through WhatsApp." width="2619" height="4286" loading="lazy">

<h2 id="heading-how-your-whatsapp-bot-works">How Your WhatsApp Bot Works</h2>
<p>Before we continue setting things up, let's make sure you understand what's actually happening behind the scenes. Don't worry – no technical experience needed here.</p>
<h3 id="heading-imagine-a-postal-service">Imagine a postal service</h3>
<p>Think of your WhatsApp bot like a very fast, automated postal service:</p>
<ul>
<li><p>Someone sends you a letter (a WhatsApp message)</p>
</li>
<li><p>A postal worker (Evolution API) picks it up and brings it to your office</p>
</li>
<li><p>Your office manager (FastAPI bot) reads it and writes a reply</p>
</li>
<li><p>The postal worker takes the reply back and delivers it</p>
</li>
</ul>
<p>That's it. That's the whole system.</p>
<h3 id="heading-the-7-steps">The 7 steps</h3>
<ol>
<li><p>Someone sends a message to your WhatsApp number – just like texting a friend.</p>
</li>
<li><p>Evolution API notices the message – it's constantly watching your WhatsApp number for new messages, like a receptionist sitting by the phone.</p>
</li>
<li><p>Evolution API passes the message to your bot – it sends the message content to your app and says <em>"hey, you've got a new message!"</em></p>
</li>
<li><p>Your bot reads the message and decides what to say – this is where your code does its job.</p>
</li>
<li><p>Your bot sends the reply back to Evolution API – <em>"okay, send this response."</em></p>
</li>
<li><p>Evolution API delivers the reply through WhatsApp.</p>
</li>
<li><p>The user sees the reply on their phone – usually within seconds.</p>
</li>
</ol>
<h3 id="heading-one-line-summary">One line summary</h3>
<pre><code class="language-plaintext">User → WhatsApp → Evolution API → Your Bot → Evolution API → WhatsApp → User
</code></pre>
<p>Every step in this guide is just setting up one piece of that chain. Once they're all connected, the whole thing runs on its own automatically.</p>
<p>This architecture allows you to automate replies while keeping full control of your infrastructure.</p>
<h3 id="heading-why-these-tools">Why These Tools?</h3>
<p>Let’s briefly understand why we’re using each tool.</p>
<h4 id="heading-fastapi">FastAPI</h4>
<p>FastAPI is a modern Python framework for building APIs. It is fast, lightweight, and ideal for handling webhook requests from Evolution API.</p>
<h4 id="heading-evolution-api">Evolution API</h4>
<p>Evolution API is a self-hosted WhatsApp automation server built on top of Baileys. It connects your personal WhatsApp account without requiring official WhatsApp Business API approval.</p>
<h4 id="heading-docker">Docker</h4>
<p>Docker allows us to run applications in containers. This makes deployments consistent, portable, and production-ready.</p>
<h4 id="heading-easypanel">EasyPanel</h4>
<p>EasyPanel is a graphical platform for managing Docker services. Instead of writing Docker Compose files manually, we use EasyPanel’s UI to deploy and manage our services easily.</p>
<h4 id="heading-google-cloud-platform-gcp">Google Cloud Platform (GCP)</h4>
<p>GCP provides the virtual machine that hosts our infrastructure. We will use an Ubuntu 22.04 server to run Docker, EasyPanel, Evolution API, and our FastAPI bot.</p>
<p>I chose these tools because they are practical, lightweight, and suitable for real-world production deployments.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, make sure you have:</p>
<ul>
<li><p>A Google Cloud, AWS, or Azure account</p>
</li>
<li><p>Billing enabled</p>
</li>
<li><p>A project selected</p>
</li>
<li><p>Access to Cloud Shell</p>
</li>
<li><p>Basic Linux and Docker knowledge</p>
</li>
</ul>
<h2 id="heading-step-1-create-firewall-rules-on-gcp">Step 1: Create Firewall Rules on GCP</h2>
<p>We need to allow traffic to specific ports on our VM. So, we run this command in GCP Cloud Shell:</p>
<pre><code class="language-bash">gcloud compute firewall-rules create easypanel-whatsapp-fw \
 --network default \
 --direction INGRESS \
 --priority 1000 \
 --action ALLOW \
 --rules tcp:22,tcp:80,tcp:443,tcp:3000,tcp:8080,tcp:9000,tcp:5000-5999 \
 --source-ranges 0.0.0.0/0 \
 --description "SSH, EasyPanel, Evolution API, Bot"
</code></pre>
<p>This command:</p>
<ul>
<li><p>Creates a <strong>firewall rule</strong> named <code>easypanel-whatsapp-fw</code></p>
</li>
<li><p>On the <strong>default network</strong></p>
</li>
<li><p>Allows incoming internet traffic (<code>INGRESS</code>)</p>
</li>
<li><p>Opens these ports:</p>
<ul>
<li><p><code>22</code> → SSH (server access)</p>
</li>
<li><p><code>80</code> → HTTP</p>
</li>
<li><p><code>443</code> → HTTPS</p>
</li>
<li><p><code>3000, 8080, 9000</code> → App panels / APIs</p>
</li>
<li><p><code>5000–5999</code> → Custom app range</p>
</li>
</ul>
</li>
<li><p>Allows access from <strong>any IP address</strong> (<code>0.0.0.0/0</code>)</p>
</li>
</ul>
<p>Basically It opens your server so people (and you) can access your apps and services from the internet. This firewall rule allows external traffic to reach your VM.</p>
<h2 id="heading-step-2-create-a-virtual-machine-ubuntu-2204">Step 2: Create a Virtual Machine (Ubuntu 22.04)</h2>
<p>Now we'll create the server that hosts everything. Run the following command in the GCP Cloud Shell to set up a virtual machine with Ubuntu 22.04.</p>
<pre><code class="language-bash">gcloud compute instances create whatsapp-vm \
  --zone=asia-south1-a \
  --machine-type=e2-medium \
  --image-family=ubuntu-2204-lts \
  --image-project=ubuntu-os-cloud \
  --boot-disk-size=30GB \
  --tags=easypanel
</code></pre>
<p>This command creates a new virtual machine (VM) on Google Cloud:</p>
<ul>
<li><p><strong>Name:</strong> <code>whatsapp-vm</code></p>
</li>
<li><p><strong>Location (zone):</strong> <code>asia-south1-a</code> (India region)</p>
</li>
<li><p><strong>Machine size:</strong> <code>e2-medium</code> (2 vCPU, 4GB RAM)</p>
</li>
<li><p><strong>Operating System:</strong> Ubuntu 22.04 LTS</p>
</li>
<li><p><strong>Disk size:</strong> 30GB</p>
</li>
<li><p><strong>Tag:</strong> <code>easypanel</code> (used to apply firewall rules)</p>
</li>
</ul>
<p>This creates a Linux server in Google Cloud that you can use to host EasyPanel, WhatsApp bot, or your APIs.</p>
<p>Note: Wait about one minute for the instance to start.</p>
<h2 id="heading-step-3-ssh-into-the-vm">Step 3: SSH into the VM</h2>
<p>Connect to your server by using SSH to access the virtual machine you just created on Google Cloud.</p>
<pre><code class="language-bash">gcloud compute ssh whatsapp-vm --zone=asia-south1-a
</code></pre>
<p>This command connects to your virtual machine named <code>whatsapp-vm</code> in the zone <code>asia-south1-a</code> using SSH (secure remote login).</p>
<p>It logs you into your Google Cloud server so you can start installing software and running commands. After running this, you will see a terminal prompt – that means you are now inside your Ubuntu server and ready to go.</p>
<h2 id="heading-step-4-install-docker">Step 4: Install Docker</h2>
<p>Docker is needed to run EasyPanel and the Evolution API.</p>
<p><strong>First update the system:</strong></p>
<pre><code class="language-bash">sudo apt update -y
sudo apt install -y curl
</code></pre>
<p>This does two things:</p>
<ol>
<li><p><code>sudo apt update -y</code>→ Updates your server’s package list (refreshes available software info).</p>
</li>
<li><p><code>sudo apt install -y curl</code>→ Installs <strong>curl</strong>, a tool used to download things from the internet using the terminal.</p>
</li>
</ol>
<p>It prepares your server and installs a tool needed to download and install other software.</p>
<p><strong>Then install Docker:</strong></p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com | sudo sh
</code></pre>
<p>This command uses <code>curl</code> to download Docker’s official installation script. The <code>|</code> (pipe) sends it directly to <code>sudo sh</code>, which runs the script as administrator.</p>
<p>It automatically installs <strong>Docker</strong> on your server.</p>
<p>After this finishes, Docker should be installed.</p>
<p><strong>Enable Docker:</strong></p>
<pre><code class="language-bash">sudo systemctl enable docker
sudo systemctl start docker
</code></pre>
<p>This command does two things:</p>
<ol>
<li><p><code>enable docker</code>→ Makes Docker start automatically every time the server reboots.</p>
</li>
<li><p><code>start docker</code>→ Starts Docker right now.</p>
</li>
</ol>
<p>It turns Docker ON now and makes sure it stays ON after restart.</p>
<p><strong>Allow the Ubuntu user to run Docker:</strong></p>
<pre><code class="language-bash">sudo usermod -aG docker ubuntu
</code></pre>
<p>This command adds the user <code>ubuntu</code> to the Docker group.</p>
<p>This is important: By default, you must use <code>sudo</code> before every Docker command.After running this, the Ubuntu user can run Docker without needing sudo every time.</p>
<p>Note: This command assumes your username is <code>ubuntu</code>, which is the default on Google Cloud VMs. If your username is different, replace Ubuntu with your actual username.</p>
<p><strong>Exit the session and reconnect:</strong></p>
<pre><code class="language-bash">exit
gcloud compute ssh whatsapp-vm --zone=asia-south1-a
</code></pre>
<ol>
<li><p><code>exit</code>→ Logs you out of your current server session.</p>
</li>
<li><p><code>gcloud compute ssh whatsapp-vm --zone=asia-south1-a</code>→ Logs you back into your Google Cloud VM.</p>
</li>
</ol>
<p>Why we do this: After adding the <code>ubuntu</code> user to the Docker group, you must log out and log back in for the permission changes to work.</p>
<p><strong>Test Docker:</strong></p>
<pre><code class="language-bash">docker run hello-world
</code></pre>
<p>This command downloads a small test image called <strong>hello-world</strong>, runs it inside Docker, and prints a success message if Docker is working correctly.</p>
<p>It checks if Docker is installed and working properly. If you see “Hello from Docker!”, Docker is working correctly.</p>
<h2 id="heading-step-5-install-easypanel">Step 5: Install EasyPanel</h2>
<p>EasyPanel provides a user interface for deploying Docker services. Run this command in the VM:</p>
<pre><code class="language-bash">curl -sSL https://get.easypanel.io | sudo bash
</code></pre>
<p>This command:</p>
<ul>
<li><p>Downloads the official <strong>EasyPanel installation script</strong></p>
</li>
<li><p>Runs it with administrator (sudo) permission</p>
</li>
<li><p>Automatically installs and configures EasyPanel on your server</p>
</li>
</ul>
<p>It installs EasyPanel on your VM so you can manage apps using a web dashboard instead of commands. Installation takes about one minute.</p>
<h2 id="heading-step-6-open-the-easypanel-dashboard">Step 6: Open the EasyPanel Dashboard</h2>
<p>Once you have your IP address, open a new tab in your browser and type it in like this:</p>
<pre><code class="language-plaintext">http://&lt;YOUR_PUBLIC_IP&gt;:3000
</code></pre>
<p>For example, if your IP was 34.123.45.67, you would type:</p>
<pre><code class="language-plaintext">http://34.123.45.67:3000
</code></pre>
<p>EasyPanel runs on port 3000 by default – that's why we add :3000 at the end. Without it, your browser won't know which service to open on the server.</p>
<p>Create an admin account and log in; the EasyPanel login page will appear.</p>
<p>Click <strong>“Create Admin Account”</strong>.</p>
<p>Fill in:</p>
<ul>
<li><p><strong>Username</strong> (choose something you’ll remember)</p>
</li>
<li><p><strong>Email</strong></p>
</li>
<li><p><strong>Password</strong> (make it strong!)</p>
</li>
<li><p>Submit the form.</p>
</li>
</ul>
<p>You are now logged in as the admin and can start managing apps, APIs, and bots through the EasyPanel dashboard.</p>
<p>You will see a page like the one below:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771478879926/c222ae8c-8714-4af2-a631-c6fead8185e8.png" alt="easypanel page" style="display:block;margin:0 auto" width="1908" height="1060" loading="lazy">

<h2 id="heading-step-7-deploy-evolution-api">Step 7: Deploy Evolution API</h2>
<ol>
<li><p>Create a new project (for example: <code>whatsapp-1</code>)</p>
</li>
<li><p>Go to Services → Templates</p>
</li>
<li><p>Select Evolution API</p>
</li>
<li><p>Deploy the latest version</p>
</li>
</ol>
<p>Wait until all services turn green. You will see a page like the one below.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771479080612/ce8453b2-e20e-4c37-b150-de4e032ba794.png" alt="Deploying evolution-api" style="display:block;margin:0 auto" width="1919" height="995" loading="lazy">

<p>Next, open Environment Variables and locate:</p>
<pre><code class="language-plaintext">AUTHENTICATION_API_KEY
</code></pre>
<p>Copy the AUTHENTICATION_API_KEY.</p>
<p>Open the Evolution API dashboard</p>
<p>Inside EasyPanel, find your Evolution API service. You will see a clickable domain link – it usually looks something like:</p>
<pre><code class="language-plaintext">https://evolution-api.easypanel.host
</code></pre>
<p>Click that link to open it in your browser. You will see a JSON response confirming the service is running.</p>
<p>Once you open the link, you’ll see a JSON response confirming success. To proceed with login, copy the <strong>Manager link</strong> displayed in the response. This link opens the management dashboard where you can authenticate and begin using the Evolution API. The screenshot below highlights the manager URL along with version details for easy reference</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771480117778/017524df-f8be-41bd-9d6c-0ba04b499fe9.png" alt="manager URL" style="display:block;margin:0 auto" width="840" height="396" loading="lazy">

<p>Copy the manager link and open it in a new tab, then copy the AUTHENTICATION_API_KEY, which you did in the previous step. This is how it looks, as you can see below:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771480570638/9e18ca51-1637-4261-be22-7cd89ee0459f.png" alt="Evolution manager" style="display:block;margin:0 auto" width="1919" height="831" loading="lazy">

<p>Create a new instance:</p>
<ul>
<li><p>Choose channel: <strong>Baileys</strong></p>
</li>
<li><p>Leave phone number blank</p>
</li>
<li><p>Give your instance a name</p>
</li>
</ul>
<p>Save the instance.</p>
<h2 id="heading-step-8-connect-whatsapp">Step 8: Connect WhatsApp</h2>
<p>Inside your instance dashboard:</p>
<ol>
<li><p>Click <strong>Get QR</strong></p>
</li>
<li><p>Scan it using WhatsApp on your phone</p>
</li>
</ol>
<p>Once connected, your chats and contacts will sync automatically. If syncing fails, disconnect and reconnect the session.</p>
<h2 id="heading-step-9-deploy-the-fastapi-bot">Step 9: Deploy the FastAPI Bot</h2>
<p>Now we’ll deploy the bot service.</p>
<h3 id="heading-1-go-to-easypanel">1: Go to EasyPanel</h3>
<p>You’re opening the EasyPanel dashboard you just installed. This is where you can manage apps, servers, and services using a graphical interface instead of terminal commands.</p>
<h3 id="heading-2-create-a-new-project">2: Create a new project</h3>
<p>A “project” is like a container or folder for your bot service. It organizes all files, settings, and deployments for this app.</p>
<h3 id="heading-3-add-an-app-service">3: Add an App service</h3>
<p>“App service” means a running instance of your application. In this case, it will be the WhatsApp bot.</p>
<h3 id="heading-4-choose-git-deployment">4: Choose Git deployment</h3>
<p>Git deployment lets you connect a code repository to EasyPanel.This will automatically download your code from GitHub and run it inside Docker.</p>
<h3 id="heading-5-paste-your-repository-url">5: Paste your repository URL</h3>
<pre><code class="language-plaintext">https://github.com/rajumanoj333/wabot
</code></pre>
<p>This is the GitHub repository containing the WhatsApp bot code. EasyPanel will clone this repo and prepare the app automatically.</p>
<h3 id="heading-6-domains-in-easypanel">6: Domains in EasyPanel</h3>
<p>This section lets you assign a URL or domain name to your app service. Even if you don’t have a custom domain, you can use your server’s public IP. Your WhatsApp bot app runs on port 9000 inside the server.</p>
<h3 id="heading-7-set-the-port-to-9000">7: Set the port to <code>9000</code></h3>
<p>By setting the domain to use port 9000, EasyPanel knows where to send traffic.</p>
<p>Example URL after this step:</p>
<pre><code class="language-plaintext">https://your-project.easypanel.host
</code></pre>
<p>This is the public address people (and other services) will use to reach your bot.</p>
<p>You’re telling EasyPanel:</p>
<blockquote>
<p>“Whenever someone accesses this project, forward them to the bot service running on port 9000.”</p>
</blockquote>
<p>Without this step, the bot service would run but <strong>you wouldn’t be able to access it from your browser or other apps</strong>.</p>
<h3 id="heading-configure-environment-variables">Configure Environment Variables</h3>
<p>Set the following variables:</p>
<pre><code class="language-plaintext">EVOLUTION_API_URL=http://evolution-api:8080
EVOLUTION_API_KEY=YOUR_AUTHENTICATION_API_KEY
INSTANCE_NAME=your_instance_name
</code></pre>
<p>Note: You might notice two different names here – AUTHENTICATION_API_KEY (used in EasyPanel) and EVOLUTION_API_KEY (used in your bot code). They are the same key. Just copy the value from EasyPanel and paste it into both places.</p>
<h2 id="heading-step-10-connect-the-webhook-telling-evolution-api-where-to-send-messages">Step 10: Connect the Webhook – Telling Evolution API Where to Send Messages</h2>
<p>At this point, you have two separate things running:</p>
<ol>
<li><p><strong>Evolution API</strong>: the service that connects to WhatsApp and handles messages</p>
</li>
<li><p><strong>Your app (fastapi bot)</strong>: the chatbot brain you deployed in the previous steps</p>
</li>
</ol>
<p>Right now, these two don't know each other exists. They're like two people in different rooms with no way to pass notes between them. A <strong>webhook</strong> fixes that.</p>
<h3 id="heading-so-what-exactly-is-a-webhook">So what exactly is a webhook?</h3>
<p>A webhook is simply a URL (a web address) that you hand to one service so it can automatically notify another service when something happens.</p>
<p>You're going to tell Evolution API <em>"whenever a WhatsApp message arrives, forward it to this address."</em> Your app will be sitting at that address, waiting to receive it, read it, and send a reply.</p>
<p>Think of it like a forwarding address at the post office. When mail (a WhatsApp message) arrives, it gets automatically redirected to your app's door.</p>
<h3 id="heading-lets-set-it-up">Let's set it up</h3>
<h4 id="heading-1-open-your-evolution-api-dashboard">1. Open your Evolution API dashboard.</h4>
<p>You should already have this open from earlier steps. In the left sidebar, click on <strong>Events</strong>, then click on <strong>Webhook</strong>. This is where you control how Evolution API sends data to your app.</p>
<h4 id="heading-2-turn-the-webhook-on">2. Turn the webhook on.</h4>
<p>At the top of the page, you'll see a toggle next to the word <strong>"Enabled"</strong>. Click it so it turns green. This tells Evolution API that you want to start using a webhook.</p>
<h4 id="heading-3-enter-your-apps-webhook-url">3. Enter your app's webhook URL.</h4>
<p>In the <strong>URL</strong> field, type your app's address with <code>/webhook</code> added to the end, like this:</p>
<pre><code class="language-plaintext">https://your-domain.easypanel.host/webhook
</code></pre>
<p>Replace <code>your-domain</code> with the actual domain name you set up when you deployed your app. The <code>/webhook</code> part at the end is important: it's a specific page your app has set up just for receiving these messages. Without it, Evolution API would be knocking on the wrong door.</p>
<h4 id="heading-4-leave-webhook-by-events-and-webhook-base64-turned-off-for-now">4. Leave "Webhook by Events" and "Webhook Base64" turned off for now.</h4>
<p>These are advanced options you won't need for a basic chatbot.</p>
<h4 id="heading-5-scroll-down-to-the-events-section-and-enable-these-two-events">5. Scroll down to the Events section and enable these two events:</h4>
<ul>
<li><p><strong>MESSAGES_UPSERT</strong>: This triggers every time someone sends your WhatsApp number a message. Without this, your app would never know a message arrived.</p>
</li>
<li><p><strong>SEND_MESSAGE</strong>: This triggers when a message is sent <em>out</em>. It helps your app confirm that replies are going through correctly.</p>
</li>
</ul>
<p>You can leave all the other events (like <code>APPLICATION_STARTUP</code>) turned off. They handle things like group chats and contact updates, which aren't needed for what we're building.</p>
<p><strong>6. Click Save.</strong></p>
<h3 id="heading-quick-recap-of-what-you-just-did">Quick recap of what you just did</h3>
<p>You created a direct line between Evolution API and your app. Now, the moment someone messages your WhatsApp number, Evolution API will instantly pass that message along to your app. Your app reads it, figures out a response, and sends one back all automatically.</p>
<p>This is the step that brings your chatbot to life. Without it, nothing would happen when someone sent you a message. With it, the whole system clicks into place.</p>
<h2 id="heading-step-11-final-test">Step 11: Final Test</h2>
<p>Send a message from a different WhatsApp number (not the connected one).</p>
<p>Send:</p>
<pre><code class="language-plaintext">Hi
</code></pre>
<p>If everything is configured correctly, your bot should reply:</p>
<pre><code class="language-plaintext">👋 Hello! Bot is working.
</code></pre>
<p>Congratulations! Your WhatsApp bot is now live.</p>
<h2 id="heading-production-considerations">Production Considerations</h2>
<p>For real-world deployments, consider:</p>
<ul>
<li><p>Restricting firewall rules instead of allowing 0.0.0.0/0</p>
</li>
<li><p>Using HTTPS with a custom domain</p>
</li>
<li><p>Securing API keys with a secret manager</p>
</li>
<li><p>Monitoring logs and container health</p>
</li>
<li><p>Setting up automatic backups</p>
</li>
</ul>
<p>This tutorial demonstrates the core working system, but these improvements will make your deployment more secure and scalable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a fully self-hosted WhatsApp bot running on a cloud VM using FastAPI, Evolution API, Docker, EasyPanel, and GCP.</p>
<p>This setup gives you:</p>
<ul>
<li><p>Full control over infrastructure</p>
</li>
<li><p>No dependency on expensive SaaS platforms</p>
</li>
<li><p>Production-ready container deployment</p>
</li>
<li><p>Scalable architecture</p>
</li>
</ul>
<p>From here, you can extend your bot with:</p>
<p>AI integrations : connect your bot to ChatGPT or Gemini or Claude so it can answer questions intelligently instead of just sending fixed replies.</p>
<p>Database storage: save incoming messages, user details, or conversation history to a database like PostgreSQL or MongoDB.</p>
<p>Custom automation workflows trigger actions based on keywords, like sending a PDF when someone types "menu" or booking an appointment when they type "schedule".</p>
<p>CRM integrations :connect your bot to tools like HubSpot or Notion to automatically log leads and customer conversations.Building your own infrastructure is one of the best ways to deeply understand how modern backend systems work together.</p>
<p>Happy building!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
