Announcing CIMD support for MCP Client registration
Learn more

WhatsApp MCP vs WhatsApp API for AI Agents

Vinayak Ravi
Head of Marketing

TL;DR

  • Meta's WhatsApp Business Tools MCP (beta, September 2026) ships 18 setup-focused tools. Meta says it targets development and testing, not production sending.
  • The Cloud API owns the production surface: interactive messages, media, reactions, read receipts, groups, and calling. On either path, inbound messages arrive only by webhook.
  • MCP auth is OAuth through Facebook Login for Business, bound to a signed-in admin. The Cloud API runs on System User tokens or, for Tech Providers, per-customer Business Integration System User tokens.
  • A multi-tenant WhatsApp agent holds one long-lived token per customer WhatsApp Business Account. Neither path vaults or revokes them.
  • Scalekit's WhatsApp connector vaults each tenant's token, exposes 36 tools through execute_tool or a Virtual MCP server, and logs every downstream call.

The decision in front of you

Your agent needs to send and act on WhatsApp messages for your customers. Two weeks ago Meta shipped an official Model Context Protocol (MCP) server for the WhatsApp Business Platform, and the Cloud API your team has probably called for years is still there. They look like two routes to the same place.

They are not. One is a setup assistant for a developer at a keyboard. The other is the production messaging surface. Each puts you on a different auth path, and neither solves per-tenant credentials. Here's how to pick, and how to wire either one into a multi-tenant agent.

What WhatsApp MCP and WhatsApp API actually are

Both come from Meta, and both act on the same WhatsApp Business Accounts (WABAs). What differs is who each was built for.

WhatsApp Business Tools MCP

Meta announced the WhatsApp Business Tools MCP on September 15, 2026. It is a remote server over Streamable HTTP, hosted and maintained by Meta. It is in beta with a gradual rollout, and Meta says the interface and tool set may change.

The 18 tools at launch carry a whatsapp_biz_ prefix. They cover business and WABA discovery, phone number onboarding (add, send OTP, verify, register), template create, read, update, and delete, webhook configuration, payment and business verification checks, and a single whatsapp_biz_send_message tool.

The official reference is the WhatsApp Business Tools MCP page in the MCP section of the Meta for Developers documentation.

What Meta says it is for

Meta scopes this release explicitly: it is built for development and testing workflows, not production sending at scale. The tool list confirms it. whatsapp_biz_system_user_token exists to return a deep link to the System Users page in Business Settings, so you can generate a token and call the Cloud API directly.

Meta's own MCP hands production traffic to the API.

Meta also runs a separate Meta Social Technologies MCP for Graph API endpoint discovery, error troubleshooting, and documentation search. It complements the WhatsApp server rather than replacing it.

WhatsApp Cloud API

The Cloud API is Meta-hosted REST on the Graph API; current reference examples use v25.0, and each version stays callable for roughly two years. The core call is POST /<PHONE_NUMBER_ID>/messages, which carries every message type. Companion endpoints manage phone numbers, templates, media, business profiles, webhook subscriptions, groups, and calling.

Auth is always a bearer access token. Meta defines three kinds: System User tokens for direct developers, Business Integration System User tokens for Tech Providers acting on onboarded customers, and User tokens for the first test message.

The official reference is the WhatsApp Business Platform section of the Meta for Developers documentation.

Comparing them where it matters for agents

Four dimensions decide this for an agent: what it can do, how it authenticates, which platform rules it inherits, and what you run in production.

What your agent can actually do

The MCP server covers the setup lifecycle. The Cloud API covers the conversation. The third column is the Scalekit WhatsApp connector, which wraps the Cloud API as 36 agent-ready tools.

Setup and account management

These are the rows where Meta's MCP is strongest, because onboarding and template work is what it was built for.

Capability
WhatsApp MCP (beta)
WhatsApp Cloud API
Scalekit WhatsApp connector
Discover businesses and WABAs
Yes
Yes
Yes: businesses, owned WABAs, shared WABAs
Phone number onboarding
Yes: add, OTP, verify, register
Yes
Partial: request code, verify, register, deregister, set PIN
Template create, update, delete
Yes
Yes
Yes
Webhook setup
Yes: callback URL, fields, WABA subscription
Yes
Partial: WABA subscription with callback override
Business profile and QR codes
No
Yes
Yes

Messaging and conversation

