WhatsApp Providers¶
Business API (Webhook-based)¶
WhatsAppProvider ¶
Bases: ABC
WhatsApp delivery provider.
send
abstractmethod
async
¶
Send a WhatsApp message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
RoomEvent
|
The room event containing the message content. |
required |
to
|
str
|
Recipient WhatsApp ID or phone number. |
required |
Returns:
| Type | Description |
|---|---|
ProviderResult
|
Result with provider-specific delivery metadata. |
parse_webhook
async
¶
Parse an inbound webhook payload into an InboundMessage.
send_reaction
async
¶
Send a reaction to a message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chat
|
str
|
Chat identifier (JID or phone). |
required |
sender
|
str
|
Sender of the message being reacted to. |
required |
message_id
|
str
|
External message ID to react to. |
required |
emoji
|
str
|
Emoji reaction (empty string to remove). |
required |
MockWhatsAppProvider ¶
Personal Account (Neonize)¶
Warning:
WhatsAppPersonalProvideruses the unofficial WhatsApp Web multidevice protocol via neonize. It is intended for personal use and experimentation only. Using unofficial clients may violate WhatsApp Terms of Service and could result in account restrictions.
WhatsAppPersonalProvider sends outbound messages through a shared neonize
client managed by WhatsAppPersonalSourceProvider. The provider does not
import neonize at the module level, so importing the class never requires the
optional dependency.
Quick start¶
from roomkit import RoomKit, WhatsAppPersonalChannel
from roomkit.sources import WhatsAppPersonalSourceProvider
from roomkit.providers.whatsapp.personal import WhatsAppPersonalProvider
kit = RoomKit()
# Lifecycle events (QR code, auth, connection status)
async def on_wa_event(event_type: str, data: dict):
if event_type == "qr":
print(f"Scan QR: {data['codes'][0]}")
elif event_type == "connected":
print("WhatsApp connected!")
# Source owns the neonize client and inbound message loop
source = WhatsAppPersonalSourceProvider(
db="wa-session.db",
channel_id="wa-personal",
on_event=on_wa_event,
)
# Provider wraps the source for outbound delivery
provider = WhatsAppPersonalProvider(source)
# Register channel and attach source
kit.register_channel(WhatsAppPersonalChannel("wa-personal", provider=provider))
await kit.attach_source("wa-personal", source, auto_restart=True)
Supported outbound content types¶
| Content type | Neonize method | Notes |
|---|---|---|
TextContent |
send_message |
Plain text |
MediaContent (image/*) |
send_image |
With optional caption |
MediaContent (other) |
send_document |
With filename |
AudioContent |
send_audio |
ptt=True for voice notes (audio/ogg) |
VideoContent |
send_video |
MP4 |
LocationContent |
send_location |
Lat/lng with optional label |
Unsupported content types return ProviderResult(success=False).
Typing indicators¶
Send typing indicators to show "composing..." or "recording audio..." on the recipient's device:
provider = WhatsAppPersonalProvider(source)
# Show "typing..." to recipient
await provider.send_typing("14155551234", is_typing=True)
# Show "recording audio..." to recipient
await provider.send_typing("14155551234", is_typing=True, media="audio")
# Stop typing indicator
await provider.send_typing("14155551234", is_typing=False)
Inbound typing indicators are delivered through the on_event callback as
"presence" events:
async def on_wa_event(event_type: str, data: dict):
if event_type == "presence":
name = data.get("sender_name") or data["sender"]
if data["state"] == "composing":
action = "recording audio..." if data["media"] == "audio" else "typing..."
print(f"[{name}] {action}")
elif data["state"] == "paused":
print(f"[{name}] stopped typing")
Note: Inbound typing requires the client to be marked as "available". This is done automatically on connect. Typing indicators are not sent in self-chat — a second person must be typing in your conversation.
Read receipts¶
Send read receipts (blue ticks) and receive delivery/read notifications:
# Send blue ticks for a message
await provider.mark_read(
message_ids=["ABCD1234"],
chat="14155551234",
sender="14155551234",
)
Inbound receipts are delivered through the on_event callback:
async def on_wa_event(event_type: str, data: dict):
if event_type == "receipt":
# data["type"]: "delivered", "read", "played", "read_self", etc.
# data["message_ids"]: list of message IDs
# data["sender_name"]: resolved name (if known)
print(f"Receipt: {data['type']} from {data.get('sender_name') or data['sender']}")
| Receipt type | Meaning |
|---|---|
delivered |
Message reached WhatsApp servers (grey double ticks) |
read |
Message was read by recipient (blue double ticks) |
read_self |
Message was read on another linked device |
played |
Audio/video was played by recipient |
played_self |
Audio/video played on another linked device |
sender |
Sender acknowledgement |
server_error |
Server delivery error |
Installation¶
API reference¶
WhatsAppPersonalProvider ¶
Bases: WhatsAppProvider
Outbound WhatsApp delivery via a shared neonize source.
The provider delegates all sends to the neonize client owned by the
paired WhatsAppPersonalSourceProvider. It does not manage
client lifecycle — that responsibility stays with the source.
send
async
¶
Send a message through the neonize client.
Maps RoomEvent.content types to the appropriate neonize send
method.
send_typing
async
¶
Send a typing indicator to a WhatsApp chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to
|
str
|
Phone number or full JID. |
required |
is_typing
|
bool
|
|
True
|
media
|
str
|
|
'text'
|
mark_read
async
¶
Send read receipts (blue ticks) for messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message_ids
|
list[str]
|
List of message IDs to mark as read. |
required |
chat
|
str
|
Phone number or full JID of the chat. |
required |
sender
|
str
|
Phone number or full JID of the sender. |
required |
send_reaction
async
¶
Send a reaction to a WhatsApp message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chat
|
str
|
Chat JID of the conversation. |
required |
sender
|
str
|
Sender JID of the message being reacted to. |
required |
message_id
|
str
|
WhatsApp message ID to react to. |
required |
emoji
|
str
|
Emoji reaction (empty string to remove). |
required |