Back to blog
Chatmaid Developers··10 min read

Connect an AI Agent to WhatsApp: Claude, ChatGPT, Gemini

Give Claude, ChatGPT or Gemini a WhatsApp number: link it with a QR code, send with one API call, read replies through webhooks, or use the MCP server.

Chatmaid Team
Connect an AI Agent to WhatsApp: Claude, ChatGPT, Gemini

To connect an AI agent to WhatsApp, give it a tool that calls a WhatsApp messaging API, and send that API's incoming-message webhooks back to your agent so it can read replies. With Chatmaid, you link a WhatsApp number by scanning a QR code. Your agent then sends with one POST /v1/messages/send request and receives replies as signed message.received webhooks. Claude Code, Cursor and Claude Desktop can skip the HTTP code and use the Chatmaid MCP server instead. There is no Meta Business verification and no template approval, and a free sandbox lets you test before you pay.

Code and API details below are current as of October 2026.

Why developers hit a wall with WhatsApp's official API

Maybe you built a Claude agent that triages email, a GPT assistant that answers customer questions, or a Gemini workflow that writes reports. Sooner or later someone asks: can it send me a WhatsApp message?

Through Meta's WhatsApp Business Platform, that takes more than an afternoon:

For a large, regulated sender that process makes sense. For a developer who wants an agent to message a few people who expect to hear from it, it slows everything down. The guide to the WhatsApp API without Meta Business verification goes deeper on the trade-offs, and Chatmaid vs Twilio compares the two approaches side by side.

How Chatmaid connects without Meta's API

The Chatmaid Developers API works like WhatsApp Web. You link your own number as a linked device, and the API sends from it.

  • Setup: scan a QR code with WhatsApp, the same way you log in to WhatsApp Web.
  • Price: a flat $7.99 a month for live sending from one number.
  • Sandbox: free. Test keys simulate each send and fire real webhooks to your endpoint, but nothing reaches WhatsApp.

Because the messages come from a normal WhatsApp account, normal WhatsApp rules apply: have your agent message only people who expect to hear from it.

Give your AI agent a WhatsApp number in three steps

Step 1: Create an account and get a test key

Create a free account from the Chatmaid Developers page. In the dashboard, stay in Test Mode, open API Keys and create a key. Test keys start with sk_test_ and work without a subscription. A new key can send messages and read phone numbers, groups and message status by default.

When you're ready for real messages, start the subscription, switch the dashboard to Live and create a live key (sk_live_). The base URL stays the same: https://developers-api.chatmaid.net/v1.

Step 2: Connect a WhatsApp number

Open Phone Numbers and add your number. In Test Mode there is no QR code to scan: the sandbox connects the number for you. In Live mode, the dashboard shows a QR code: scan it from WhatsApp's Linked devices screen on your phone. Your phone stays logged in, and the API sends from that number. Numbers are kept separately for each mode, so add yours again after switching to Live, and use a live key to send from it. If WhatsApp asks for a passkey to link the number, turn off passkey linking for that WhatsApp account first, because a QR scan can't complete it; the dashboard warns you when this happens.

To check which sender numbers your key can use, list them:

curl https://developers-api.chatmaid.net/v1/phone-numbers \
  -H "Authorization: Bearer sk_test_xxx"

Use the E.164 number (for example +15551234567) as fromPhoneId when you send. The id from this list works too.

Step 3: Send a message from your agent

Sending is a single POST:

curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fromPhoneId": "+15551234567",
    "to": "+15557654321",
    "content": "Your agent finished the weekly report. Summary: 3 new leads, 1 overdue invoice."
  }'

The response wraps the message in a data object (abridged):

{
  "success": true,
  "data": {
    "id": "msg_abc123def456",
    "from": "+15551234567",
    "to": "+15557654321",
    "isGroup": false,
    "groupId": null,
    "content": "Your agent finished the weekly report. Summary: 3 new leads, 1 overdue invoice.",
    "mediaUrls": [],
    "status": "sent",
    "createdAt": "2026-10-22T14:18:33.000Z",
    "sentAt": "2026-10-22T14:18:33.120Z"
  }
}

