Softechinfra
Technology

n8n + WhatsApp Cloud API: Auto-Reply to 80% of Queries Without a Human

A real WhatsApp Cloud API + Claude + KB lookup workflow on n8n v1.121. Meta setup steps, INR per-message math, and the escalation logic Indian SMBs need.

Hrishikesh BaidyaHrishikesh Baidya
April 4, 202614 min read
n8n + WhatsApp Cloud API: Auto-Reply to 80% of Queries Without a Human

WhatsApp marketing messages are charged per message in India as of January 2026 (Meta raised the rate ~10% on Jan 1). Service-window replies — anything within 24 hours of a user-initiated message — are still free. That free service window is the entire reason this workflow makes financial sense: a customer asks a question, your n8n + Claude + KB pipeline answers in 3 seconds, and Meta charges you nothing. We run this for a Pune-based D2C skincare brand handling 4,200 conversations/month. 84% of queries resolved without a human.

84%
Queries auto-resolved (last 30 days)
Free
Cost per service-window reply
3.1s
Median reply latency
22
n8n nodes (incl. handoff)

TL;DR

Customer sends a WhatsApp message to your business number. Meta posts the message to your n8n webhook. The workflow classifies intent (order status / FAQ / human request / spam) with Claude Haiku, looks up the answer in your knowledge base (we use a Notion database or a Google Sheet), and posts the reply back via the WhatsApp Cloud API. Confidence below 0.7 → handoff to a human via Slack with the full thread. Cost: nothing for service-window replies, a small per-template fee if you need to push a utility notification later.

Why this matters now — April 2026

Three things made this viable in 2026 that were not 18 months ago. First, WhatsApp Cloud API matured (v20.0+, current as of April 2026) — Meta hosts the infra so you skip the on-premises BSP setup. Second, Indian per-message pricing dropped to a per-message model in July 2025, killing the "conversation fee" math. Third, Claude Haiku 4.5 got cheap enough per intent classification that the AI side is essentially free at SMB scale.

A Meta for Developers post from March 2026 confirms the v20.0 webhook schema we use below. The r/n8n community thread from April 2026 — "WhatsApp Cloud API webhook in n8n, finally clean" — covers the same setup with a few alternative routing patterns.

The 5-block workflow

📲
Meta webhook ingress
Webhook node verifies Meta's GET challenge, then receives every inbound message as a POST.
🧠
Intent + entity extraction
Claude Haiku classifies into 6 buckets and pulls order_id / product_name when present.
📚
KB / order lookup
Notion DB query for FAQs, Shopify Order API for order status, Sheets for return policy.
↩️
Reply + escalate
High-confidence answer goes back via Cloud API. Low confidence pings #wa-support with thread.

Meta Business Suite setup — 7 real steps

This is where most teams give up. Plan 90 minutes the first time; once you have done it once, the next number takes 20.

  1. Create a Meta Business Account at business.facebook.com → Settings → Business Info. You need a verified business — usually GST cert + a utility bill for the registered address.
  2. Inside Meta Business Suite, go to WhatsApp Accounts → Add WhatsApp Business Account. Tie it to a phone number you control (not a personal one — once enrolled, the number cannot be used in the regular WhatsApp app).
  3. In Meta for Developers (developers.facebook.com), create an App → Business type → add the WhatsApp product. Note the App ID and App Secret.
  4. In your App → WhatsApp → API Setup, generate a permanent System User Access Token. Set the token's expiry to "Never." This is the token n8n will use for outbound messages.
  5. Add your test number first, then your production number. Meta charges nothing to verify; SMS OTP confirms ownership.
  6. Submit your business for verification (required to lift the 250 unique users/24h sandbox cap). Verification takes 1–5 business days and asks for GST cert, business website, and authorised signatory.
  7. Configure the webhook: in App → WhatsApp → Configuration, set the Callback URL to your n8n webhook (must be HTTPS), set the Verify Token (any string — n8n must return it on the GET handshake), and subscribe to the `messages` field.

That last step is where n8n earns its keep. Meta's webhook handshake is a GET request with hub.mode=subscribe, hub.verify_token, and hub.challenge query params. You must return the challenge as plain text.

Block 1 — The Webhook node with verify-token handshake

