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
- Go to the Meta Developer Portal and create a Business app.
- In your app dashboard, add the WhatsApp product.
- 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:
- Create a System User with Admin role.
- Assign your WhatsApp app to the System User.
- Generate a token with the
whatsapp_business_messagingpermission.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a2319f8e8464382e031a7d6d5d2241cb18e9407090482485aca4a2e92a73d3b
|
|
| MD5 |
c36614c92f8063f9f231d5429c6e7af2
|
|
| BLAKE2b-256 |
f78a5bb9ba884412a3479e0e4ca69df087e0f78af3eada80cdb3479b8f08a776
|
Provenance
The following attestation bundles were made for whatsapp_client-0.2.0.tar.gz:
Publisher:
publish.yml on Kludex/whatsapp-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whatsapp_client-0.2.0.tar.gz -
Subject digest:
5a2319f8e8464382e031a7d6d5d2241cb18e9407090482485aca4a2e92a73d3b - Sigstore transparency entry: 926941892
- Sigstore integration time:
-
Permalink:
Kludex/whatsapp-client@6e235423808f2e57f4e19dd83ae2464c664e6a5e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6e235423808f2e57f4e19dd83ae2464c664e6a5e -
Trigger Event:
push
-
Statement type:
File details
Details for the file whatsapp_client-0.2.0-py3-none-any.whl.
File metadata
- Download URL: whatsapp_client-0.2.0-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c278827562b39e861eeb73682594812ef71d2447d465d8455f78e1c069f67ec8
|
|
| MD5 |
aa3f73257eeec9c75f32503533911ca2
|
|
| BLAKE2b-256 |
89daf35d35a17e2e1d46932fc999f5a296984954bce2c1a1e3a42b8f0e8cc1fe
|
Provenance
The following attestation bundles were made for whatsapp_client-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on Kludex/whatsapp-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whatsapp_client-0.2.0-py3-none-any.whl -
Subject digest:
c278827562b39e861eeb73682594812ef71d2447d465d8455f78e1c069f67ec8 - Sigstore transparency entry: 926941893
- Sigstore integration time:
-
Permalink:
Kludex/whatsapp-client@6e235423808f2e57f4e19dd83ae2464c664e6a5e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6e235423808f2e57f4e19dd83ae2464c664e6a5e -
Trigger Event:
push
-
Statement type: