WhatsApp API with Twilio vs Meta Cloud API: Setup and Cost

October 15, 2026 • 11 min read

Home / Blog / WhatsApp API with Twilio vs Meta Cloud API: Setup and Cost
WhatsApp API with Twilio vs Meta Cloud API: Setup and Cost | Connect a WhatsApp number to Twilio in 15 minutes, answer with a free model, and compare per-message cost against Meta Cloud API with the October 2026 rates

For most products that are about to send their first WhatsApp message, the right first move is Twilio, and the right second move, on the same day, is to start Meta's business verification so you can leave Twilio later.

 

I'm a Twilio Champion, so take the first half with the salt it deserves. The second half is the part that saves money, and it is the part most tutorials skip, because they show you one path and stop when the first message arrives.

 

This post covers both paths, the setup I use from the Twilio console, the webhook code from the demo repo of my video, and what each message costs with Meta's rate card effective October 1, 2026.

 

Two paths to the same WhatsApp

 

On paper the direct path is shorter. Your code talks to Meta Cloud API and that's it, two boxes in the diagram. In practice there is a lot in the middle:

 

  • You own a Meta app, and Meta asks you for business information, compliance details and a list of other things before it lets you send at volume.
  • You manage your own access tokens and their refresh. If you don't handle the refresh, the integration disconnects, every one, two or 24 hours depending on the token.
  • Billing goes to a credit card. Meta shows a dashboard with the month's consumption and charges when the cycle closes.

 

The Twilio path has one more box: your code talks to Twilio, and Twilio talks to Meta through the APIs Meta has for providers and partners. What you get for that extra box is one API for WhatsApp, SMS and voice, Twilio handling the token and refresh part, and a prepaid balance that Twilio consumes as you send. There is no big minimum to load, and the balance also covers the phone number and the rest of the telephony. What you pay for it is a fee on every message, which I break down below.

 

Setting up a WhatsApp sender from the Twilio console

 

The order matters here, because a fresh account will not let you do much.

 

  1. Create the account at twilio.com (start for free). A new account comes with a balance but in trial mode, which only allows specific tests. Go to Billing, add a payment method and load funds. That takes you out of trial.
  2. Buy a number, or bring one you already have on another platform by porting it, so it shows in your inventory as if it was native.
  3. Have a Meta business portfolio. Mine has a page and is registered in Business Suite, but it has no WhatsApp numbers and no WhatsApp accounts. That is on purpose: everything else happens from the Twilio console.
  4. Go to Messaging > Overview > WhatsApp, and Create new sender. Pick the Twilio number and continue.
  5. Continue with Facebook. This is Twilio's Self Sign-up, the part that lets you connect without calling Meta's API yourself. You log in with the Meta account, paste the number, and Meta sends a verification code.
  6. Where is that code, if the number is a virtual Twilio number with no WhatsApp app behind it? Twilio shows it to you in the console. Copy, paste, next.
  7. Create a new WhatsApp Business Account inside your portfolio, give it a name and the right category, and confirm the permissions Twilio asks for: manage the accounts, manage conversations and log events on your behalf.

 

Reload the WhatsApp senders page. The sender shows offline for a few minutes and then goes online, ready to send and receive. From no number to the first message, the whole thing takes 10 to 15 minutes.

 

Two things on that sender page are worth a second look. The first is the throughput: it says 80 messages per second as base output, which is half a lie, because what you actually get depends on your compliance status and on how much business information you shared with Twilio and Meta. Plan with it as a ceiling, not as a promise. The second is that everything else lives there too: the webhook for incoming messages, the WhatsApp calling configuration, and the profile (display name, website, address, email, description) that people see in WhatsApp. None of it needs a trip to Meta's console.

 

The webhook

 

The demo repo is a small Node 20 app with Express 5, the Twilio SDK and a free model from OpenRouter (google/gemma-4-31b-it:free by default). The purpose is a functional test: you send a WhatsApp message, the app generates the answer and sends it back. Locally I expose it with ngrok and point the sender's incoming webhook to PUBLIC_URL/whatsapp.

 