With a live key, the message then moves to delivered and read as WhatsApp receipts arrive (read may never arrive if the recipient has read receipts turned off). Sandbox messages stop at sent. Poll GET /v1/messages/{id} or subscribe to webhooks to follow it.

If WhatsApp refuses a send, the request returns an HTTP error instead of success: true. The body carries a stable error code, such as RECIPIENT_NOT_REACHABLE or NOT_GROUP_MEMBER, plus the messageId, so your agent can tell the user what went wrong.

If your agent runs on Node.js, the official SDK does the same in a few lines:

import { Chatmaid } from "@chatmaid/sdk";

const cm = new Chatmaid({ apiKey: process.env.CHATMAID_API_KEY! });

const message = await cm.messages.send({
  fromPhoneId: "+15551234567",
  to: "+15557654321",
  content: "Your agent finished the weekly report.",
});

console.log(message.id, message.status);

Agents sometimes repeat a tool call after a timeout. Pass an idempotencyKey (up to 64 characters) tied to the task, and a repeated request returns the original message instead of sending a second one. The SDK adds a random key to each call, which covers its own network retries but not a repeated tool call, so pass your own key for that.

How to connect Claude, ChatGPT and Gemini

Claude: MCP server or Messages API

With the Chatmaid MCP server installed in Claude Code or Claude Desktop, Claude can send WhatsApp messages and read the ones your number receives, with no glue code. Claude can then:

  • Send you a WhatsApp message when a long task finishes
  • Forward a research summary to your phone
  • Alert you when a data pipeline shows something unusual

Setup is in the MCP section below. If you call Claude through the Messages API instead, register the same send_whatsapp tool as in the ChatGPT example that follows, with its schema under input_schema instead of parameters. When Claude wants to send, the response contains a tool_use block whose input is already a dict, so call send_whatsapp(block.input["to"], block.input["message"]). Then send back a user message containing a tool_result block, with the block's id as tool_use_id and the JSON reply as content.

ChatGPT: OpenAI Responses API

OpenAI shut down the Assistants API on August 26, 2026 and points developers to the Responses API. Function calling there works like this: you describe a send_whatsapp tool, the model decides when to call it, your code runs the call, and you hand the result back.

Install the packages with pip install openai requests, then set CHATMAID_API_KEY, OPENAI_API_KEY and OPENAI_MODEL (any current model that supports function calling, such as gpt-6-luna) and change FROM_PHONE to your connected number.

import json
import os

import requests
from openai import OpenAI

CHATMAID_API_KEY = os.environ["CHATMAID_API_KEY"]  # sk_test_... or sk_live_...
FROM_PHONE = "+15551234567"  # your connected WhatsApp number


def send_whatsapp(to: str, message: str) -> dict:
    response = requests.post(
        "https://developers-api.chatmaid.net/v1/messages/send",
        headers={"Authorization": f"Bearer {CHATMAID_API_KEY}"},
        json={"fromPhoneId": FROM_PHONE, "to": to, "content": message},
        timeout=30,
    )
    return response.json()


tools = [
    {
        "type": "function",
        "name": "send_whatsapp",
        "description": "Send a WhatsApp message to one person.",
        "parameters": {
            "type": "object",
            "properties": {
                "to": {
                    "type": "string",
                    "description": "Recipient phone number in E.164 format, e.g. +15557654321",
                },
                "message": {
                    "type": "string",
                    "description": "Message text, up to 4096 characters",
                },
            },
            "required": ["to", "message"],
            "additionalProperties": False,
        },
        "strict": True,
    }
]

client = OpenAI()  # reads OPENAI_API_KEY
model = os.environ["OPENAI_MODEL"]  # any current model that supports function calling

input_list = [
    {
        "role": "user",
        "content": "Tell +15557654321 on WhatsApp that the nightly import finished with no errors.",
    }
]