These are the rows a production agent exercises on every run, and where the beta MCP server thins out.

Capability
WhatsApp MCP (beta)
WhatsApp Cloud API
Scalekit WhatsApp connector
Send text or template message
Yes: one tool, confirms the target first
Yes
Yes
Interactive buttons, lists, CTA URLs
No
Yes
Yes
Media, location, contact cards
No
Yes
Yes: media by ID or public link
Reactions and read receipts
No
Yes, plus typing indicators
Yes
Receive inbound messages
No
Webhooks only
No: runs through your webhook receiver
Groups, calling, WhatsApp Flows
No
Yes
No

Where the gap bites

The most important row is the one where every column is weak: receiving messages. The Cloud API is a send pipe with a webhook stream attached. Inbound message content arrives only by webhook, and there is no endpoint to fetch a conversation you did not capture, outside a one-time history sync when an existing WhatsApp Business app number is onboarded.

Every WhatsApp agent therefore needs a webhook receiver that verifies the X-Hub-Signature-256 signature, deduplicates by wamid, and persists the message before any tool call runs.

The MCP gap is different in kind. Interactive messages, media, and reactions are what a support or commerce agent sends all day, and the beta server does not expose them.

The auth path each one puts you on

The two paths authenticate different things. MCP authenticates a person. The Cloud API authenticates a token, and the token decides which WABAs the agent can reach.

MCP: a signed-in admin, every session

You sign in with Facebook Login for Business, select the businesses and apps the server may touch, and grant business_management, whatsapp_business_management, and whatsapp_business_messaging. Before any tool runs, the server confirms you are an admin of the app and that the business has accepted the Cloud API Terms of Service.

Anything that changes state requires an authenticated person rather than an app credential, and you repeat the sign-in when the client restarts. That is the right model for a developer in Claude Code. It is the wrong model for a headless agent.

Cloud API: long-lived tokens per business

A direct developer uses a System User token, and the generation flow lets you pick its expiry, including never. Admin system users see every WABA the business portfolio owns or has been shared by default, so scope assets to an Employee system user instead.

A Tech Provider implements Embedded Signup, exchanges the returned code server-side, and receives a Business Integration System User token scoped to that one customer. Meta designed these tokens for automated actions without future re-authentication. User tokens expire within hours and only suit the first test.

The identity problem that follows you either way

Both paths end with a credential per business. MCP gives you a session tied to one admin. The Cloud API gives you one token per WABA. In a multi-tenant B2B agent, neither path solves storage, isolation, rotation, or revocation of those credentials.

Those are infrastructure problems regardless of which path you choose. For a deeper look at credential ownership across agent tool-calling patterns, the structural problem is the same whether you're on MCP or REST.

The 24-hour window and templates

Some rules sit below both paths, and the agent has to reason about them. A customer message opens a 24-hour customer service window, and each new message resets it. Inside the window the agent can send any service message. Outside it, only Meta-approved templates go through; a free-form send fails with error 131047.

Templates add their own latency. A new template is reviewed before first use, and editing one resets it to pending review. An agent that drafts templates on the fly will stall on approval.

Limits, pricing, and the AI provider policy

Messaging limits cap how many unique users you reach outside the window in a moving 24 hours: 250 for a new business portfolio, then 2,000, 10,000, 100,000, and unlimited. The limit is shared across every number in the portfolio. Meta has billed per delivered message since July 1, 2025, and service messages inside an open window are not charged.

Since January 15, 2026, the WhatsApp Business Solution Terms bar AI Providers from offering general-purpose assistants, except where Meta is legally required to permit it. Purpose-specific business agents, such as support, order updates, and bookings, remain allowed. Scope your agent's job accordingly.

What you own on the MCP path

Meta hosts the server and maintains the tool schemas. You own re-authentication on every client restart, per-user per-tool rate limits, and tool changes that can land at any point during the beta. The send tool confirms the target with a human before sending.

None of that is a defect. It describes a developer tool, and it means the MCP server should not sit in your production request path.

What you own on the Cloud API path

You own everything. That covers the webhook receiver, window tracking, template lifecycle, and retries. Each number supports 80 messages per second by default, counting inbound and outbound, with automatic upgrade to 1,000. Exceed it and you get 130429; send too fast to one recipient and you hit the pair rate limit, 131056.