Three decisions in that webhook are the ones I would keep in any production version.

 

Validate the signature against the public URL. Twilio signs the exact URL configured in the console, which is the ngrok one, not the one Express sees behind the tunnel. If you rebuild the URL from the request you get a 403 on every message.

 

webhook.post('/whatsapp', express.urlencoded({ extended: false }), (req, res) => {
  // Twilio signs the public URL (the ngrok one), not the one Express sees.
  const url = PUBLIC_URL + req.originalUrl;
  const signature = req.header('X-Twilio-Signature');

  if (!signature) {
    console.warn('[403] Request without X-Twilio-Signature header');
    return res.status(403).send('Forbidden');
  }
  if (!twilio.validateRequest(TWILIO_AUTH_TOKEN, signature, url, req.body)) {
    console.warn(`[403] Invalid signature for ${url} (stale PUBLIC_URL?)`);
    return res.status(403).send('Forbidden');
  }

  // Answer right away: Twilio cuts the webhook at 15 seconds.
  res.type('text/xml').send('<Response></Response>');

  handleMessage(req.body).catch((err) => console.error('[ERR] Unexpected:', err.message));
});

 

Answer first, think after. Twilio cuts the webhook at 15 seconds, and a free model on a busy day can take longer than that. So the handler returns an empty TwiML immediately and the reply goes out later through the REST API. The same function also handles the two failures that happen in real life: the model fails, or the send fails, and in both cases the person gets a fallback message instead of silence.

 

async function handleMessage({ From, Body = '', ProfileName, NumMedia = '0' }) {
  const text = Body.trim();
  let reply;
  if (Number(NumMedia) > 0 && !text) {
    reply = ONLY_TEXT;
  } else {
    try {
      addMessage(From, 'user', text);
      reply = await generateReply(getHistory(From), ProfileName);
      addMessage(From, 'assistant', reply);
    } catch (err) {
      reply = FALLBACK;
    }
  }

  try {
    await client.messages.create({ from: TWILIO_WHATSAPP_FROM, to: From, body: reply });
  } catch (err) {
    if (reply === FALLBACK) return;
    await client.messages
      .create({ from: TWILIO_WHATSAPP_FROM, to: From, body: FALLBACK })
      .catch(() => {});
  }
}

 

Keep the history short and well formed. For the demo the conversation lives in memory, capped at the last 10 messages, always starting with a user message and with roles alternating, which is what the chat completion APIs expect. This does not survive a restart and does not scale past one instance, and the file says so. In production it goes to a database.

 

const MAX_MESSAGES = 10;
const conversations = new Map(); // From -> [{ role, content }]

export function addMessage(from, role, content) {
  const history = getHistory(from);
  // If the previous message got no answer (the model failed), replace it
  // to keep user/assistant alternating.
  if (history.at(-1)?.role === role) history.pop();
  history.push({ role, content });
  while (history.length > MAX_MESSAGES || history[0]?.role === 'assistant') history.shift();
  conversations.set(from, history);
}

 

The behavior of the assistant lives in a prompts/system.md file, so you change what it says without touching code. A natural next step is loading that file with the business information, opening hours for example, so it answers the first questions and hands off to a human when the case needs it.

 

Free models on OpenRouter have limits, around 20 requests per minute plus a daily cap, and sometimes they are just saturated. If you see 429s, switch OPENROUTER_MODEL or use openrouter/free, which picks an available one.

 

The 24-hour window and templates

 

Once a person writes to you, you have 24 hours to answer freely, and your agent can reply as much as it wants inside that window. Outside of it, WhatsApp returns error 63016 and you need an approved template.

 

If you want to be the one starting the conversation, templates are the only way. In the Twilio console you create one with a name, a language and a content type (quick reply, catalog, carousel, depending on the use case), the body, and variables if you need them. The same template can serve WhatsApp and other channels like Messenger or Instagram.

 