response = client.responses.create(model=model, tools=tools, input=input_list)
input_list += response.output

for item in response.output:
    if item.type == "function_call" and item.name == "send_whatsapp":
        args = json.loads(item.arguments)
        result = send_whatsapp(args["to"], args["message"])
        input_list.append(
            {
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": json.dumps(result),
            }
        )

final = client.responses.create(model=model, tools=tools, input=input_list)
print(final.output_text)

The model sees the API's JSON reply, including any error code, so it can tell the user whether the message went out.

Gemini, LangChain and other frameworks

Gemini agents call Chatmaid the same way. Declare a send_whatsapp function with the same two parameters using Gemini function calling, run the HTTP request when the model asks for it, and return the JSON reply. The same pattern works in LangChain, CrewAI or your own loop, and the send_whatsapp function above can be reused as-is. For a full walkthrough with a framework, see building a WhatsApp AI agent with LangChain and Chatmaid.

Reading incoming messages

Sending is half of it. For an agent people can talk to, you need their replies, and Chatmaid delivers those as webhooks.

In the dashboard, open Webhooks, enter your server's public HTTPS URL (for local testing, use a tunnel such as ngrok) and tick Message Received (message.received). When you save, copy the webhook secret the dashboard shows, because it is displayed only once. The code below reads it from CHATMAID_WEBHOOK_SECRET. When someone messages your connected number, Chatmaid POSTs this to your endpoint:

{
  "event": "message.received",
  "timestamp": "2026-10-22T14:21:09.000Z",
  "data": {
    "messageId": "inmsg_abc123def456",
    "from": "+15557654321",
    "to": "+15551234567",
    "content": "Thanks, got it!",
    "type": "text",
    "isGroup": false,
    "groupId": null,
    "receivedAt": "2026-10-22T14:21:08.000Z"
  }
}

Every delivery carries an X-Chatmaid-Signature header (an HMAC-SHA256 signature in the form t=<unix time>,v1=<hex>) and an X-Chatmaid-Event header. This Express server verifies the signature with the SDK, passes the text to your agent and replies in the same chat:

import express from "express";
import { Chatmaid, verifyWebhook, WebhookVerificationError } from "@chatmaid/sdk";

const cm = new Chatmaid({ apiKey: process.env.CHATMAID_API_KEY! });
const app = express();

// Replace with a call to your model (Claude, GPT, Gemini...).
async function askAgent(text: string): Promise<string> {
  return `You said: ${text}`;
}

app.post(
  "/webhooks/chatmaid",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    // This example only handles incoming messages; acknowledge other events.
    if (req.header("x-chatmaid-event") !== "message.received") {
      res.status(200).end();
      return;
    }

    try {
      const event = verifyWebhook({
        body: req.body,
        signature: req.header("x-chatmaid-signature"),
        secret: process.env.CHATMAID_WEBHOOK_SECRET!,
        // Retries reuse the first attempt's signature, so allow 10 minutes.
        toleranceSeconds: 600,
      });

      // Acknowledge quickly; deliveries without a 2xx are retried.
      res.status(200).end();

      if (event.event !== "message.received") return;

      const { messageId, from, to, content, isGroup } = event.data;
      if (!content || isGroup) return; // answer direct chats only

      const reply = await askAgent(content);

      await cm.messages.send({
        fromPhoneId: to, // the connected number that received the message
        to: from,
        content: reply,
        idempotencyKey: `reply-${messageId}`,
      });
    } catch (err) {
      if (err instanceof WebhookVerificationError) {
        res.status(400).end();
        return;
      }
      console.error(err);
    }
  },
);

app.listen(3000);

The bot answers direct chats only. To answer in a group, check groupId against a list of groups you chose and send to groupId.