Delivery order across a sequence is not guaranteed, so confirm a delivered status webhook before sending the next step. Undeliverable messages retry for 30 days by default, then drop. Graph API version upgrades land on your calendar every couple of years.

When to use MCP, when to use the API

The line is not about preference. It is about whether a human admin is present and whether the traffic reaches customers.

Use WhatsApp MCP when

  • A developer is onboarding a new number from Claude Code, Codex, or ChatGPT: create the WABA, verify the OTP, and register it for the Cloud API without clicking through Business Manager.
  • You are iterating on template copy and want create, edit, and approval status in one conversation.
  • You are configuring webhooks and want a test send from the real registered number to validate the integration end to end.
  • You want Terms of Service, payment method, and Business Verification gaps surfaced before they break a launch.

Use the Cloud API when

  • The agent runs headless: order updates, appointment reminders, and support replies triggered by webhooks, with no admin signed in.
  • It needs interactive buttons, list menus, media, reactions, read receipts, or typing indicators.
  • You serve many businesses as a Tech Provider through Embedded Signup and per-customer business tokens.
  • Volume matters and you need throughput control, backoff on 130429, and per-recipient pacing to avoid 131056.
  • You need groups, calling, or WhatsApp Flows.

The credential problem that exists on both paths

Both paths hand you a credential per business. Neither hands you a vault, isolation, or a revocation flow.

The shared system user token failure mode

A single admin System User token looks right in a demo. In production, it reaches every WABA in the portfolio, it may never expire, and it sits wherever your agent runtime can read it. A prompt injection that talks the agent into whatsapp_phone_number_deregister or whatsapp_message_template_delete has the entire portfolio as its blast radius.

Every audit entry looks identical: one system user, with no link to the tenant or the human who triggered the run.

The N-WABA problem

As a Tech Provider, every onboarded customer adds one Business Integration System User token. Two hundred tenants means two hundred long-lived bearer tokens to encrypt at rest, isolate per tenant, keep out of logs and model context, and delete the day a customer offboards.

The failure is quiet. A customer revokes your access, and the agent finds out on the next send, mid-conversation, with that business's own customer waiting for a reply.

Recommended reading: How tool calling auth changes when you move from single-tenant to multi-tenant covers the same credential problem for agents serving many businesses.

Where Scalekit fits

Scalekit's WhatsApp connector stores each tenant's token as a connected account in an AES-256 encrypted vault and injects it on every call. Credentials never touch the agent runtime or the model's context. The same connected account serves direct tool calling and MCP, so the MCP vs API decision doesn't change your auth infrastructure.

Building a WhatsApp agent with Scalekit

The Scalekit WhatsApp connector exposes 36 tools over the Cloud API with Bearer Token auth. Below are two integration styles in Python: direct tool calling with the Claude SDK, and a Virtual MCP server consumed by LangChain. Both run on the same connected accounts.

Configure the WhatsApp connection

In Meta Business Settings, create a system user with the Employee role, assign it your WhatsApp Business Account with full control, and generate a token with whatsapp_business_messaging and whatsapp_business_management. Meta's get-started guide also grants business_management, which business-level discovery calls such as whatsapp_businesses_list typically rely on.

In the Scalekit dashboard, open AgentKit > Connections > Create Connection, choose WhatsApp, and paste the token. One rule prevents most integration errors: the connection_name in your code must match the connection name in the dashboard exactly.

Connect each tenant's WhatsApp account

Each customer gets a connected account keyed by an identifier you choose, typically your tenant ID. For non-OAuth connectors like this one, the authorization link opens a Scalekit-hosted page that collects the tenant's token. The token goes straight into the vault instead of passing through your backend.

pip install scalekit-sdk-python anthropic python-dotenv
import json import os import anthropic from dotenv import load_dotenv from google.protobuf.json_format import MessageToDict from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit_client.actions # Must match the connection name in AgentKit > Connections exactly CONNECTION_NAME = os.getenv("WHATSAPP_CONNECTION_NAME", "whatsapp") IDENTIFIER = "tenant_acme" # one identifier per customer WhatsApp Business Account PHONE_NUMBER_ID = os.environ["ACME_PHONE_NUMBER_ID"] # 1. Make sure this tenant's WhatsApp credential is in the vault account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=IDENTIFIER, ).connected_account if account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=IDENTIFIER, ) raise SystemExit(f"Ask the tenant admin to connect WhatsApp here: {link.link}")

