Messaging API - Send Anything¶
Complete reference for everything you can send through WhatsApp. Copy these examples and modify them for your conversational app.
Quick Reference¶
Here's what you can send and when to use each:
Message Type | When to Use | Example Use Case |
---|---|---|
Text | Most common - announcements, confirmations | "Order confirmed! Tracking: ORD123" |
Image | Product photos, receipts, infographics | Product showcase, receipt images |
Video | Demos, tutorials, marketing | Product demos, how-to videos |
Audio | Voice messages, announcements | Personal messages, audio updates |
Document | Catalogs, forms, contracts | PDF catalog, price list, invoice |
Sticker | Fun reactions, branding | Thank you reactions, branded stickers |
Buttons | Quick choices (max 3 options) | Yes/No, menu options, quick actions |
Lists | Many options (up to 100 items) | Product categories, service options |
Call-to-Action | Drive to website/app | "Visit our store", "Download app" |
Contact | Share business/team contact | Customer service, sales rep |
Location | Share business address | Store location, delivery address |
Templates | Pre-approved business messages | Order confirmations, promotions |
How Messaging Works in Wappa¶
In your event handler, you have access to self.messenger
- your gateway to sending anything:
from wappa import WappaEventHandler
class MyApp(WappaEventHandler):
async def handle_message(self, webhook):
# The messenger is automatically injected
await self.messenger.send_text("Hello!", webhook.user.user_id)
# You also have access to the media handler
upload_result = await self.messenger.media_handler.upload_media("image.jpg")
if upload_result.success:
await self.messenger.send_image(upload_result.media_id, webhook.user.user_id)
That's the pattern. Everything else is just different methods on self.messenger
.
Text Messages¶
send_text()
¶
Send text messages up to 4,096 characters:
# Simple text
await self.messenger.send_text("Hello there!", recipient_phone)
# Multi-line with formatting
await self.messenger.send_text(
"""๐ Order Confirmed!
๐ Order: ORD123
๐ฐ Total: $89.99
๐
Delivery: March 15
Thank you for your purchase!""",
recipient_phone
)
# Reply to a specific message
await self.messenger.send_text(
"Thanks for your message!",
recipient_phone,
reply_to_message_id="wamid.123456"
)
# Disable URL preview
await self.messenger.send_text(
"Check this link: https://example.com (no preview)",
recipient_phone,
disable_preview=True
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
text | str | โ | Message text (1-4096 characters) |
recipient | str | โ | Recipient phone number or ID |
reply_to_message_id | str \| None | โ | Message ID to reply to (creates thread) |
disable_preview | bool | โ | Disable URL preview (default: False) |
Returns¶
MessageResult
object with: - success
: Boolean indicating if message was sent - message_id
: WhatsApp message ID for tracking - error
: Error message if failed - recipient
: Recipient identifier - platform
: Always "whatsapp"
Media Messages¶
send_image()
¶
Send images (JPEG/PNG, up to 5MB):
# Image from URL with caption
await self.messenger.send_image(
"https://example.com/product.jpg",
recipient_phone,
caption="Check out our new product! ๐ธ"
)
# Local image file
await self.messenger.send_image(
"assets/welcome.jpg", # Local file path
recipient_phone,
caption="Welcome to our store! ๐๏ธ"
)
# Using media_id (for already uploaded media)
await self.messenger.send_image(
"1234567890123456", # media_id from previous upload
recipient_phone,
caption="Reusing uploaded image! โป๏ธ"
)
# Image without caption
await self.messenger.send_image(
"https://example.com/logo.png",
recipient_phone
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
image_source | str \| Path | โ | Image URL, file path, or media_id |
recipient | str | โ | Recipient phone number or ID |
caption | str \| None | โ | Image caption (max 1024 chars) |
reply_to_message_id | str \| None | โ | Message ID to reply to |
send_video()
¶
Send videos (MP4/3GP, up to 16MB):
# Product demo video
await self.messenger.send_video(
"https://example.com/demo.mp4",
recipient_phone,
caption="See our product in action! ๐ฌ"
)
# Local video file
await self.messenger.send_video(
"videos/tutorial.mp4",
recipient_phone,
caption="Quick tutorial - 30 seconds โฑ๏ธ"
)
# Reuse uploaded video
await self.messenger.send_video(
"9876543210987654", # media_id
recipient_phone,
caption="Product demo (cached) ๐ฅ"
)
send_audio()
¶
Send audio files (AAC/AMR/MP3/OGG, up to 16MB):
# Voice message (no caption support)
await self.messenger.send_audio(
"audio/greeting.ogg",
recipient_phone
)
# Music or podcast
await self.messenger.send_audio(
"https://example.com/podcast.mp3",
recipient_phone
)
# Reuse uploaded audio
await self.messenger.send_audio(
"1111222233334444", # media_id
recipient_phone
)
send_document()
¶
Send documents (PDF/DOC/XLS/PPT, up to 100MB):
# PDF catalog with custom filename
await self.messenger.send_document(
"catalogs/products.pdf",
recipient_phone,
filename="Product_Catalog_2025.pdf",
caption="Our complete product catalog ๐"
)
# Excel price list
await self.messenger.send_document(
"https://example.com/prices.xlsx",
recipient_phone,
caption="Updated pricing - effective immediately"
)
# Reuse uploaded document
await self.messenger.send_document(
"5555666677778888", # media_id
recipient_phone,
filename="Manual.pdf",
caption="Product manual (cached) ๐"
)
send_sticker()
¶
Send stickers (WebP only, 500KB max):
# Fun sticker (no caption support)
await self.messenger.send_sticker(
"stickers/thumbs_up.webp",
recipient_phone
)
# Reuse uploaded sticker
await self.messenger.send_sticker(
"9999888877776666", # media_id
recipient_phone
)
Media Specifications¶
Media Type | Max Size | Formats | Caption | media_id Support |
---|---|---|---|---|
Image | 5MB | JPG, PNG | โ Yes | โ Yes |
Video | 16MB | MP4, 3GP | โ Yes | โ Yes |
Audio | 16MB | AAC, AMR, MP3, OGG | โ No | โ Yes |
Document | 100MB | PDF, DOC, XLS, PPT, TXT | โ Yes | โ Yes |
Sticker | 500KB | WebP | โ No | โ Yes |
Interactive Messages¶
send_button_message()
¶
Send up to 3 quick reply buttons:
from wappa.models import ReplyButton
# Simple Yes/No buttons
buttons = [
ReplyButton(id="yes", title="Yes, please! โ
"),
ReplyButton(id="no", title="No, thanks โ")
]
await self.messenger.send_button_message(
"Would you like to receive our newsletter?",
buttons,
recipient_phone
)
# Customer service menu with header/footer
support_buttons = [
ReplyButton(id="track_order", title="๐ฆ Track Order"),
ReplyButton(id="return_item", title="โฉ๏ธ Return Item"),
ReplyButton(id="talk_human", title="๐ฌ Talk to Human")
]
await self.messenger.send_button_message(
"How can we help you today?",
support_buttons,
recipient_phone,
header="๐ ๏ธ Customer Support",
footer="We're here to help!"
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
body | str | โ | Main message text (max 1024 chars) |
buttons | list[ReplyButton] | โ | Up to 3 buttons |
recipient | str | โ | Recipient phone number or ID |
header | str \| None | โ | Header text (max 60 chars) |
footer | str \| None | โ | Footer text (max 60 chars) |
reply_to_message_id | str \| None | โ | Message ID to reply to |
Button Limits¶
- Maximum 3 buttons per message
- Button titles: 20 characters max
- Button IDs: 256 characters max (use descriptive IDs!)
send_list_message()
¶
Send lists with up to 100 items organized in sections:
from wappa.models import ListSection, ListRow
# Product categories
sections = [
ListSection(
title="๐ฑ Electronics",
rows=[
ListRow(id="phones", title="Smartphones", description="Latest iPhone, Samsung"),
ListRow(id="laptops", title="Laptops", description="MacBook, Dell, HP"),
ListRow(id="accessories", title="Accessories", description="Cases, chargers, cables")
]
),
ListSection(
title="๐ Clothing",
rows=[
ListRow(id="mens", title="Men's Fashion", description="Shirts, pants, shoes"),
ListRow(id="womens", title="Women's Fashion", description="Dresses, tops, accessories")
]
)
]
await self.messenger.send_list_message(
"What would you like to browse?",
sections,
"๐๏ธ Browse Categories", # Button text
recipient_phone,
header="Welcome to our store!",
footer="Tap to explore our collection"
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
body | str | โ | Main message text (max 4096 chars) |
sections | list[ListSection] | โ | Up to 10 sections |
button_text | str | โ | Text for list button (max 20 chars) |
recipient | str | โ | Recipient phone number or ID |
header | str \| None | โ | Header text (max 60 chars) |
footer | str \| None | โ | Footer text (max 60 chars) |
reply_to_message_id | str \| None | โ | Message ID to reply to |
List Limits¶
- Maximum 10 sections per list
- Maximum 10 rows per section
- Section titles: 24 characters max
- Row titles: 24 characters max
- Row descriptions: 72 characters max
- All row IDs must be unique across the entire list
send_cta_message()
¶
Send call-to-action buttons that link to external URLs:
# Drive traffic to website
await self.messenger.send_cta_message(
"Check out our new summer collection!",
"๐๏ธ Shop Now", # Button text
"https://yourstore.com/summer", # Button URL
recipient_phone,
header="๐ Summer Sale",
footer="Free shipping on orders over $50"
)
# App download
await self.messenger.send_cta_message(
"Download our mobile app for exclusive deals!",
"๐ฑ Download App",
"https://apps.apple.com/app/yourapp",
recipient_phone
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
body | str | โ | Main message text |
button_text | str | โ | Text on the button |
button_url | str | โ | URL to open (must start with http/https) |
recipient | str | โ | Recipient phone number or ID |
header | str \| None | โ | Header text |
footer | str \| None | โ | Footer text |
reply_to_message_id | str \| None | โ | Message ID to reply to |
Template Messages¶
send_text_template()
¶
Send pre-approved business templates:
# Order confirmation template
await self.messenger.send_text_template(
template_name="order_confirmation",
recipient=customer_phone,
body_parameters=[
{"type": "text", "text": "John"}, # {{1}} - Customer name
{"type": "text", "text": "ORD123"}, # {{2}} - Order number
{"type": "text", "text": "$89.99"} # {{3}} - Total amount
],
language_code="en"
)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
template_name | str | โ | WhatsApp-approved template name |
recipient | str | โ | Recipient phone number |
body_parameters | list[dict] \| None | โ | Parameters for {{1}}, {{2}}, {{3}} placeholders |
language_code | str | โ | BCP-47 language code (default: "es") |
send_media_template()
¶
Templates with image/video headers:
# Product announcement with image
await self.messenger.send_media_template(
template_name="product_announcement",
recipient=customer_phone,
media_type="image",
media_url="https://yourstore.com/new-product.jpg",
body_parameters=[
{"type": "text", "text": "Summer Collection"},
{"type": "text", "text": "30% OFF"}
]
)
send_location_template()
¶
Templates with location headers:
# Store location template
await self.messenger.send_location_template(
template_name="store_location",
recipient=customer_phone,
latitude="37.7749",
longitude="-122.4194",
name="Our Main Store",
address="123 Market St, San Francisco, CA",
body_parameters=[
{"type": "text", "text": "Downtown Store"}
]
)
Specialized Messages¶
send_contact()
¶
Share contact information using convenient helper models:
from wappa.models import BusinessContact, PersonalContact, ContactCard
# Quick business contact (easiest way)
business = BusinessContact(
business_name="Mimeia Hotel",
phone="+1234567890",
email="contact@mimeia.com",
website="https://mimeia.com",
address="123 Business St, City"
)
await self.messenger.send_contact(
business.to_contact_card(),
recipient_phone
)
# Quick personal contact
personal = PersonalContact(
first_name="Sarah",
last_name="Johnson",
phone="+1234567890",
email="sarah@company.com"
)
await self.messenger.send_contact(
personal.to_contact_card(),
recipient_phone
)
# Full contact card with all details
from wappa.models import ContactCard, ContactName, ContactPhone, PhoneType
contact_card = ContactCard(
name=ContactName(
formatted_name="Dr. John Smith",
first_name="John",
last_name="Smith",
prefix="Dr."
),
phones=[
ContactPhone(phone="+1234567890", type=PhoneType.WORK),
ContactPhone(phone="+1234567891", type=PhoneType.CELL)
],
emails=[{
"email": "john@hospital.com",
"type": "WORK"
}],
org={
"company": "City Hospital",
"department": "Cardiology",
"title": "Senior Cardiologist"
}
)
await self.messenger.send_contact(contact_card, recipient_phone)
send_location()
¶
Share geographic location using helper models:
from wappa.models import LocationMessage
# Using LocationMessage model (validates coordinates)
location = LocationMessage(
recipient=recipient_phone,
latitude=37.7749,
longitude=-122.4194,
name="Mimeia Hotel",
address="123 Market Street, San Francisco, CA"
)
await self.messenger.send_location(
latitude=location.latitude,
longitude=location.longitude,
recipient=location.recipient,
name=location.name,
address=location.address
)
# Quick location sharing (direct method)
await self.messenger.send_location(
latitude=37.7749,
longitude=-122.4194,
recipient_phone,
name="Our Main Store",
address="123 Market Street, San Francisco, CA"
)
send_location_request()
¶
Request user's location using helper models:
from wappa.models import LocationRequestMessage
# Using LocationRequestMessage model (validates text length)
location_request = LocationRequestMessage(
recipient=recipient_phone,
body="Please share your location so we can find the nearest store and provide accurate delivery estimates."
)
await self.messenger.send_location_request(
location_request.body,
location_request.recipient
)
# Quick location request (direct method)
await self.messenger.send_location_request(
"Please share your location so we can find the nearest store.",
recipient_phone
)
Media Handler API¶
Access advanced media operations through self.messenger.media_handler
:
Upload Media¶
# Upload and get media_id for reuse
upload_result = await self.messenger.media_handler.upload_media("logo.png")
if upload_result.success:
# Reuse the media_id multiple times
media_id = upload_result.media_id
await self.messenger.send_image(media_id, recipient1)
await self.messenger.send_image(media_id, recipient2)
await self.messenger.send_image(media_id, recipient3)
# Upload from bytes
with open("document.pdf", "rb") as f:
file_data = f.read()
upload_result = await self.messenger.media_handler.upload_media_from_bytes(
file_data,
"application/pdf",
"catalog.pdf"
)
Download Media¶
# Download user-sent media to process it
async def handle_message(self, webhook):
if webhook.type == "image":
# Get the media_id from the webhook
media_id = webhook.image.id
# Download as bytes for processing
download_result = await self.messenger.media_handler.get_media_as_bytes(media_id)
if download_result.success:
# Process the image data
image_data = download_result.file_data
processed_image = await process_image(image_data)
# Send processed result back
await self.messenger.send_text("Image processed successfully! โ
", webhook.user.user_id)
# Download to temporary file
async with self.messenger.media_handler.download_media_tempfile(media_id, '.jpg') as result:
if result.success:
# File automatically cleaned up when context exits
processed_file = await process_image_file(result.file_path)
Media Handler Methods¶
Method | Purpose | Returns |
---|---|---|
upload_media(file_path) | Upload file, get media_id | MediaUploadResult |
upload_media_from_bytes(data, type, name) | Upload from memory | MediaUploadResult |
get_media_info(media_id) | Get media URL and metadata | MediaInfoResult |
get_media_as_bytes(media_id) | Download to memory | MediaDownloadResult |
download_media(media_id, path) | Download to file | MediaDownloadResult |
download_media_tempfile(media_id) | Download to temp file | Context manager |
delete_media(media_id) | Delete from WhatsApp servers | MediaDeleteResult |
validate_media_type(mime_type) | Check if MIME type supported | bool |
validate_file_size(size, mime_type) | Check if file size allowed | bool |
Reading Messages¶
mark_as_read()
¶
Mark messages as read (good UX practice):
# Simple mark as read
await self.messenger.mark_as_read(message_id)
# Mark as read with typing indicator
await self.messenger.mark_as_read(message_id, typing=True)
Parameters¶
Parameter | Type | Required | Description |
---|---|---|---|
message_id | str | โ | WhatsApp message ID to mark as read |
typing | bool | โ | Show typing indicator (default: False) |
Error Handling¶
All messaging methods return MessageResult
objects. Always check for success:
# Good error handling
result = await self.messenger.send_text("Hello!", recipient_phone)
if result.success:
self.logger.info(f"Message sent! ID: {result.message_id}")
else:
self.logger.error(f"Failed to send message: {result.error}")
# Handle specific errors
if "401" in str(result.error):
# Authentication issue
await self.messenger.send_text(
"Service temporarily unavailable. Please try again.",
recipient_phone
)
elif "rate limit" in str(result.error).lower():
# Rate limited - wait and retry
await asyncio.sleep(1)
# Retry logic here
Common Error Codes¶
Error | Meaning | Solution |
---|---|---|
FILE_NOT_FOUND | Media file doesn't exist | Check file path |
MIME_TYPE_UNSUPPORTED | Invalid file format | Use supported formats |
FILE_SIZE_EXCEEDED | File too large | Compress or use smaller file |
INVALID_RECIPIENT | Bad phone number | Validate phone format |
RATE_LIMIT_EXCEEDED | Too many messages | Implement backoff |
AUTHENTICATION_FAILED | Invalid WhatsApp token | Check credentials |
Rate Limiting & Best Practices¶
WhatsApp Rate Limits¶
Business-Initiated Messages Only (Templates)¶
- Unverified accounts: 250 business-initiated messages/day
- Verified accounts: 1,000 business-initiated messages/day (Tier 1)
- Higher tiers: 10,000 (Tier 2), 100,000 (Tier 3), unlimited (Tier 4)
- Tier upgrades: Automatic based on message quality and volume
Customer Service Window (User-Initiated)¶
- No limits when user starts or continues conversation
- 24-hour window after user's last message
- Unlimited responses during active conversations
For complete details, see WhatsApp Business Platform Messaging Limits
Best Practices¶
1. Batch Operations¶
# โ Don't send messages one by one
for user in users:
await self.messenger.send_text("Update!", user.phone)
# โ
Batch with delays
for i, user in enumerate(users):
await self.messenger.send_text("Update!", user.phone)
if i % 10 == 0: # Pause every 10 messages
await asyncio.sleep(1)
2. Cache Media IDs¶
# โ Upload same image multiple times
for customer in customers:
await self.messenger.send_image("logo.png", customer.phone)
# โ
Upload once, reuse media_id
upload_result = await self.messenger.media_handler.upload_media("logo.png")
if upload_result.success:
logo_media_id = upload_result.media_id
for customer in customers:
await self.messenger.send_image(logo_media_id, customer.phone)
3. Smart Error Recovery¶
async def send_with_retry(self, message_func, max_retries=3):
for attempt in range(max_retries):
try:
result = await message_func()
if result.success:
return result
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt) # Exponential backoff
4. Message Sequencing¶
# โ Overwhelming users
await self.messenger.send_text("Welcome!", user_id)
await self.messenger.send_image("logo.jpg", user_id)
await self.messenger.send_button_message("What next?", buttons, user_id)
# โ
Spaced interaction
await self.messenger.send_text("Welcome to our store! ๐", user_id)
# Wait for user response or trigger, then continue...
Complete API Reference¶
MessageResult Object¶
Every messaging method returns a MessageResult
:
class MessageResult:
success: bool # True if message sent successfully
message_id: str | None # WhatsApp message ID for tracking
recipient: str # Recipient identifier
error: str | None # Error message if failed
error_code: str | None # Error code for programmatic handling
platform: str # Always "whatsapp"
tenant_id: str # Your phone_number_id
Usage Example¶
async def send_welcome_sequence(self, user_id: str):
# Send welcome text
text_result = await self.messenger.send_text(
"Welcome to our store! ๐",
user_id
)
if not text_result.success:
self.logger.error(f"Failed to send welcome: {text_result.error}")
return
# Send store image
image_result = await self.messenger.send_image(
"assets/store-front.jpg",
user_id,
caption="Our beautiful store location ๐ช"
)
# Show menu options
from wappa.models import ReplyButton
buttons = [
ReplyButton(id="browse", title="๐๏ธ Browse Products"),
ReplyButton(id="support", title="๐ฌ Get Support"),
ReplyButton(id="location", title="๐ Store Location")
]
button_result = await self.messenger.send_button_message(
"What would you like to do first?",
buttons,
user_id,
footer="We're here to help!"
)
if button_result.success:
self.logger.info(f"Welcome sequence completed for {user_id}")
Auto-Generated API Documentation¶
Wappa automatically generates interactive API documentation for all messaging endpoints! When you run your conversational app, visit:
http://localhost:8000/docs
(or your custom port)
This gives you a complete interactive API explorer where you can:
Available API Endpoints¶
Endpoint Category | Endpoints | What You Can Do |
---|---|---|
Messages | /api/whatsapp/messages/ | Send text, mark as read, get limits |
Media | /api/whatsapp/media/ | Upload, send images/videos/audio/docs, download |
Interactive | /api/whatsapp/interactive/ | Send buttons, lists, call-to-action messages |
Templates | /api/whatsapp/templates/ | Send business templates, get status |
Specialized | /api/whatsapp/specialized/ | Send contacts, locations, location requests |
Webhooks | /api/webhooks/ | Handle incoming WhatsApp webhooks |
Health | /health | Service health checks and monitoring |
Interactive Features¶
๐งช Test Messages Directly: Try any endpoint right from your browser - Upload media files and get media_ids - Send test messages to your WhatsApp number
- Validate parameters before sending - See real API responses and error messages
๐ Copy cURL Commands: Get ready-to-use cURL commands for external integration
๐ Explore Data Models: See all request/response schemas with examples
Example API Usage¶
# Send text message via API
curl -X POST "http://localhost:8000/api/whatsapp/messages/send-text" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from the API!",
"recipient": "+1234567890"
}'
# Upload media and get media_id
curl -X POST "http://localhost:8000/api/whatsapp/media/upload" \
-F "file=@product.jpg" \
-F "media_type=image/jpeg"
# Send interactive buttons
curl -X POST "http://localhost:8000/api/whatsapp/interactive/send-buttons" \
-H "Content-Type: application/json" \
-d '{
"body": "Choose an option:",
"buttons": [
{"id": "yes", "title": "โ
Yes"},
{"id": "no", "title": "โ No"}
],
"recipient": "+1234567890"
}'
Why This Matters: You can integrate Wappa with external systems, build admin panels, or test messages without writing code!
Pro Tip
Use the /docs
interface to test your message flows before integrating them into your conversational app. It's much faster than coding and testing!
What's Next?¶
Now you know every messaging method in Wappa! Ready to handle responses?
- Event System Guide - Handle user responses and build conversation flows
- Webhook Data Reference - Understand the data WhatsApp sends you
- Configuration Guide - Set up your messaging credentials
๐จ Messaging API Master!
You can now send any type of WhatsApp message. Time to handle user responses!
๐ Learn Event Handling