Bird

Send WhatsApp with FastAPI

Send a managed template from a server route, retain its message ID and inspect the message status. This example uses the existing Bird Python SDK.

1. Prepare the workspace

Use a funded Bird workspace with WhatsApp sending enabled and a WhatsApp number you control. This recipe sends the managed bird_otp template to that number. Its literal code is demonstration content; use Verify to authenticate a customer. Recipient-permission requirements still apply. Complete the first-message setup and create a server-side API key before running the application.

2. Create the application

Use Python 3.10 or later and a virtual environment:
Przykład kodu
mkdir bird-whatsapp-fastapi
cd bird-whatsapp-fastapi
python3 -m venv .venv
source .venv/bin/activate
python -m pip install messagebird-sdk "fastapi[standard]"
export BIRD_API_KEY="YOUR_API_KEY"
export BIRD_TEST_PHONE="YOUR_WHATSAPP_NUMBER"

3. Add the routes

Save this as main.py. A lifespan context opens one async SDK client and closes it during shutdown. FastAPI validates the request parameters.
Przykład kodu
import logging
import os
from contextlib import asynccontextmanager
from uuid import UUID

from bird import AsyncBird, APIError
from fastapi import FastAPI, Header, HTTPException

sender = os.environ["BIRD_TEST_PHONE"]


@asynccontextmanager
async def lifespan(app: FastAPI):
    async with AsyncBird(api_key=os.environ["BIRD_API_KEY"]) as client:
        app.state.bird = client
        yield


app = FastAPI(lifespan=lifespan)


@app.post("/api/whatsapp/messages", status_code=202)
async def send(idempotency_key: str = Header()):
    try:
        UUID(idempotency_key)
    except ValueError:
        raise HTTPException(400, "Supply an Idempotency-Key UUID.")
    try:
        message = await app.state.bird.whatsapp.send(
                to=sender,
                template="bird_otp",
                language="en",
                components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
                options={"idempotency_key": idempotency_key},
        )
        return {"id": message.id, "status": message.status}
    except APIError:
        logging.exception("Bird send failed")
        raise HTTPException(503, "Could not confirm the send. Inspect the original attempt before retrying.")


@app.get("/api/whatsapp/messages")
async def read(id: str):
    try:
        message = await app.state.bird.whatsapp.get(id)
        return {"id": message.id, "status": message.status}
    except APIError:
        logging.exception("Bird read failed")
        raise HTTPException(503, "Could not read the message.")

4. Run the example

Start the server:
Przykład kodu
fastapi dev main.py --host 127.0.0.1
In a second terminal, create an operation ID once and send the fixed example:
Przykład kodu
export DEMO_OPERATION_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
curl -i -X POST http://127.0.0.1:8000/api/whatsapp/messages \
  -H "Idempotency-Key: $DEMO_OPERATION_ID"
Keep DEMO_OPERATION_ID with this attempt. Do not regenerate it while resolving an uncertain response. The SDK infers the API region from the key.

Inspect the result and recover

The POST returns 202 with the message id and current status. Acceptance precedes delivery. Copy the ID and read it without sending again:
Przykład kodu
curl "http://127.0.0.1:8000/api/whatsapp/messages?id=YOUR_MESSAGE_ID"
The read returns the recorded status. Follow WhatsApp events for asynchronous outcomes, and use the product log or event guide to investigate a missing receipt. A missing receipt does not establish delivery or failure.
  • A missing Idempotency-Key produces 422; a malformed value produces 400 in this application before it calls Bird.
  • If the send fails, the application returns 503 and leaves the result unresolved. Inspect the SDK error in the server terminal and the product log. Correct authentication, sender, destination, template or balance errors before retrying.
  • For an uncertain response, retain the same key and identical payload. Read the idempotency guide before retrying; a new key creates a new operation, and response retention does not provide an indefinite exactly-once guarantee.
A managed-template test does not configure your business-owned sender. Follow number setup, template authoring and the customer-service reply rules before connecting your own conversations.
These handlers use a fixed test recipient and bind the development server to loopback. Before publishing, authorize each customer action, associate message IDs with the owning account before permitting reads, and apply your application's abuse limits. The booking and workflow are sample application logic.

Continue the integration