Texa DeskDevelopers

Texa AI in your product

A WhatsApp bot in 15 minutes

Your server already talks to Meta's Cloud API; Texa AI answers your customers through it.

No code at all? Connect your WhatsApp number in Texa Desk (Settings → Inboxes → Add inbox → WhatsApp) and switch Texa AI on for it under Texa AI → Agent and inboxes. This guide is for when your own server already talks to WhatsApp through Meta's Cloud API and you want Texa AI to answer.

What you need

  • A WhatsApp Business number on Meta's Cloud API: its phone number id and an access token. Meta must send its webhook to your server.
  • A Texa test key (texa_test_…), from Texa AI → Developers.
  • Python 3.10+ with fastapi, uvicorn and httpx.

The whole bot

Python
# bot.py: run with  uvicorn bot:app --port 8000
import os
import httpx
from fastapi import FastAPI, Request

TEXA = httpx.Client(base_url="https://api.texadesk.com/v1", timeout=90,
                    headers={"Authorization": f"Bearer {os.environ['TEXA_API_KEY']}"})
META = httpx.Client(base_url="https://graph.facebook.com/v21.0", timeout=30,
                    headers={"Authorization": f"Bearer {os.environ['META_TOKEN']}"})
PHONE_ID = os.environ["META_PHONE_NUMBER_ID"]
conversations: dict[str, str] = {}      # customer's number -> Texa conversation (use your DB)
app = FastAPI()


def send_whatsapp(to: str, text: str) -> None:
    META.post(f"/{PHONE_ID}/messages", json={
        "messaging_product": "whatsapp", "to": to, "type": "text", "text": {"body": text}})


def conversation_for(number: str, name: str | None) -> str:
    if number not in conversations:
        r = TEXA.post("/conversations", headers={"Idempotency-Key": f"wa-open-{number}"},
                      json={"channel": "whatsapp", "customer_ref": f"wa:{number}",
                            "first_name": name})
        conversations[number] = r.json()["conversation_id"]
    return conversations[number]


@app.post("/whatsapp")                  # Meta's webhook (verify its signature too)
async def incoming(request: Request):
    body = await request.json()
    for entry in body.get("entry", []):
        for change in entry.get("changes", []):
            value = change.get("value", {})
            names = {c["wa_id"]: c["profile"]["name"] for c in value.get("contacts", [])}
            for m in value.get("messages", []):
                if m.get("type") != "text":
                    continue
                number = m["from"]
                cid = conversation_for(number, names.get(number))
                r = TEXA.post(f"/conversations/{cid}/messages",
                              json={"message_id": m["id"], "text": m["text"]["body"]})
                if r.status_code == 409 and r.json()["error"]["code"] == "conversation_closed":
                    conversations.pop(number)          # it ended: start a new one
                    cid = conversation_for(number, names.get(number))
                    r = TEXA.post(f"/conversations/{cid}/messages",
                                  json={"message_id": m["id"], "text": m["text"]["body"]})
                for event in r.json().get("events", []):
                    if event["type"] == "reply":
                        send_whatsapp(number, event["text"])
    return {"ok": True}

Run it, point Meta's webhook at https://your-server/whatsapp, and message your number.

Why it is safe to retry

Meta sends a webhook again when your server is slow. Passing Meta's message id as message_id means Texa answers a repeat with the first answer ("replayed": true) and never replies twice. Opening with an Idempotency-Key means one number gets one conversation, however often the first message arrives.

Things WhatsApp decides

  • The 24-hour window. You can send a free-form message only within 24 hours of the customer's last one. After that, Meta takes only an approved template. A reply that arrives later (see below) may need one.
  • Replies that come later. A voice note or photo is understood before Texa AI answers, so its reply can come after your request has returned. Subscribe a webhook to conversation.reply and send those too. Use reply_id to make sure you send each reply once.
  • People taking over. When an event is a handover, Texa AI stops replying. With handover to Texa Desk, your team answers there, and each answer reaches you as the conversation.human_message webhook: send its text to the customer on WhatsApp. See handing over.
  • Voice notes and photos. Upload the file with POST /v1/media (with the conversation_id), then send a message whose attachments holds {"media_id": ...}.

Going live

Make a live key, swap it in, and nothing else changes. Everything is counted against your prepaid credits. Watch it in Texa Desk under Texa AI → Credits and billing.