Updated on October 28, 2026
To connect ChatGPT to the official WhatsApp Business API, you receive the customer’s message through the webhook, send that conversation’s history to OpenAI’s Responses API (POST https://api.openai.com/v1/responses) and deliver the generated text back with a Cloud API send call. Free-form replies can only go out inside the 24-hour window opened by the customer, and Meta’s policy requires a clear path to a human agent. Using AI to support your own business’s customer service is allowed; offering a “general-purpose ChatGPT” as a product on WhatsApp falls under separate rules in Meta’s terms.
This tutorial is the hands-on part: full Node.js code, per-contact history, window checks, human handoff and cost. The general architecture (queue, deduplication, states) is covered in WhatsApp Cloud API: what it is, how it works and what it costs and in the bot tutorials linked below.
What you need before you start
- A phone number connected to the official API, with a webhook set up. If you are not there yet, start with the Cloud API guide linked above.
- An OpenAI API key (
OPENAI_API_KEY), created in the OpenAI dashboard. - Node.js 18 or later (the example uses native
fetchand the officialopenaiSDK). - A database to store conversation history and state. The example keeps everything in memory for simplicity.
Nothing on the WhatsApp side changes because you use ChatGPT: the Cloud API has no idea which model wrote the text. What governs the use case is Meta’s terms, covered further down.
The flow in 5 steps
| Step | What happens | Where |
|---|---|---|
| 1 | The customer sends a message and Meta fires the webhook | Meta → your server (directly or through a provider) |
| 2 | Your server returns 200 right away and queues the event | Webhook |
| 3 | The worker loads the history for that wa_id and decides whether the bot replies | Database + router |
| 4 | The worker calls the Responses API with instructions and history | OpenAI |
| 5 | The text goes back to the customer via POST /messages | Cloud API |
Step 2 is where rushed integrations break. The model can take a few seconds to answer, and the webhook cannot wait. On Vectalk, for example, the endpoint must return HTTP 200 within 20 seconds or the event is resent, according to the webhook documentation. Retries without deduplication become duplicate replies and double cost.
Want to try it while you read? Create a Vectalk account and send your first message through the official API. Create free account →
Responses API or Chat Completions?
OpenAI has two APIs for generating text. The text generation guide recommends the Responses API for new applications, and that is what we use here. The minimal call is:
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL",
"instructions": "You are the assistant for Example Store. Answer in up to 3 sentences.",
"input": "How long does shipping to Austin take?"
}'
Three parameters matter for a WhatsApp bot:
instructions: the bot’s behavior (tone, scope, what it will not do). It applies only to the current request, so send it every time.input: the question, or a list ofuserandassistantmessages with the history.model: pick one from OpenAI’s models page. Keep it in an environment variable so you can switch without touching code.
In the official SDKs, the final text is in response.output_text. Do not read output[0] directly: the output can contain other items before the text, such as tool calls.
Per-contact history: three options
ChatGPT does not remember the conversation on its own. With every message, you have to supply the context. OpenAI’s conversation state guide describes three options:
| Option | How it works | Where history lives | When to use it |
|---|---|---|---|
| Manual history | You send the latest messages in input | Your database | Recommended default for customer service: you control size, retention and auditing |
previous_response_id | You send only the new message plus the previous response ID | OpenAI (response objects are kept for 30 days by default) | Prototypes; tokens from earlier responses are still billed as input |
| Conversations API | You create a conversation and pass its ID in conversation | OpenAI, without the 30-day limit | When you want durable state on OpenAI’s side |
For customer service, manual history is usually the easiest choice to justify: the data stays in your database, you decide how long to keep it, and you can set store: false so OpenAI does not save the response. The history key is the customer’s wa_id (the from field of the incoming message).
If the conversation includes personal data, privacy law applies to what you send to the AI provider. In Brazil that is the LGPD (Lei Geral de Proteção de Dados, Brazil’s data protection law); see LGPD and WhatsApp. This article is not legal advice.
Full Node.js code
The example uses Express, the openai SDK and sending through Vectalk, which accepts the same payload as the Cloud API. With an instance token, the sender number is resolved automatically; with an organization token, include phone_number_id in the body, per the send reference. If you integrate directly with Meta, change the URL to https://graph.facebook.com/<version>/<PHONE_NUMBER_ID>/messages and use your Meta token.
npm install express openai
import express from 'express';
import OpenAI from 'openai';
const openai = new OpenAI(); // reads OPENAI_API_KEY from the environment
const MODEL = process.env.OPENAI_MODEL;
const SEND_URL = 'https://apis.vectalk.com.br/api/messages';
const TOKEN = process.env.VECTALK_TOKEN; // instance token
const WINDOW_MS = 24 * 60 * 60 * 1000;
const MAX_TURNS = 20;
const INSTRUCTIONS = `You are Example Store's virtual assistant on WhatsApp.
- Answer in English, in at most 3 short sentences.
- Only talk about the store's orders, shipping, returns and products.
- Never make up prices, deadlines or policies: use only the data provided.
- If you don't know, if the customer asks for a person, or the topic is a
complaint, cancellation or billing dispute, reply exactly: HANDOFF`;
const app = express();
app.use(express.json());
const queue = [];
const processed = new Set(); // handled wamids (use Redis/a database in production)
const chats = new Map(); // wa_id -> { state, history, lastInbound }
// 1. Webhook: queue and return 200 immediately
app.post('/webhook', (req, res) => {
for (const entry of req.body.entry ?? []) {
for (const change of entry.changes ?? []) {
for (const msg of change.value?.messages ?? []) queue.push(msg);
}
}
res.sendStatus(200);
});
// 2. Send to WhatsApp
async function send(body) {
const r = await fetch(SEND_URL, {
method: 'POST',
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ messaging_product: 'whatsapp', ...body }),
});
if (!r.ok) throw new Error(`Send failed: ${r.status} ${await r.text()}`);
return r.json();
}
// 3. Call OpenAI (Responses API)
async function askModel(history) {
const response = await openai.responses.create(
{
model: MODEL,
instructions: INSTRUCTIONS,
input: history, // [{ role: 'user' | 'assistant', content: '...' }]
max_output_tokens: 300, // short answers cost less
store: false, // do not store the response at OpenAI
},
{ timeout: 15000 },
);
return response.output_text.trim();
}
// 4. Worker
async function handle(msg) {
if (processed.has(msg.id)) return;
processed.add(msg.id);
const c = chats.get(msg.from) ?? { state: 'bot', history: [], lastInbound: 0 };
c.lastInbound = Number(msg.timestamp) * 1000;
chats.set(msg.from, c);
if (c.state !== 'bot') return; // a human took over: the bot stays quiet
if (msg.type !== 'text') {
await send({ to: msg.from, type: 'text', text: { body: 'I can only read text here. Could you type your question?' } });
return;
}
await send({ status: 'read', message_id: msg.id }); // mark as read
c.history.push({ role: 'user', content: msg.text.body });
c.history = c.history.slice(-MAX_TURNS);
let text;
try {
text = await askModel(c.history);
} catch (e) {
console.error('OpenAI failed', e);
text = 'HANDOFF'; // when in doubt, hand off to a person
}
if (text === 'HANDOFF') return handOff(msg.from, c);
if (Date.now() - c.lastInbound > WINDOW_MS) return; // window closed: template only
await send({ to: msg.from, type: 'text', text: { body: text } });
c.history.push({ role: 'assistant', content: text });
}
async function handOff(waId, c) {
c.state = 'human';
await send({ to: waId, type: 'text', text: { body: "I'm bringing in someone from our team. They'll pick it up here shortly." } });
// Notify your support tool (ticket queue, CRM, Slack).
}
setInterval(async () => {
const msg = queue.shift();
if (!msg) return;
try { await handle(msg); } catch (e) { console.error(e); }
}, 50);
app.listen(3000, () => console.log('Bot listening on port 3000'));
To run it:
export OPENAI_API_KEY="sk-..."
export OPENAI_MODEL="model-chosen-from-the-models-page"
export VECTALK_TOKEN="your-instance-token"
node bot.mjs
To test locally, expose port 3000 with a tunnel and register the URL as your webhook; see how to test the WhatsApp webhook locally. If your webhook receives events directly from Meta, validate each request’s signature before processing it: see how to validate X-Hub-Signature-256.
While the model is thinking, the Cloud API lets you show “typing…” in the same call that marks the message as read by adding "typing_indicator": { "type": "text" }. According to the typing indicators documentation, it disappears when you reply or after 25 seconds, whichever comes first, and Meta recommends showing it only when you are actually going to reply.
What the example covers and what is missing
- Covers: instant webhook acknowledgement, deduplication by
wamid, per-contact history with a size cap, OpenAI timeout, window check and handoff state. - Missing for production: a persistent queue with retries, a database for history and state, send-rate control (throughput), per-conversation cost tracking and a dashboard so agents can hand the conversation back to the bot.
Complete bots without the AI part are in WhatsApp bot with Node.js and WhatsApp bot with Python.
The 24-hour window: ChatGPT cannot start a conversation
The model’s reply is a free-form message. The WhatsApp Business Messaging Policy allows replies without a template only inside the 24-hour customer service window, which opens and resets with every customer message. Outside it, only approved templates.
In practice:
- The bot is reactive. It cannot send “Hi, still interested?” two days later with ChatGPT-generated text. That is what templates are for.
- Check the window right before sending, not only when the message arrives. If the model stalled, the queue lagged or the worker crashed, the window can close mid-flight.
- Do not use the model to “fill in” a template. Templates have approved text and variables; variable values should come from your system’s data.
Handing off to a human
Meta’s policy allows automation inside the window but requires clear escalation paths, such as a handoff to a human agent in the chat, a phone number, email, web support or a support form. In the code above, three situations trigger a handoff:
- The model replies
HANDOFFbecause it does not know, the customer asked for a person or the topic is sensitive (the instructions say so). - The OpenAI call fails or times out.
- You can add your own triggers, such as keywords or a “Talk to an agent” button.
After the handoff, the conversation state becomes human and the bot stays silent until an agent hands it back. If the number uses coexistence, the agent can reply from the WhatsApp Business app, and messages sent from the app reach your system as echo events; you can use them to mark the conversation as human automatically.
What it costs: Meta + OpenAI
There are two separate bills.
WhatsApp (Meta). Bot replies inside the window are service messages. Since October 1, 2026, each number gets 1,000 free service messages per month, and billing starts at the 1,001st. For recipients in Brazil, the service rate is BRL 0.0350 per delivered message, according to Meta’s pricing page; other countries have their own rates on the same page. Templates are charged at their category’s rate whether sent inside or outside the window (in Brazil, marketing BRL 0.3217; utility and authentication BRL 0.0350); since October 1, 2026, that includes utility templates sent inside the window.
OpenAI. You pay for input and output tokens, at a price that depends on the model. Check OpenAI’s pricing page before estimating.
Three tweaks cut both bills at once:
- One reply per question. Three short messages count as three service messages once the free tier is used up. Ask the model for a single reply.
- Short history. Every old message sent in
inputis billed again as input tokens. Keep the last few turns and, if needed, a summary. max_output_tokens. Caps reply length and avoids long walls of text nobody reads on a phone.
What Meta allows: a “general-purpose ChatGPT” is a different case
The Meta Terms for WhatsApp Business Platform, updated on September 23, 2026, address “AI Providers” in section 4.7: providers and developers of AI or machine learning technologies, such as large language models, generative AI platforms and general-purpose AI assistants. They are prohibited from using the platform to offer those technologies when they are the primary functionality, rather than incidental or ancillary, as Meta determines. The terms allow exceptions in certain countries.
| Scenario | How the terms read |
|---|---|
| A store uses an OpenAI model to answer questions about orders | AI supporting the business’s own customer service; not what the clause targets |
| A clinic uses AI to book and confirm appointments | Same: AI is a tool of the service, not the product |
| A company offers “chat with an AI about anything” on WhatsApp | This is the AI Provider case; it depends on country rules |
Brazil is one of those countries. The AI Providers pricing page, updated on October 15, 2026, says that since March 11, 2026, AI Providers can send service messages to users with +55 numbers, at their own rates, always in response to the user (the customer service window still applies). For those messages, the status webhook shows the general_purpose_ai category. The same page lists other markets and dates. The rule applies only to AI Providers; pricing for other businesses does not change.
The terms also prohibit using platform data to build, train or improve AI models, with an exception for fine-tuning a model used exclusively by the business that is not used for other models. In practice: review how your AI provider handles retention and training, and do not reuse WhatsApp conversations to train third-party models.
How to do it with Vectalk
Vectalk is a proxy for the official Cloud API: your bot receives the same webhooks Meta sends and sends with the same payloads, changing only the base URL and the token. In the code above, that is the SEND_URL constant and VECTALK_TOKEN. You register the bot URL with POST /api/webhooks and can add more than one URL per instance, for example one for the bot and one for your customer service system. You don’t add a card at Meta: each delivered message is deducted from your prepaid Vectalk balance, and coexistence lets your human team reply from the app when the bot hands off. Get started at apps.vectalk.com.br/register and read the quickstart.
Frequently asked questions
Do I need Meta’s permission to use ChatGPT on WhatsApp?
For a customer service bot for your own business, the documentation we reviewed does not describe any extra registration: you use the official API as usual. If your product is a general-purpose AI assistant, read section 4.7 of the terms and the AI Providers page before launching.
Can I use the ChatGPT app or an off-the-shelf “ChatGPT for WhatsApp” tool?
This tutorial uses OpenAI’s API on your server, connected to the official WhatsApp API. Tools that connect WhatsApp by scanning a QR code use unofficial channels and risk bans; see official vs unofficial WhatsApp API.
Can the bot send the first message?
Not with model-generated text. To start or resume contact outside the 24-hour window, you need an approved template.
How do I make ChatGPT answer voice notes?
The webhook delivers the media ID. Download the file through the media API, transcribe it with the service of your choice and send the text to the model. Downloading is covered in how to download media customers send.
Responses API or Chat Completions?
Both work with WhatsApp, because the integration is plain HTTP. OpenAI recommends the Responses API for new applications, and that is what the example uses.
Official sources checked on October 28, 2026: OpenAI: text generation, OpenAI: conversation state, Meta Terms for WhatsApp Business Platform (updated September 23, 2026), WhatsApp Business Messaging Policy (updated September 23, 2026), AI Providers pricing (updated October 15, 2026), WhatsApp Business Platform pricing, Typing indicators, Vectalk: webhooks, Vectalk: sending messages.
Try the official WhatsApp API today
Create your account, generate a token and send your first message with the quick-start guide. Same payloads and webhooks as Meta's Cloud API.
