What you'll build: A WhatsApp number that replies with an AI agent powered by Google Gemini. You'll use Gemini (via the Gemini CLI or Gemini Code Assist in your IDE) as your pair-programming copilot to scaffold, debug, and extend the code, so Gemini is both the agent's runtime brain and the assistant building it.
WhatsApp user
β message
βΌ
Meta WhatsApp Cloud API ββwebhook POSTβββΊ Your server (Flask)
β² β
β send reply (Graph API) βΌ
βββββββββββββββββββββββββββββββββββ Gemini API (the "brain")
Two moving parts:
Two hats for Gemini: at runtime, the Gemini API generates replies to WhatsApp users. While building, you use Gemini as your coding copilot β the Gemini CLI (npm install -g @google/gemini-cli
, then run gemini
) in your terminal, or Gemini Code Assist inside VS Code / JetBrains. You describe what you want in plain English, and it writes, explains, and fixes the code below.
npm install -g @google/gemini-cli
) or
Ask Gemini:"Explain what a Gemini API key can access and how to store it safely as an environment variable."
Ask Gemini:"Walk me through creating a permanent WhatsApp access token with a System User in Meta Business Suite."
mkdir whatsapp-gemini-agent && cd whatsapp-gemini-agent
python -m venv .venv && source .venv/bin/activate
pip install flask google-genai requests python-dotenv
Create a .env
file:
GEMINI_API_KEY=your_gemini_key
WHATSAPP_TOKEN=your_whatsapp_access_token
WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id
VERIFY_TOKEN=pick_any_random_string
Ask Gemini:"Generate a .gitignore for a Python project and make sure .env is excluded."
Meta requires two things from your endpoint:
/webhook
β a one-time verification handshake./webhook
β where inbound messages arrive.Create app.py
:
import os
import requests
from flask import Flask, request
from google import genai
from dotenv import load_dotenv
load_dotenv()
GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
WHATSAPP_TOKEN = os.environ["WHATSAPP_TOKEN"]
PHONE_NUMBER_ID = os.environ["WHATSAPP_PHONE_NUMBER_ID"]
VERIFY_TOKEN = os.environ["VERIFY_TOKEN"]
app = Flask(__name__)
client = genai.Client(api_key=GEMINI_API_KEY)
SYSTEM_PROMPT = (
"You are a friendly, concise WhatsApp assistant. "
"Keep replies short and clear β this is a chat app, not email."
)
def ask_gemini(user_text: str) -> str:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=user_text,
config=genai.types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT),
)
return response.text
def send_whatsapp_message(to: str, body: str) -> None:
url = f"https://graph.facebook.com/v22.0/{PHONE_NUMBER_ID}/messages"
headers = {"Authorization": f"Bearer {WHATSAPP_TOKEN}"}
payload = {
"messaging_product": "whatsapp",
"to": to,
"type": "text",
"text": {"body": body},
}
requests.post(url, headers=headers, json=payload, timeout=20)
@app.get("/webhook")
def verify():
if (request.args.get("hub.mode") == "subscribe"
and request.args.get("hub.verify_token") == VERIFY_TOKEN):
return request.args.get("hub.challenge"), 200
return "Forbidden", 403
@app.post("/webhook")
def incoming():
data = request.get_json()
try:
change = data["entry"][0]["changes"][0]["value"]
message = change["messages"][0] # inbound message
sender = message["from"] # user's phone number
text = message["text"]["body"] # message text
reply = ask_gemini(text)
send_whatsapp_message(sender, reply)
except (KeyError, IndexError):
pass
return "OK", 200
if __name__ == "__main__":
app.run(port=5000)
Ask Gemini:"Paste this file and explain each function line by line, then suggest error handling I'm missing."
Run the server, then tunnel it:
python app.py # terminal 1
ngrok http 5000 # terminal 2 β copy the https URL
In Meta's WhatsApp β Configuration:
https://<your-ngrok-id>.ngrok.io/webhook
VERIFY_TOKEN
from your .env
Ask Gemini:"My webhook verification is returning 403. Here's my code and the ngrok logs β what's wrong?"
Send a WhatsApp message from your registered number to the test number. Within a second or two you should get a Gemini-generated reply.
If nothing comes back, ask Claude to help you read the logs:
Ask Gemini:"The webhook receives a POST but no reply is sent. Here's the JSON payload and my server log β trace where it breaks."
A chatbot answers. An agent takes actions. Gemini supports function calling β you declare tools, and the model decides when to call them.
Example: give the agent a check_reservation
tool.
def check_reservation(confirmation_code: str) -> dict:
return {"code": confirmation_code, "status": "confirmed", "checkin": "2026-08-14"}
def ask_gemini_agent(user_text: str) -> str:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=user_text,
config=genai.types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPT,
tools=[check_reservation], # SDK auto-generates the schema from the function
),
)
return response.text
The Gemini SDK reads the function's signature and docstring to build the tool schema, calls it when the user asks about a reservation, and folds the result into its reply.
Ask Gemini:"Add a second tool that cancels a reservation, and add conversation memory so the agent remembers earlier messages in the same chat."
Good next tools to build with Claude's help:
contents
.X-Hub-Signature-256
header200
fast and process Gemini calls in a
Ask Gemini:"Write theX-Hub-Signature-256
verification middleware for my Flask app, and refactor the Gemini call to run in a background thread so the webhook returns 200 immediately."
gemini "review app.py for security issues"
).| Piece | Service | Docs | |---|---|---| | Inbound + outbound messages | WhatsApp Cloud API | developers.facebook.com/docs/whatsapp/cloud-api | | Agent reasoning + tools | Gemini API | ai.google.dev/gemini-api/docs | | Your coding copilot | Gemini CLI / Code Assist | github.com/google-gemini/gemini-cli Β· codeassist.google |
Model note: gemini-2.5-flash is fast and cheap for chat; switch to gemini-2.5-pro for harder reasoning. Check Google's model list for the latest IDs.