Path 1: Retrieve the scoped tool surface

The agent does not load the connector catalog. list_scoped_tools returns only the tools this tenant's connected account is authorized to call, and the tool_names filter narrows that to the agent's role.

That matters at 36 tools. At roughly 200 tokens per definition, the full connector costs about 7,200 tokens per model call before the agent does any work. Four tools cost about 800. The fix is not better prompting. It is surface reduction.

# 2. Load only the tools this agent role needs, for this tenant ALLOWED_TOOLS = [ "whatsapp_message_templates_list", "whatsapp_send_template_message", "whatsapp_send_text_message", "whatsapp_mark_message_read", ] scoped_response, _ = actions.tools.list_scoped_tools( identifier=IDENTIFIER, filter={"connection_names": [CONNECTION_NAME], "tool_names": ALLOWED_TOOLS}, page_size=100, ) llm_tools = [] for scoped_tool in scoped_response.tools: definition = MessageToDict(scoped_tool.tool).get("definition", {}) llm_tools.append( { "name": definition["name"], "description": definition.get("description", ""), "input_schema": definition.get("input_schema", {"type": "object", "properties": {}}), } )

Path 1: Run the agent loop with the Claude SDK

The loop sends the scoped tools to Claude, checks stop_reason, runs each tool_use block through execute_tool, and returns every result in one user turn. Errors go back to the model as tool results, so a 131047 window error prompts it to switch to a template instead of crashing the run.

# 3. Run the agent loop claude = anthropic.Anthropic() system_prompt = ( "You send order updates for Acme on WhatsApp. " f"Always send from phone_number_id {PHONE_NUMBER_ID}. " "If the customer has not messaged in the last 24 hours, you must use an " "approved template; list templates first and pick the right one." ) messages = [ { "role": "user", "content": "Tell +14155550123 that order #1042 has shipped. " "They last messaged us two days ago.", } ] while True: response = claude.messages.create( model="claude-sonnet-5", max_tokens=2048, system=system_prompt, tools=llm_tools, messages=messages, ) messages.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": print("".join(block.text for block in response.content if block.type == "text")) break tool_results = [] for block in response.content: if block.type != "tool_use": continue try: result = actions.execute_tool( tool_name=block.name, tool_input=block.input, identifier=IDENTIFIER, connection_name=CONNECTION_NAME, ) content, is_error = json.dumps(result.data, default=str), False except Exception as exc: # surface Meta error codes (131047, 130429, ...) to the model content, is_error = f"Tool call failed: {exc}", True tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": content, "is_error": is_error, } ) messages.append({"role": "user", "content": tool_results})

Path 2: Create a Virtual MCP server once per agent role

A Virtual MCP server is a scoped endpoint that declares which connections and tools an agent sees. You create it once per agent role, not once per tenant, and reuse its static mcp_server_url for every run. There is no MCP server to deploy, host, or maintain.

