Skip to main content

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.

Metadata

Release files for whatsapp-client 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for whatsapp-client 0.2.0
File Size Uploaded
whatsapp_client-0.2.0.tar.gz 57.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for whatsapp-client 0.2.0
File Interpreter ABI Platform
whatsapp_client-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 69.2 kB

Release files / whatsapp_client-0.2.0.tar.gz

Download URL whatsapp_client-0.2.0.tar.gz
Size 57.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5a2319f8e8464382e031a7d6d5d2241cb18e9407090482485aca4a2e92a73d3b
BLAKE2b-256 checksum
How to use checksums
f78a5bb9ba884412a3479e0e4ca69df087e0f78af3eada80cdb3479b8f08a776
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 7, 2026.

Transparency log

Release files / whatsapp_client-0.2.0-py3-none-any.whl

Download URL whatsapp_client-0.2.0-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c278827562b39e861eeb73682594812ef71d2447d465d8455f78e1c069f67ec8
BLAKE2b-256 checksum
How to use checksums
89daf35d35a17e2e1d46932fc999f5a296984954bce2c1a1e3a42b8f0e8cc1fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page