Skip to content

WhatsApp Providers

Business API (Webhook-based)

WhatsAppProvider

Bases: ABC

WhatsApp delivery provider.

name property

name

Provider name (e.g. 'meta', 'twilio_wa').

send abstractmethod async

send(event, to)

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_webhook(payload)

Parse an inbound webhook payload into an InboundMessage.

send_template async

send_template(to, template_name, params=None)

Send a template message.

send_reaction async

send_reaction(chat, sender, message_id, emoji)

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

close async

close()

Release resources. Override in subclasses that hold connections.

MockWhatsAppProvider

MockWhatsAppProvider()

Bases: WhatsAppProvider

Records sent messages for verification in tests.

Personal Account (Neonize)

Warning: WhatsAppPersonalProvider uses 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

pip install roomkit[whatsapp-personal]

API reference

WhatsAppPersonalProvider

WhatsAppPersonalProvider(source)

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(event, to)

Send a message through the neonize client.

Maps RoomEvent.content types to the appropriate neonize send method.

send_typing async

send_typing(to, *, is_typing=True, media='text')

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 for composing, False for paused.

True
media str

"text" for typing or "audio" for recording.

'text'

mark_read async

mark_read(message_ids, chat, sender)

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_reaction(chat, sender, message_id, emoji)

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

close async

close()

No-op — the source owns the client lifecycle.