AI

WhatsApp as a tool in LangChain and CrewAI (Python)

To use WhatsApp as a tool in LangChain or CrewAI, you write a Python function that calls the ZapZap API (for example POST /send/text), register it with the tool decorator and hand it to the agent. The critical part is what surrounds that function: number validation, an allowed list, a volume limit and human approval. Below are both frameworks with code, on the current versions as of 10/27/2026.

Published on · 6 min read

This article is also available in: Português · Español

How an agent uses the WhatsApp API as a tool

In agent frameworks, a tool is a function with a name, a description and a parameter schema. The AI model reads the description, decides whether the tool helps with the task and returns the arguments; the framework runs the function and returns the result to the model. For WhatsApp, the functions are thin: each one calls an API endpoint.

Two design consequences. The tool description is part of the prompt: write it as a clear instruction, saying what it does, the number format (country code, digits only) and when not to use it. And the function is where the locks live: the model can be wrong or be induced by malicious text, so validation cannot depend on it.

Versions and the state of the ecosystem

State checked on PyPI on 10/27/2026: langchain 1.4.7, langchain-core 1.7.0 and crewai 1.15.30. Both projects move fast and have broken things between major versions; the examples use LangChain 1.x's create_agent and CrewAI 1.x's tool decorator. If you use another version, check its documentation before copying.

As of this date there is no ZapZap package for LangChain or CrewAI and no ZapZap MCP server of our own; what exists is the REST API and the OpenAPI file. That is why the tools below are your code, on top of the API. For the MCP route, see the guide on the WhatsApp MCP server.

Shared layer: a Python client with locks

Write first a module that talks to the API and applies the rules. Both frameworks reuse it. It validates the number format, restricts recipients to an allowed list, caps text length and counts sends per hour. This lives outside the prompt on purpose.

Credentials come from environment variables. Install with pip install requests langchain crewai and set ZAPZAP_INSTANCE_ID, ZAPZAP_API_KEY, ZAPZAP_API_SECRET, ZAPZAP_ALLOWED (comma-separated numbers) and, to enable real sending, ZAPZAP_ENABLE_SEND=true.

Python (zapzap_tools.py)

import os
import re
import time

import requests

BASE = "https://api.zapzapapi.com/api/v1/" + os.environ["ZAPZAP_INSTANCE_ID"]
HEADERS = {
    "Content-Type": "application/json",
    "x-api-key": os.environ["ZAPZAP_API_KEY"],
    "x-api-secret": os.environ["ZAPZAP_API_SECRET"],
}
ALLOWED = {n for n in os.environ.get("ZAPZAP_ALLOWED", "").split(",") if n}
SEND_ENABLED = os.environ.get("ZAPZAP_ENABLE_SEND") == "true"
MAX_PER_HOUR = 20
_sent = []