A few things to know before you rely on it:

  • Retries. A delivery that doesn't get a 2xx response within 30 seconds is retried after 1 minute and again about 5 minutes later, so the same event can arrive more than once. The reply above uses the inbound messageId as its idempotency key, so a repeat never sends a second answer.
  • Live numbers and a live key. Inbound messages exist only for live connections, and the sandbox inbound list is always empty. Test the receiving side with a live number, and set CHATMAID_API_KEY on this server to a live key (sk_live_): a test key cannot send from a live number.
  • Media. type tells you what arrived: text, image, video, audio, document, sticker, location, contact or other. For media, content holds the caption if there is one; the file itself is not passed through the API.
  • Human takeover. Subscribe to message.outgoing to learn when someone types into the connected phone's WhatsApp directly, so your agent can step back from that conversation. Every event goes to the same webhook URL, and as of October 2026 the published SDK (0.3.0) does not recognize this one: verifyWebhook throws on it. That's why the handler above checks the X-Chatmaid-Event header first. To act on message.outgoing, verify it with the HMAC check in the webhooks docs.

For signature checks in other languages, see the webhook guide. To keep context across messages, read adding conversation memory to a WhatsApp AI agent.

Skip the code: the WhatsApp MCP server

If you work in Claude Code, Claude Desktop or Cursor, the open-source Chatmaid MCP server (@chatmaid/mcp) gives your agent WhatsApp tools without any code. In Claude Code, run this in your project:

claude mcp add chatmaid \
  --env CHATMAID_API_KEY=sk_test_xxx \
  -- npx -y @chatmaid/mcp

The server gives your agent eleven tools to send to numbers and groups, list your numbers and groups, read the messages your number receives (live keys only) and check delivery status. Setup for Claude Desktop, Cursor and Windsurf, and the full tool list, are in the WhatsApp MCP server guide.

Then ask in plain language: "Send a WhatsApp message to +15557654321 saying the build passed." The agent can look up your sender number with list_phone_numbers, then call send_message. Start with a test key while you check how the agent behaves, then switch to a live key.

Prefer not to install anything? Point the agent at the full documentation in one Markdown file, https://www.chatmaid.net/llms-full.txt, and it can write the HTTP calls itself. The MCP docs have more setup detail.

What your agent can send

A message can carry text, media or both. Media goes in mediaUrls as public HTTPS links (plain http:// is rejected), and Chatmaid fetches each file and sends it.

TypeFormats
TextUp to 4,096 characters
ImagesJPEG, PNG, WebP
VideoMP4, 3GP
AudioMP3, OGG, M4A, AAC, AMR
DocumentsPDF, Word, Excel, PowerPoint, plain text

With one image, video or document, the text becomes its caption. Audio can't carry a caption, so send the text in its own request. With several files, the text goes out first as its own message, then the files, which can arrive in any order. So your agent can send a report as a PDF, a chart as an image or an audio summary, not only plain text. Interactive buttons and list messages are not supported as of October 2026.

Frequently asked questions

Do I need a WhatsApp Business API account or Meta verification?

No. Chatmaid links your number like WhatsApp Web does, so there is no Meta Business portfolio, business verification or template approval. You scan a QR code and send.

Can my agent use my personal WhatsApp number?

Yes. A personal number or a separate one both work, and you stay logged in on your phone. A separate number is a good idea for production, so your own chats and your agent's chats don't mix.

Will WhatsApp ban the number?

It's very unlikely with normal use. Restrictions happen when an account behaves like spam, such as unsolicited bulk messages to people who don't have you saved. Keep your agent to people who expect to hear from it.

How much does it cost?

The sandbox is free. Live sending is a flat $7.99 a month from one number, with no per-message charges. See pricing for all Chatmaid plans.

Can the agent post in WhatsApp groups?

Yes. Call GET /v1/groups with your sender number as the fromPhoneId query parameter (or use the list_groups MCP tool), then pass a group ID, such as 120363043211234567@g.us, as to. The connected number must be a member and allowed to post. Inbound group messages include the same ID in groupId, so the agent can reply to the group.

Can the agent understand photos or voice notes people send?

Not through Chatmaid alone. The message.received webhook tells you the type (image, audio, document and so on) and puts any caption in content, but the file itself is not passed through the API, so the agent sees only the caption.