Skip to main content

Async Python client for the WhatsApp Business Cloud API

Project description

WhatsApp Client

Async Python client for the WhatsApp Business Cloud API.

[!NOTE] This package was mainly LLM-generated (Claude), with very strong opinions from @Kludex.

Requires Python 3.10+

Installation

Install with uv:

uv add whatsapp-client

Setup

You need two credentials from Meta to use this package:

1. Phone Number ID

  1. Go to the Meta Developer Portal and create a Business app.
  2. In your app dashboard, add the WhatsApp product.
  3. Go to WhatsApp > API Setup — your Phone Number ID is listed below the test phone number.

2. Access Token

For testing, click Generate on the API Setup page to get a temporary 24-hour token.

For production, create a System User in Meta Business Suite:

  1. Create a System User with Admin role.
  2. Assign your WhatsApp app to the System User.
  3. Generate a token with the whatsapp_business_messaging permission.

3. Add test recipients

In test mode, you must verify recipient phone numbers before sending messages. Go to WhatsApp > API Setup, click Manage phone number list, and add the numbers you want to message.

Usage

Send a text message:

from whatsapp_client import WhatsAppClient

async with WhatsAppClient(phone_number_id="your-phone-number-id", access_token="your-access-token") as client:
    await client.send_text(to="5511999999999", body="Hello!")

Send media

Images, audio, video, documents, and stickers are sent by URL:

await client.send_image(to="5511999999999", link="https://example.com/image.png", caption="Check this out")
await client.send_audio(to="5511999999999", link="https://example.com/audio.mp3")
await client.send_video(to="5511999999999", link="https://example.com/video.mp4")
await client.send_document(to="5511999999999", link="https://example.com/doc.pdf", filename="report.pdf")
await client.send_sticker(to="5511999999999", link="https://example.com/sticker.webp")

Send location

Share a pin with optional name and address:

await client.send_location(to="5511999999999", latitude=-23.5505, longitude=-46.6333, name="São Paulo")

Send template

Send a pre-approved message template:

from whatsapp_client import Template, TemplateLanguage

await client.send_template(
    to="5511999999999", template=Template(name="hello_world", language=TemplateLanguage(code="en_US"))
)

Send interactive messages

Reply buttons and list menus let users pick from predefined options:

from whatsapp_client import ReplyButton, ListSection, ListRow

await client.send_buttons(
    to="5511999999999",
    body="Choose an option:",
    buttons=[ReplyButton(id="btn1", title="Option 1"), ReplyButton(id="btn2", title="Option 2")],
)

await client.send_list(
    to="5511999999999",
    body="Browse products:",
    button_text="View",
    sections=[ListSection(title="Category", rows=[ListRow(id="r1", title="Item 1")])],
)

Error handling

API errors are raised as WhatsAppAPIError with the status code and Graph API error details:

from whatsapp_client import WhatsAppAPIError

try:
    await client.send_text(to="invalid", body="Hello")
except WhatsAppAPIError as e:
    print(e.status_code, e.error_code, e.message)

Webhooks

Receive incoming messages and status updates via Meta's webhook system.

Starlette example

Create a WebhookHandler with your app secret and client. Register callbacks with @handler.on_message and @handler.on_status — the client is passed as the first argument, so replying is straightforward. Use match/case on message.content to handle different message types:

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse, Response
from starlette.routing import Route

from whatsapp_client import (
    MediaContent,
    Message,
    Status,
    TextContent,
    WebhookHandler,
    WebhookNotification,
    WhatsAppClient,
    verify_challenge,
)

client = WhatsAppClient(phone_number_id="your-phone-number-id", access_token="your-access-token")
handler = WebhookHandler(app_secret="your-app-secret", client=client)


@handler.on_message
async def on_message(client: WhatsAppClient, notification: WebhookNotification, message: Message) -> None:
    match message.content:
        case TextContent(body=body):
            await client.send_text(to=message.from_, body=f"Echo: {body}")
        case MediaContent():
            await client.send_text(to=message.from_, body=f"Got {message.type}: {message.content.id}")


@handler.on_status
async def on_status(client: WhatsAppClient, notification: WebhookNotification, status: Status) -> None:
    print(f"{status.id} -> {status.status}")


async def webhook_get(request: Request) -> Response:
    challenge = verify_challenge(
        mode=request.query_params["hub.mode"],
        token=request.query_params["hub.verify_token"],
        challenge=request.query_params["hub.challenge"],
        verify_token="your-verify-token",
    )
    return PlainTextResponse(challenge)


async def webhook_post(request: Request) -> Response:
    body = await request.body()
    signature = request.headers["x-hub-signature-256"]
    await handler.handle(body=body, signature=signature)
    return Response(status_code=200)


@asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
    async with client:
        yield


app = Starlette(
    routes=[Route("/webhook", webhook_get, methods=["GET"]), Route("/webhook", webhook_post, methods=["POST"])],
    lifespan=lifespan,
)

License

This project is licensed under the terms of the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

whatsapp_client-0.2.0.tar.gz (57.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

whatsapp_client-0.2.0-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

File details

Details for the file whatsapp_client-0.2.0.tar.gz.

File metadata

  • Download URL: whatsapp_client-0.2.0.tar.gz
  • Upload date:
  • Size: 57.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for whatsapp_client-0.2.0.tar.gz
Algorithm Hash digest
SHA256 5a2319f8e8464382e031a7d6d5d2241cb18e9407090482485aca4a2e92a73d3b
MD5 c36614c92f8063f9f231d5429c6e7af2
BLAKE2b-256 f78a5bb9ba884412a3479e0e4ca69df087e0f78af3eada80cdb3479b8f08a776

See more details on using hashes here.

Provenance

The following attestation bundles were made for whatsapp_client-0.2.0.tar.gz:

Publisher: publish.yml on Kludex/whatsapp-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whatsapp_client-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for whatsapp_client-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c278827562b39e861eeb73682594812ef71d2447d465d8455f78e1c069f67ec8
MD5 aa3f73257eeec9c75f32503533911ca2
BLAKE2b-256 89daf35d35a17e2e1d46932fc999f5a296984954bce2c1a1e3a42b8f0e8cc1fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for whatsapp_client-0.2.0-py3-none-any.whl:

Publisher: publish.yml on Kludex/whatsapp-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page