Two webhook paths: one for the verification GET, one for the POST messages. n8n's Webhook node v2 supports multiple HTTP methods on the same path if you use a custom routing pattern. Simpler: two separate webhook nodes.

The verification webhook:

json
{
    "parameters": {
      "httpMethod": "GET",
      "path": "wa/incoming",
      "responseMode": "responseNode",
      "options": {}
    },
    "id": "wa-verify-001",
    "name": "Meta Verify GET",
    "type": "n8n-nodes-base.webhook",
    "typeVersion": 2,
    "position": [240, 200]
  }

Followed by a Code + Respond to Webhook pair:

javascript
// Verify Meta's challenge
  const VERIFY_TOKEN = $env.META_VERIFY_TOKEN;
  const mode = $json.query['hub.mode'];
  const token = $json.query['hub.verify_token'];
  const challenge = $json.query['hub.challenge'];
  
  if (mode === 'subscribe' && token === VERIFY_TOKEN) {
    return { json: { ok: true, body: challenge } };
  }
  return { json: { ok: false, body: 'unauthorized' } };

The Respond to Webhook node returns {{ $json.body }} as plain text. Once this passes, Meta marks the webhook "verified" and starts sending messages.

The message webhook is a separate POST endpoint at the same path or a different one — we use /wa/incoming for POST and /wa/verify for GET to keep them isolated.

Block 2 — Parse the inbound payload + intent extraction

Meta's webhook POST shape is nested. The actual message text lives at body.entry[0].changes[0].value.messages[0].text.body. Extract once into a clean object:

javascript
const entry = $json.body.entry?.[0];
  const change = entry?.changes?.[0]?.value;
  const msg = change?.messages?.[0];
  if (!msg) return []; // status updates, deliveries — ignore
  
  return {
    json: {
      from: msg.from,                     // E.164 phone, e.g. 919876543210
      msg_id: msg.id,                     // wamid for read-receipt later
      timestamp: msg.timestamp,
      type: msg.type,                     // text | image | audio | interactive
      text: msg.text?.body || '',
      profile_name: change.contacts?.[0]?.profile?.name || 'Unknown',
      business_phone: change.metadata?.display_phone_number
    }
  };

If type is not text, we route to a branch that asks the user to send a text (for images/audio we have a separate Whisper pipeline; that is Post 1 territory).

Claude Haiku call, identical structure to Post 1, but a different system prompt:

code
You are a WhatsApp customer support classifier for Tvashtri Skincare, a Pune-based D2C brand.
  Read the customer message and return strict JSON only:
  {
    intent: 'order_status' | 'product_question' | 'return_refund' | 'discount_query' | 'human_handoff' | 'spam',
    order_id: string | null,
    product_name: string | null,
    language: 'en' | 'hi' | 'mr' | 'hinglish',
    sentiment: 'positive' | 'neutral' | 'negative',
    confidence: number 0-1
  }
  If the user explicitly asks for a human, agent, or executive, intent='human_handoff' with confidence 0.99.

Block 3 — KB lookup, Shopify order lookup, or escalate

A Switch node on intent fans out. Three of the most common branches:

