How to Send WhatsApp Messages with Python: Full Code Tutorial (2026)
Send WhatsApp messages with Python using the Rapiwa REST API. Complete tutorial with requests library, async aiohttp, Django, Flask, and error handling examples.
You can send WhatsApp messages with Python by making an HTTP POST request to the Rapiwa API using the requests library. With Rapiwa ($5/month), connect your WhatsApp number, get an API key, and send messages in 3 lines of Python. This tutorial covers basic sending, bulk messaging, file attachments, error handling, async sending, and Django/Flask integration.
Prerequisites
pip install requests
- Rapiwa account: free 3-day trial
- Your API key from Dashboard → API Keys
- WhatsApp number connected via QR code
Step 1: Send Your First Message
import requests
API_KEY = "YOUR_RAPIWA_API_KEY"
response = requests.post(
"https://app.rapiwa.com/send-message",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"number": "8801234567890", "message": "Hello from Python!"}
)
print(response.json())
# {'status': 'success', 'messageId': 'msg_abc123', 'timestamp': '...'}
Phone number format: international format, no +, no spaces. Bangladesh: 8801234567890.
Step 2: Create a Reusable WhatsApp Client
import requests
from typing import Optional
class RapiwaClient:
"""WhatsApp messaging client using Rapiwa API."""
BASE_URL = "https://app.rapiwa.com"
def __init__(self, api_key: str):
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
def send_text(self, phone: str, message: str) -> dict:
"""Send a text message."""
response = self.session.post(
f"{self.BASE_URL}/send-message",
json={"number": phone, "message": message}
)
response.raise_for_status()
return response.json()
def send_image(self, phone: str, image_url: str, caption: str = "") -> dict:
"""Send an image with optional caption."""
payload = {"number": phone, "image": image_url}
if caption:
payload["caption"] = caption
response = self.session.post(f"{self.BASE_URL}/send-message", json=payload)
response.raise_for_status()
return response.json()
def send_document(self, phone: str, doc_url: str, filename: str, caption: str = "") -> dict:
"""Send a document (PDF, DOCX, etc.)."""
payload = {"number": phone, "document": doc_url, "filename": filename}
if caption:
payload["caption"] = caption
response = self.session.post(f"{self.BASE_URL}/send-message", json=payload)
response.raise_for_status()
return response.json()
def check_number(self, phone: str) -> dict:
"""Check if a phone number is registered on WhatsApp."""
response = self.session.get(
f"{self.BASE_URL}/check-number",
params={"number": phone}
)
response.raise_for_status()
return response.json()
# Usage
client = RapiwaClient("YOUR_API_KEY")
result = client.send_text("8801234567890", "Hello!")
print(result)
Step 3: Send Different Message Types
client = RapiwaClient("YOUR_API_KEY")
# Text with WhatsApp formatting
client.send_text("8801234567890",
"*Bold text*\n_Italic text_\n~Strikethrough~\n`Monospace`"
)
# Image with caption
client.send_image("8801234567890",
"https://yourstore.com/product.jpg",
caption="Check out our new product!"
)
# PDF invoice
client.send_document("8801234567890",
"https://yourstore.com/invoices/INV-001.pdf",
filename="Invoice_001.pdf",
caption="Your invoice is attached."
)
# Location
requests.post("https://app.rapiwa.com/send-message",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"number": "8801234567890",
"latitude": 23.8103,
"longitude": 90.4125,
"locationName": "Our Store — Dhaka"
}
)
Step 4: Bulk Sending with Error Handling
import requests
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def send_bulk_messages(contacts: list[dict], message_template: str,
api_key: str, delay: float = 1.5) -> dict:
"""
Send personalized messages to multiple contacts.
contacts: list of dicts with 'phone' and any template variables
message_template: string with {variable} placeholders
delay: seconds to wait between messages
"""
results = {"success": [], "failed": []}
for i, contact in enumerate(contacts, 1):
try:
message = message_template.format(**contact)
response = requests.post(
"https://app.rapiwa.com/send-message",
headers={"Authorization": f"Bearer {api_key}"},
json={"number": contact["phone"], "message": message},
timeout=10
)
response.raise_for_status()
data = response.json()
if data.get("status") == "success":
results["success"].append(contact["phone"])
logger.info(f"[{i}/{len(contacts)}] ✓ {contact['phone']}")
else:
results["failed"].append({"phone": contact["phone"], "error": data})
except requests.exceptions.RequestException as e:
logger.error(f"[{i}/{len(contacts)}] ✗ {contact['phone']}: {e}")
results["failed"].append({"phone": contact["phone"], "error": str(e)})
if i < len(contacts):
time.sleep(delay)
logger.info(f"Done: {len(results['success'])} sent, {len(results['failed'])} failed")
return results
# Usage
contacts = [
{"phone": "8801111111111", "name": "Alice", "order_id": "12345"},
{"phone": "8802222222222", "name": "Bob", "order_id": "12346"},
]
results = send_bulk_messages(
contacts,
message_template="Hi {name}! Your order #{order_id} is confirmed.",
api_key="YOUR_API_KEY"
)
Step 5: Async Sending with aiohttp
For high-throughput applications, use async sending:
import asyncio
import aiohttp
API_KEY = "YOUR_API_KEY"
async def send_whatsapp_async(session: aiohttp.ClientSession, phone: str, message: str) -> dict:
async with session.post(
"https://app.rapiwa.com/send-message",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"number": phone, "message": message}
) as response:
return await response.json()
async def send_bulk_async(contacts: list[dict], message: str, max_concurrent: int = 10):
"""Send messages concurrently with rate limiting."""
semaphore = asyncio.Semaphore(max_concurrent)
async def send_with_limit(contact):
async with semaphore:
result = await send_whatsapp_async(session, contact["phone"],
message.format(**contact))
await asyncio.sleep(0.1) # Small delay even in async
return result
async with aiohttp.ClientSession() as session:
tasks = [send_with_limit(c) for c in contacts]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
# Usage
contacts = [{"phone": f"880100000{i:04d}", "name": f"User{i}"} for i in range(100)]
results = asyncio.run(send_bulk_async(contacts, "Hi {name}! Your account is active."))
Install aiohttp: pip install aiohttp
Step 6: Django Integration
# yourapp/services/whatsapp.py
import requests
from django.conf import settings
def send_order_notification(customer_phone: str, order_id: int, total: str):
"""Send WhatsApp order notification using Django settings."""
message = (
f"*Order Confirmed!* 🛒\n\n"
f"Order #{order_id}\n"
f"Total: ${total}\n\n"
f"We'll update you when it ships!"
)
response = requests.post(
"https://app.rapiwa.com/send-message",
headers={"Authorization": f"Bearer {settings.RAPIWA_API_KEY}"},
json={"number": customer_phone, "message": message},
timeout=10
)
if not response.ok:
raise ValueError(f"WhatsApp API error: {response.text}")
return response.json()
# In settings.py:
# RAPIWA_API_KEY = os.getenv("RAPIWA_API_KEY")
# In views.py:
# from .services.whatsapp import send_order_notification
# send_order_notification(order.phone, order.id, str(order.total))
Step 7: Flask Integration
from flask import Flask, request, jsonify
import requests
import os
app = Flask(__name__)
RAPIWA_KEY = os.getenv("RAPIWA_API_KEY")
def send_whatsapp(phone: str, message: str) -> bool:
resp = requests.post(
"https://app.rapiwa.com/send-message",
headers={"Authorization": f"Bearer {RAPIWA_KEY}"},
json={"number": phone, "message": message}
)
return resp.json().get("status") == "success"
@app.route("/notify-customer", methods=["POST"])
def notify_customer():
data = request.json
success = send_whatsapp(data["phone"], f"Hi {data['name']}! Your order is confirmed.")
return jsonify({"sent": success})
if __name__ == "__main__":
app.run(port=5000)
FAQ
What Python library should I use to send WhatsApp messages?
Use the requests library for simplicity. For async applications, use aiohttp. For Django, the built-in django.utils.http or requests both work.
How do I send WhatsApp messages with attachments in Python?
Use the document field with a publicly accessible URL: json={"number": phone, "document": url, "filename": "file.pdf"}.
Can I send WhatsApp messages from a Python script without a phone connected? Yes. Once your WhatsApp number is connected in the Rapiwa dashboard via QR code, your Python script can make API calls from any server without a phone present.
How do I handle rate limits in Python?
Add time.sleep(1.5) between messages, or use exponential backoff when receiving 429 status codes. See the bulk sending example above.
What is the best Python SDK for Rapiwa?
There is no official Rapiwa Python SDK yet (on the roadmap). The requests library with 3–5 lines of code is all you need for full functionality.