import os from dotenv import load_dotenv from scalekit import ScalekitClient from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping load_dotenv() scalekit_client = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) vmcp = scalekit_client.actions.mcp.create_config( name="whatsapp-support-agent", description="Reply-only WhatsApp tools for the support agent role", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="whatsapp", # must match AgentKit > Connections exactly tools=[ "whatsapp_send_text_message", "whatsapp_send_interactive_message", "whatsapp_send_template_message", "whatsapp_mark_message_read", ], ), ], ) print("SCALEKIT_MCP_CONFIG_ID =", vmcp.config.id) print("SCALEKIT_MCP_SERVER_URL =", vmcp.config.mcp_server_url)

Path 2: Mint a tenant token and run the LangChain agent

Before each run, confirm the tenant's connections are ACTIVE, then mint a short-lived session token for that tenant. The endpoint is static; the identity is not. LangChain consumes the server through langchain-mcp-adapters.

pip install scalekit-sdk-python langchain-anthropic "langchain-mcp-adapters>=0.3,<1" python-dotenv
import asyncio import os from datetime import timedelta from dotenv import load_dotenv from langchain_anthropic import ChatAnthropic from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_mcp_adapters.client import MultiServerMCPClient from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit_client.actions CONFIG_ID = os.environ["SCALEKIT_MCP_CONFIG_ID"] MCP_URL = os.environ["SCALEKIT_MCP_SERVER_URL"] PHONE_NUMBER_ID = os.environ["ACME_PHONE_NUMBER_ID"] def mint_token(identifier: str) -> str: # Refuse to run if any connection behind this server is not ACTIVE for the tenant state = actions.mcp.list_mcp_connected_accounts( config_id=CONFIG_ID, identifier=identifier, include_auth_link=True, ) for account in state.connected_accounts: if account.connected_account_status != "ACTIVE": raise RuntimeError( f"{account.connection_name} needs auth: {account.authentication_link}" ) # Short-lived, tenant-scoped bearer for this run only return actions.mcp.create_session_token( mcp_config_id=CONFIG_ID, identifier=identifier, expiry=timedelta(minutes=30), ).token async def run(identifier: str, inbound: str) -> None: client = MultiServerMCPClient( { "whatsapp": { "transport": "streamable_http", "url": MCP_URL, "headers": {"Authorization": f"Bearer {mint_token(identifier)}"}, } } ) tools = await client.get_tools() tool_map = {tool.name: tool for tool in tools} llm = ChatAnthropic(model="claude-sonnet-5", max_tokens=2048).bind_tools(tools) messages = [ SystemMessage( f"You are Acme's WhatsApp support agent. Reply from phone_number_id " f"{PHONE_NUMBER_ID}. Mark the inbound message as read, then answer " "within the open customer service window." ), HumanMessage(inbound), ] while True: response = await llm.ainvoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for call in response.tool_calls: result = await tool_map[call["name"]].ainvoke(call["args"]) messages.append(ToolMessage(content=str(result), tool_call_id=call["id"])) if __name__ == "__main__": # In production, `inbound` comes from your WhatsApp webhook receiver asyncio.run( run( identifier="tenant_acme", inbound="Inbound wamid.HBgL123 from +14155550123: Where is order #1042?", ) )

Your webhook receiver calls run() with the tenant's identifier and the verified inbound payload. The agent never sees a WhatsApp token.

Why the Scalekit path holds up in production

The code above is short because the hard parts moved into the infrastructure layer. Three of them matter most for WhatsApp agents.

Tool call logs for every downstream call

Every execute_tool call returns an execution_id and lands in AgentKit logs with full attribution: who authorized the call, which agent ran it, and what the Cloud API returned. Logs export to your SIEM and separate failures by source.

For WhatsApp, that answers the question a compliance team asks after the wrong template reaches thousands of customers: which tenant, which run, which tool, which input. More on the model in agent tool observability and on the audit trails for agent auth.

Virtual MCP for multi-tool, multi-tenant agents

Meta's MCP server is scoped to one signed-in admin. A Virtual MCP server gives a production agent an MCP endpoint with per-tenant identity: one server definition per agent role, and a short-lived session token per tenant per run, defaulting to about an hour.

The same definition can map WhatsApp alongside Zendesk, HubSpot, or Slack connections, so a support agent reaches every tool it needs through one endpoint.

Least privilege at the tool level

A support agent has no reason to see whatsapp_phone_number_deregister or whatsapp_phone_number_set_pin; the connector docs note there is no API to disable two-step verification once a PIN is set. Leave those tools out of the scoped surface, and the model cannot call what it cannot see.

What the user can't do, the agent can't do. For template sends that need a human sign-off, pair the scoped surface with the approval patterns in agent tool calling auth patterns. Secure token management for AI agents at scale covers how to handle the token storage side of this problem in production.

Which one to build against

If a developer is standing up a WhatsApp number, drafting templates, or wiring webhooks, use Meta's WhatsApp Business Tools MCP. It removes real Business Manager busywork, and it is the fastest way to reach a working setup.

If your agent messages customers in production, headless and at volume, build against the Cloud API. Meta draws that line itself.

Either way, you end up holding one long-lived credential per business you serve. That is the part that needs production-grade infrastructure. The token vault pattern is why Scalekit exists for this problem.

Build your WhatsApp agent with Scalekit

Start from the WhatsApp connector docs for the full 36-tool reference, or browse the Scalekit connector catalog and the AgentKit connector docs to pair WhatsApp with the rest of your stack. Ready-made starting points include the support triage agent and support ticket automation agent templates.

Check Scalekit pricing for the free tier. Building a WhatsApp agent and want help with the auth model today? Talk to us.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.