order_status: HTTP Request to Shopify Admin GraphQL (https://{store}.myshopify.com/admin/api/2026-04/graphql.json) with a query that pulls the order by name (#{{ order_id }}):

graphql
query getOrder($name: String!) {
    orders(first: 1, query: $name) {
      edges { node {
        id name displayFulfillmentStatus displayFinancialStatus
        shippingLine { title }
        fulfillments {
          trackingInfo { number url }
          estimatedDeliveryAt
        }
      } }
    }
  }

The brand uses Shiprocket for fulfilment, so the tracking URL is a Shiprocket short-link. We compose a one-line answer: "Hi {{ profile_name }}, your order #1234 is out for delivery, estimated by tomorrow. Track: {{ tracking_url }}".

product_question: Notion database query via the Notion API node. Each FAQ row in Notion has question, answer, and embedding (we keep a cached OpenAI text-embedding-3-small vector for each FAQ in a separate column). The matching is a cosine-similarity in a Code node — for ~120 FAQs this is fast enough to skip a vector DB.

human_handoff or low-confidence anything: the workflow skips the reply and posts a structured Slack message to #wa-support with the customer's profile name, phone, full message, and a "Reply as you" link that opens WhatsApp Web with the right contact preselected.

Block 4 — Send the reply via Cloud API

The Cloud API endpoint for outbound is POST https://graph.facebook.com/v20.0/{phone_number_id}/messages. The phone_number_id is from Meta App → WhatsApp → API Setup, not the customer-facing number.

Node JSON:

json
{
    "parameters": {
      "method": "POST",
      "url": "=https://graph.facebook.com/v20.0/{{ $env.WA_PHONE_NUMBER_ID }}/messages",
      "sendHeaders": true,
      "headerParameters": {
        "parameters": [
          { "name": "Authorization", "value": "=Bearer {{ $env.WA_SYSTEM_USER_TOKEN }}" },
          { "name": "Content-Type", "value": "application/json" }
        ]
      },
      "sendBody": true,
      "specifyBody": "json",
      "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"recipient_type\": \"individual\",\n  \"to\": \"{{ $json.from }}\",\n  \"type\": \"text\",\n  \"text\": {\n    \"preview_url\": true,\n    \"body\": \"{{ $json.reply_text }}\"\n  }\n}",
      "options": { "timeout": 15000, "retry": { "enabled": true, "maxRetries": 3 } }
    },
    "id": "wa-send-001",
    "name": "Send WhatsApp Reply",
    "type": "n8n-nodes-base.httpRequest",
    "typeVersion": 4.2,
    "position": [1640, 320]
  }

We then mark the original message as read with a second small request — Meta gives the user a "read" tick instead of just "delivered," which builds trust visibly:

json
{
    "messaging_product": "whatsapp",
    "status": "read",
    "message_id": "{{ $json.msg_id }}"
  }

Cost math

Per the Pune skincare brand's March 2026 bill:

  • 4,200 inbound conversations × 84% auto-resolved = 3,528 service-window auto-replies, free
  • 672 escalated to human → still service-window, free to Meta
  • 38 outbound "your order shipped" utility notifications, billed at the utility rate
  • 720 marketing campaign blasts, billed at the marketing rate
    • Add the AI + ops layer:
    • Anthropic Haiku 4.5: ~10,000 classifications
    • Hetzner CX22 (shared with 2 other workflows)
    • Notion (already paid): no marginal cost
    • n8n self-hosted: no marginal cost

    Compare to AiSensy or Interakt-style BSPs' monthly fees + the per-message markup on top.

    n8n + Cloud API direct AiSensy Pro + AI add-on Interakt + bot Custom BSP build

    When NOT to do this

    You send more than 30,000 marketing messages a month. At that scale, Meta's volume pricing tiers and BSP rebates (AiSensy, Wati) can push your per-message cost below what you would pay direct. Run the math at your volume.

    Your queries are deeply contextual. A legal-services firm where each "question" requires reading a contract — Claude Haiku will fail and the workflow becomes a frustration loop. Use Sonnet 4.5 with longer context (different cost class) or route 100% to humans.

    You do not have a knowledge base. Without a Notion / Sheets / Airtable KB to feed answers from, the bot hallucinates. Build the KB first; it is 80% of the project. We typically spend 4 of the 7 build days on KB curation, not n8n nodes.

    Template approval gotcha: Any outbound message sent outside the 24h service window must use a pre-approved template. Submission to Meta takes 1–24 hours and ~30% of marketing templates get rejected on first try. Keep templates under 1,024 characters, no all-caps, no shortened URLs, and include a clear opt-out instruction.

    Mini case — Pune D2C skincare brand, ₹8 Cr ARR

    Tvashtri Skincare (founder agreed I could share the name; numbers are theirs). Before n8n: a 3-person support team across Slack + WhatsApp Web, each handling ~40 conversations/day, average reply time 47 minutes.

    We ran a 3-week pilot in March 2026. Workflow above, KB built from their existing Zendesk macros (188 articles, pruned to 67 that actually mattered), Shopify integration on order status, Slack handoff to the 3-person team only for tier-B/C confidence.

    After 4 weeks live: median reply time dropped to 3.1 seconds for the 84% auto-handled, and the 16% human-escalated still landed in under 12 minutes (down from 47). Customer CSAT (1–5, post-conversation poll) went from 4.1 to 4.6. The support team's workload halved; the brand redeployed one rep to outbound retention campaigns.

    Same architecture powers parts of the support stack on TalkDrill, our in-house English speaking app — Indian users prefer WhatsApp over email by ~7×, so the channel is mandatory for any consumer-facing product.

    Common mistakes

    Mistake 1 — Forgetting the 24-hour window. Outbound messages outside the service window without an approved template will silently fail. Always check the timestamp of the last inbound message before sending.

    Mistake 2 — Hardcoding the verify token in n8n. Use $env.META_VERIFY_TOKEN and store it as a real environment variable on your n8n container. If your workflow JSON leaks (it will, someone exports it), the token rotates without a code change.

    Mistake 3 — Replying with raw Markdown. WhatsApp uses its own formatting: bold, _italic_, ~strikethrough~, no headers, no lists. Markdown asterisks and underscores will render weird. Always sanitize through a Set node.

    Mistake 4 — No rate-limit awareness. Meta enforces ~80 messages/second business-tier per phone. If you blast a 5,000-message marketing campaign in one go, you will hit 429s. Use n8n's "Split In Batches" node with a 50-message batch and 1-second wait.

    Mistake 5 — Storing customer PII unencrypted. Your n8n executions logs contain phone numbers and message bodies. Set retention to 7 days max (EXECUTIONS_DATA_MAX_AGE=168) and ensure your Hetzner volume is encrypted at rest.

    FAQ

    Can n8n handle WhatsApp media (images, audio, PDFs)?

    Yes. The webhook payload contains a media ID. You do a follow-up GET to /v20.0/{media_id} with your bearer token to get a temporary download URL, then fetch the binary. We pipe audio to Whisper, images to Claude Vision, and PDFs to LlamaParse. Adds ~6 nodes per media type.

    What is the difference between WhatsApp Cloud API and an Indian BSP?

    Cloud API is hosted by Meta — you call Meta directly. BSPs like AiSensy / Wati sit between you and Meta, charge a per-message markup, and add a dashboard. Cloud API is cheaper and gives you full control; BSPs are faster to start if you do not have an n8n stack.

    How do I handle multilingual queries?

    Claude Haiku detects the language in the same classification pass. Branch on language to route to language-specific KB or to translate the reply via a second Claude call. We see ~30% Hinglish, 55% English, 10% Hindi, 5% Marathi for the Pune brand.

    Do I need to opt-in users for each conversation?

    For service messages (replies to user-initiated chats within 24h), no. For marketing or utility notifications you send first, yes — you need verifiable opt-in (a checkbox on signup, an order confirmation, a phone-collection form with disclosure). Meta audits BSPs and direct API users; non-compliance gets your number rate-limited or banned.

    Can the bot pretend to be human?

    You can give it a name (we call ours "Riya from Tvashtri Support") and friendly tone, but the first interaction must indicate it is automated. Meta's policy and India's DPDP Act both nudge toward disclosure. We open every new conversation with "Hi! I am Riya, the Tvashtri auto-assistant — I will get you a quick answer or hand you off to a human teammate."

    What is the failover if n8n goes down?

    Two protections. First, Meta retries failed webhook deliveries for ~7 days, so a 30-minute n8n outage does not lose messages. Second, we have a fallback Cloudflare Worker that catches webhooks if n8n is unreachable, queues them to Cloudflare Queues, and replays once n8n comes back. Adds a small monthly cost, worth it for production.

    How do I migrate from Wati or AiSensy?

    The webhook endpoint and phone-number-ID transfer cleanly — you swap the destination URL in the WhatsApp App → Configuration. The hardest part is template re-approval; Meta does not let you transfer template IDs across apps. Plan 1 week for re-approval + n8n cutover.

    Want this WhatsApp auto-reply system in your business?

    We ship the full 22-node n8n workflow above, your KB curated from existing tickets, and Meta Business verification end-to-end in 10 working days. Suitable if you take 500+ WhatsApp conversations a month and want to keep support headcount flat. No slides — your existing chat exports and our honest take.

    Book a 20-min Call
    Tags:
    n8nWhatsApp Business APIClaudeCustomer SupportIndiaD2CWorkflow Automation
    Share this post:
    Hrishikesh Baidya

    Hrishikesh Baidya

    CTO at Softechinfra specializing in Python, system architecture, and building secure, scalable software solutions.