def _post(path, body):
    r = requests.post(BASE + path, json=body, headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.text


def status_instancia():
    r = requests.get(BASE + "/instance/status", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.text


def verificar_numeros(numeros):
    if not numeros or len(numeros) > 50 or not all(re.fullmatch(r"\d{10,15}", n) for n in numeros):
        raise ValueError("invalid numbers: use country code and digits only")
    return _post("/chat/check", {"numbers": numeros})


def enviar_texto(numero, texto):
    if not SEND_ENABLED:
        return "sending disabled (safe mode)"
    if not re.fullmatch(r"\d{10,15}", numero):
        raise ValueError("invalid number: use country code and digits only")
    if numero not in ALLOWED:
        raise PermissionError("number not in the allowed list")
    if not texto or len(texto) > 1000:
        raise ValueError("text empty or too long")
    agora = time.time()
    _sent[:] = [t for t in _sent if agora - t < 3600]
    if len(_sent) >= MAX_PER_HOUR:
        raise RuntimeError("hourly limit reached")
    _sent.append(agora)
    return _post("/send/text", {"number": numero, "text": texto})

LangChain: register the functions as tools and create the agent

In LangChain, the tool decorator (from langchain_core.tools) turns the function into a tool; the docstring becomes the description and the type annotations become the schema. create_agent assembles the agent with the model, the tools and the system prompt. The model comes from the provider you configure (you fill in the provider:model string), and that provider's cost is separate.

The invocation takes a list of messages and returns the state with the final messages. To require human approval before sending, LangChain offers interrupt mechanisms (middleware and a checkpointer); since the API varies by version, the example keeps sending disabled by default and leaves approval to you, following your version's documentation.

Python (LangChain)

from langchain.agents import create_agent
from langchain_core.tools import tool

import zapzap_tools as zap


@tool
def status_whatsapp() -> str:
    """Mostra se a instancia de WhatsApp esta conectada."""
    return zap.status_instancia()


@tool
def verificar_numeros_whatsapp(numeros: list[str]) -> str:
    """Verifica quais numeros tem WhatsApp. Use DDI e so digitos, ex: 5511999999999."""
    return zap.verificar_numeros(numeros)


@tool
def enviar_whatsapp(numero: str, texto: str) -> str:
    """Envia uma mensagem de texto. Use so quando o usuario pedir explicitamente. Numero com DDI e so digitos."""
    return zap.enviar_texto(numero, texto)


agente = create_agent(
    "SEU_PROVEDOR:SEU_MODELO",
    tools=[status_whatsapp, verificar_numeros_whatsapp, enviar_whatsapp],
    system_prompt="Voce ajuda a equipe com tarefas de WhatsApp. Nunca envie mensagem sem pedido explicito. Se faltar dado, pergunte.",
)

resultado = agente.invoke(
    {"messages": [{"role": "user", "content": "Veja se a instancia esta conectada e se 5511999999999 tem WhatsApp."}]}
)
print(resultado["messages"][-1].content)

CrewAI: agents, tasks and the same layer of locks

In CrewAI, the tool decorator (from crewai.tools) creates the tool from the function and a name. You define an Agent with role, goal and backstory, a Task with description and expected output, and a Crew that joins them. kickoff runs it. The model is configured per CrewAI's documentation for the provider you use.

CrewAI shines when there is more than one role, for example an agent that drafts the message and another that reviews the tone before sending. For WhatsApp, a worthwhile design has the sending agent receive only already reviewed text and has the send Task require human confirmation (the Task's human_input parameter, per your version's documentation).

Python (CrewAI)

from crewai import Agent, Crew, Task
from crewai.tools import tool

import zapzap_tools as zap


@tool("verificar_numeros_whatsapp")
def verificar_numeros_whatsapp(numeros: list[str]) -> str:
    """Verifica quais numeros tem WhatsApp. Use DDI e so digitos, ex: 5511999999999."""
    return zap.verificar_numeros(numeros)


@tool("enviar_whatsapp")
def enviar_whatsapp(numero: str, texto: str) -> str:
    """Envia uma mensagem de texto para um numero da lista permitida."""
    return zap.enviar_texto(numero, texto)


operador = Agent(
    role="Operador de WhatsApp",
    goal="Executar tarefas de WhatsApp com seguranca e sem enviar nada que nao tenha sido pedido",
    backstory="Voce cuida do canal de WhatsApp da empresa e segue as regras da lista permitida.",
    tools=[verificar_numeros_whatsapp, enviar_whatsapp],
)

tarefa = Task(
    description="Verifique se o numero 5511999999999 tem WhatsApp e informe o resultado. Nao envie mensagens.",
    expected_output="Uma frase dizendo se o numero tem WhatsApp.",
    agent=operador,
)

crew = Crew(agents=[operador], tasks=[tarefa])
print(crew.kickoff())

Which locks to keep, whatever the framework

LockWhere it livesWhy
Allowed list of numbersIn the function, not the promptThe model can err or be induced
Format validationIn the functionAvoids invalid numbers and field injection
Hourly limitIn the function or the queueContains an agent loop error
Human approvalIn the framework or your flowLast barrier before a real send
Safe mode by defaultEnvironment variableDevelop without actually sending
Logging of every callIn the functionAudit and debugging
Dedicated instanceIn the accountIsolates risk from the main number

Agents loop, repeat tools and get confused. Set a maximum number of steps in the framework and treat any function exception as a readable result for the model, without exposing credentials. Incoming messages the agent reads are untrusted input: a customer can write instructions aimed at the model, and the allowed list and approval are what prevent damage.

From prototype to production: channel limits and next steps

The tool is only the arm; the channel keeps its rules. Automated sending is subject to WhatsApp's Terms, and no agent reduces number-restriction risk; it can raise it if it starts cold conversations at volume. Use the agent to reply to people who wrote first, support the team and run internal tasks. The anti-ban guide and data protection law apply here as with any send.

To receive the messages the agent will handle, configure the instance webhook (see webhooks and the guide on media through the webhook) and deliver the event to the agent through a queue. The route reference is on the WhatsApp API page, and the account walkthrough is in the quick start.

Quick summary

  • An agent tool is a Python function that calls an API endpoint; the docstring is the description the model reads.
  • In LangChain 1.x use tool and create_agent; in CrewAI 1.x use the tool decorator with Agent, Task and Crew.
  • Put the locks in the function: allowed list, validation, hourly limit and safe mode by default.
  • Require human approval before real sending and treat messages you read as untrusted input.
  • There is no ZapZap package or MCP server of our own for these frameworks on 10/27/2026; the base is the REST API.

Also read

Frequently asked questions

Is there a ZapZap plugin for LangChain or CrewAI?

No, as of 10/27/2026. What exists is the REST API with a per-endpoint reference and an OpenAPI file, and a node package for n8n. The examples in this guide are your own code on top of the API, using each framework's tool decorator.

How do I stop the agent from sending to any number?

Put the allowed list inside the tool function, not the prompt, and validate the number format. Add an hourly limit, safe mode by default and human approval before real sending.

Which framework should I pick: LangChain or CrewAI?

LangChain fits a single agent with several tools and controlled flows well. CrewAI is more natural when there are distinct roles (draft, review, send). For WhatsApp, the choice matters less than the locks and human approval.

Can the agent receive WhatsApp messages?

Yes, through the instance webhook: register a URL, enable the messages event and deliver each event to the agent through a queue, filtering your own messages and duplicate events. The tools cover sending and lookups.

Do I need a specific AI model?

No. The frameworks accept several providers; use the one you have a credential for. Test with real conversations and prefer models that are good at calling tools, measuring cost and latency in your case.

Give your agent a WhatsApp channel

Create your account, verify your phone and get the welcome credit, no deposit needed (one per phone, subject to the current campaign). Connect a number by QR code and test the tools in safe mode.