Then it goes to review, by Twilio on one side and by Meta on the other, mostly to check compliance and that the message matches your use case and what the company does. Meta is much stricter with outbound messages than with replies to inbound ones, so expect some back and forth. The good part is that once a template is approved you can use it for months, even years.

 

What each message costs

 

This is the part that decides between a native path like Meta and an intermediary that gets you to market earlier for a markup. Prices in USD, from Meta's rate card effective October 1, 2026 and Twilio's published pricing:

 

Message typeArgentina, MetaArgentina, via TwilioMexico, MetaMexico, via Twilio
Marketing0.06180.06680.03970.0447
Utility0.02600.03100.00850.0135
Authentication0.02600.03100.00850.0135
Service (from message 1,001)0.02600.03100.00850.0135
Service (first 1,000 per month)00.00500.005
Inbound message00.00500.005

 

A few things in that table matter more than they look.

 

Twilio adds USD 0.005 per message, inbound or outbound, even when Meta charges nothing. So the first 1,000 service messages of the month, which are free on Meta's side, cost you USD 5 through Twilio, and from message 1,001 you pay both. At 10,000 messages a month the markup is around USD 50.

 

The country changes everything. Marketing templates in Argentina cost more than six cents each, and the same message in Mexico is much cheaper. When you run outbound campaigns, segmenting by country is also a cost decision.

 

And the one that catches people building agents: service messages are counted per message, not per conversation. If your agent answers each user message with three, four or five bubbles, every bubble counts, and the 1,000 free messages of the month are gone in weeks or days depending on your volume. Answering in one well written message is cheaper than any provider decision you make.

 

The hybrid path I use with clients

 

This is my recommendation for anyone integrating WhatsApp programmatically or with AI models, and it is what I do with clients.

 

Go out with Twilio. Buy the number and send the first message on day one, 10 to 15 minutes as shown above. Iterate with real users, adjust the templates from Twilio, which helps a lot with that part and with improving delivery while the product is still changing.

 

Start Meta's business verification the same day. It is a long process, about two weeks if everything goes well and it is approved on the first attempt. If it isn't, there is back and forth, and that is exactly when you don't want to be waiting to send your first WhatsApp message.

 

Move when the volume justifies it. Once the business is verified, move the approved templates (the same ones you had in Twilio) to Meta and switch to the direct path to stop paying the per-message markup.

 

To be fair to the other side: if you already have a verified portfolio and a team comfortable with token rotation and a Meta app, going direct from day one is perfectly reasonable, and that's fine. The hybrid path is for when time to market is the constraint.

 

Where I would put the attention is not the USD 0.005. It is writing down, before launch, the monthly volume at which you leave, because a markup nobody revisits is the one that ends up costing real money, and the architecture should keep the agent logic away from the provider so that moving is changing one layer and not rewriting the system.

 

-Gonza

7
Node.js,  Twilio,  whatsapp-api,  OpenRouter,  Meta Cloud API
Published on October 15, 2026

Want an AI agent like this on your WhatsApp? Take a look at my WhatsApp AI agent services.

Want something like this running in your business? Let's talk it through.

Book a free 30-minute call

Already know what you need? Start with a communication audit.

Related posts

AI voice agent human handoff with Twilio ConversationRelay

October 02, 2026
An AI voice agent can answer every frequently asked question a dealership gets, with the right tone and the right hours, and still lose the... Read more

Claude Agent Skills for a WhatsApp AI agent: production setup

September 11, 2026
The first thing my agent did on camera was fail a tool call. It called create_client with arguments my function did not accept, got an... Read more

How to Run a Free AI Agent on Your VPS with Hermes

August 18, 2026
I have an agent running 24/7 on a VPS I was already paying for and barely using. It sends me an AI and software news... Read more

AI WhatsApp agent with Claude and Twilio: no-code build walkthrough

July 08, 2026
IntroductionEvery minute your business takes to respond to a WhatsApp message is a minute where the lead is still talking to someone else. The typical... Read more