Welcome to django-meta-whatsapp
A production-ready WhatsApp Cloud Platform engine tailored exclusively for Django. Build CRM flows, blast campaigns, and chat with users natively.
Unified Inbox
Chat with customers in real-time. Full multimedia support.
Campaigns
Send bulk templated messages and track delivery/read rates.
Contacts & Labels
Manage audiences with colored labels and CSV imports.
Webhooks
Plug-and-play secure webhooks for the WhatsApp Cloud API.
Installation
This package is built for modern Python environments and is best managed via uv.
# Using uv
uv add django-meta-whatsapp
# Or using pip
pip install django-meta-whatsapp
After installation, add the package to your INSTALLED_APPS in Django:
INSTALLED_APPS = [
# ...
"django_meta_whatsapp",
]
Mount the dashboard URLs in your project's urls.py:
from django.urls import path, include
urlpatterns = [
# ...
path("whatsapp/", include("django_meta_whatsapp.urls", namespace="django_meta_whatsapp")),
]
Configuration
Configure the package by adding the following variables to your settings.py file. You can obtain these from the Meta Developer Portal.
Required Settings
WHATSAPP_API_TOKEN = "EAA..."
WHATSAPP_PHONE_NUMBER_ID = "1234567890"
WHATSAPP_WEBHOOK_VERIFY_TOKEN = "your_secure_random_string"
Optional UI Overrides
WHATSAPP_DASHBOARD_NAME = "My Business CRM"
WHATSAPP_DASHBOARD_LOGO = "https://yourwebsite.com/logo.png"
Finally, run migrations to set up the database schemas:
python manage.py migrate
Webhooks Engine
The package automatically exposes a secure webhook endpoint to receive incoming messages, read receipts, and status updates from Meta.
In your Meta App Dashboard, set your Webhook URL to:
https://yourdomain.com/whatsapp/webhook/
And use the WHATSAPP_WEBHOOK_VERIFY_TOKEN you defined in settings.
When messages arrive, they are instantly recorded in the WhatsAppMessage database and surface in the real-time Unified Inbox.
Contacts & Labels
The Contacts module acts as a mini-CRM for your WhatsApp audience. You can create contacts manually or import them via CSV.
Label Management
Group your contacts using Labels. Labels support custom hexadecimal colors or Tailwind presets. You can create a label directly from the Contact creation form simply by typing it into the dropdown and pressing Enter.
- CSV Import: Include a
labelscolumn in your CSV (comma-separated). Missing labels are auto-created! - Filtering: Use the dashboard to filter your contacts by label to easily prepare targeted campaigns.
Unified Inbox
A beautiful, real-time interface designed specifically for support and sales agents.
- Conversation Threads: Messages are cleanly grouped by Contact.
- Read Receipts: See exactly when your messages are delivered and read.
- Labeling: Assign labels directly to specific conversation threads.
Campaigns & Templates
Marketing on WhatsApp requires pre-approved Templates. The platform seamlessly syncs with Meta to pull down your templates.
Syncing Templates
Navigate to the Templates tab in the dashboard and click "Sync from Meta". All approved templates will be instantly available for your campaigns.
Running a Campaign
Create a new Campaign, select a synced template, and attach an audience by selecting specific Contacts or a specific Label. The engine will dispatch the messages via API and update the Campaign's status to reflect Deliveries, Reads, and Bounces.
In-App Signups
Utilize Graph API v22+ In-App Signups to easily build an audience. Create a "Signup Link" in the dashboard.
Share the wa.me/.../signup/... deep link on your website. When users tap it, WhatsApp opens and asks them to opt-in. When they do, the webhook automatically creates a WhatsAppContact record and links it to the signup source.
Blocked Users Sync
Stop wasting API calls and getting flagged for spam. When a user blocks your business on WhatsApp, the webhook detects the blocked status and automatically updates the Contact record as `is_blocked = True`.
These users are automatically excluded from future Campaigns, keeping your account health in pristine condition.
Python Utility Functions
You can use the built-in django_meta_whatsapp.utils module to programmatically send messages from anywhere in your Django backend (e.g., in signals, Celery tasks, or custom views).
Send Text Message
Sends a plain text message to a user. Pass an optional WhatsAppAccount instance if using multi-account mode.
from django_meta_whatsapp.utils import send_text_message
response = send_text_message(
phone_number="919876543210",
text="Hello from Django!",
account=my_account_instance # optional
)
Send Location Pin
Sends a map location pin with an optional name and address.
from django_meta_whatsapp.utils import send_location_message
send_location_message(
phone_number="919876543210",
latitude=28.6139,
longitude=77.2090,
name="Our Store",
address="New Delhi, India"
)
Upload & Send Media
First upload the media to Meta's servers, then dispatch the media message.
from django_meta_whatsapp.utils import upload_media, send_media_message
# 1. Upload Media
with open("invoice.pdf", "rb") as f:
media_id = upload_media(f, mime_type="application/pdf")
# 2. Send Document
send_media_message(
phone_number="919876543210",
media_id=media_id,
media_type="document",
filename="invoice.pdf",
caption="Here is your requested invoice."
)
Send Template Message
Dispatch an approved WhatsApp template with dynamic variables and buttons using the build_template_components helper.
from django_meta_whatsapp.utils import send_template_message, build_template_components
# Build the dynamic variables (e.g. for {{1}} and {{2}} in the body)
components = build_template_components(
body_params=["Rahul", "Tomorrow 5 PM"]
)
send_template_message(
phone_number="919876543210",
template_name="appointment_reminder",
language_code="en",
components=components
)
Template Management
Programmatically interact with Meta to fetch, push, or delete your message templates.
from django_meta_whatsapp.utils import (
sync_templates_from_meta,
push_template_to_meta,
delete_template_from_meta
)
# Fetch all templates from Meta
templates = sync_templates_from_meta()
# Delete a template from Meta
response = delete_template_from_meta(template_name="holiday_sale")
Campaign Execution
Trigger marketing campaigns from code. You can run them synchronously, or asynchronously via Celery if configured.
from django_meta_whatsapp.utils import run_campaign, run_campaign_async
# Run a campaign synchronously
results = run_campaign(campaign_id=1)
print(f"Sent: {results['sent']}, Failed: {results['failed']}")
# Or trigger it via Celery (requires WHATSAPP_USE_CELERY = True)
run_campaign_async(campaign_id=2)
Block Users API
Block users at the API level so you don't receive webhooks from them or accidentally message them.
from django_meta_whatsapp.utils import block_users
# Block up to 1,000 phone numbers in a single call
block_users(phone_numbers=["919876543210", "1234567890"])
Dashboard UI Customization
You can customize the look and feel of the WhatsApp Dashboard directly from your settings.py. All of these settings are optional!
# Replace the text name in the sidebar (default: "WhatsApp")
META_WHATSAPP_DASHBOARD_NAME = "My Custom App"
# Change the Lucide icon used in the sidebar (default: "message-circle")
META_WHATSAPP_DASHBOARD_ICON = "building-2"
# Or, use a complete custom image logo instead of the icon
META_WHATSAPP_DASHBOARD_LOGO = "/static/logo.png"
# Change the primary accent color of the entire dashboard! (Provide an HSL triplet)
META_WHATSAPP_ACCENT_COLOR = "142, 72%, 45%" # Emerald green
Multi-Account Support
Add accounts in the Settings → Accounts dashboard. Each account has its own:
- Access Token
- Phone Number ID
- WABA ID
- Verify Token
The webhook endpoint (/whatsapp/webhook/) auto-routes to the correct account by phone_number_id.
Pluggable Audience System
The package never assumes your user model. You control who receives campaigns by injecting your own logic.
Option 1: Named Audience Providers
Provide a function that returns a queryset of recipients.
# myapp/whatsapp_audiences.py
from myapp.models import Customer
def vip_customers():
return Customer.objects.filter(total_orders__gt=10)
# settings.py
WHATSAPP = {
"PHONE_FIELD": "mobile", # field on your model that holds the phone number
"NAME_FIELD": "full_name", # field for the display name
"AUDIENCES": {
"VIP Customers": "myapp.whatsapp_audiences.vip_customers",
},
}
Option 2: Full Campaign Resolver
# settings.py
WHATSAPP = {
"CAMPAIGN_RESOLVER": "myapp.whatsapp_audiences.resolve_campaign",
}
# myapp.whatsapp_audiences.py
def resolve_campaign(campaign):
if campaign.audience_type == "vip":
qs = Customer.objects.filter(total_orders__gt=10)
return [{"phone": c.mobile, "name": c.full_name, "params": {}} for c in qs]
return []
Linking Your Django User Model
By default, WhatsAppContact is a standalone model. You can link it to your existing Django user model in one of three ways — all without modifying the package source.
Option 1: OneToOneField in Your Own Model
The cleanest pattern. Create a profile model in your own app that links to both your user and the WhatsApp contact.
Creating a WhatsApp Profile
# myapp/models.py
from django.db import models
from django.conf import settings
from django_meta_whatsapp.models import WhatsAppContact
class UserWhatsAppProfile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="whatsapp_profile"
)
contact = models.OneToOneField(
WhatsAppContact,
on_delete=models.SET_NULL,
null=True, blank=True,
related_name="user_profile"
)
def __str__(self):
return f"{self.user.username} — {self.contact}"
You can then access the contact from any user instance: request.user.whatsapp_profile.contact
Option 2: Auto-Link on User Registration (via Signals)
Automatically create or link a contact when a user registers, so new signups are immediately ready for WhatsApp campaigns.
Auto-Create Contact on User Registration
# myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from django_meta_whatsapp.models import WhatsAppContact
User = settings.AUTH_USER_MODEL
@receiver(post_save, sender=User)
def create_whatsapp_contact(sender, instance, created, **kwargs):
if created and instance.phone_number: # adjust field name to your User model
contact, _ = WhatsAppContact.objects.get_or_create(
phone=instance.phone_number,
defaults={"name": instance.get_full_name() or instance.username}
)
# Optionally link back
UserWhatsAppProfile.objects.get_or_create(user=instance, contact=contact)
Option 3: Use AUDIENCES to Query Your User Model Directly
Don't bother with WhatsAppContact at all — plug your own User queryset directly into campaigns using the audience provider system.
settings.py
WHATSAPP = {
"PHONE_FIELD": "phone_number", # field on your User model
"NAME_FIELD": "full_name", # field on your User model
"AUDIENCES": {
"All Users": "myapp.audiences.all_users",
"Premium Users": "myapp.audiences.premium_users",
},
}
myapp/audiences.py
from django.contrib.auth import get_user_model
User = get_user_model()
def all_users():
return User.objects.filter(is_active=True, phone_number__isnull=False)
def premium_users():
return User.objects.filter(is_active=True, subscription_tier="premium")
Querying Contacts Programmatically
Common Filter Patterns
from django_meta_whatsapp.models import WhatsAppContact, WhatsAppLabel
# All opted-in, unblocked contacts
contacts = WhatsAppContact.objects.filter(opted_out=False, is_blocked=False)
# Contacts with a specific label
vip_label = WhatsAppLabel.objects.get(name="VIP")
vip_contacts = contacts.filter(labels=vip_label)
# Contacts who signed up via a specific link
contacts.filter(subscribed_via_signup__display_name="Website Banner")
# Contacts created in the last 30 days
from django.utils import timezone
from datetime import timedelta
new_contacts = contacts.filter(created_at__gte=timezone.now() - timedelta(days=30))
# Get the linked Django User (if you've created the UserWhatsAppProfile model)
for contact in vip_contacts:
user = contact.user_profile.user # via related_name
print(user.email, contact.phone)
Contact Filter Presets for Campaigns
You can pre-define named audience filters in your settings.py. These appear as a friendly dropdown inside the Campaign form, so marketers don't need to write JSON manually.
Defining Filter Presets in settings.py
# settings.py
WHATSAPP = {
# ... other settings ...
"CONTACT_FILTERS": {
"VIP Customers": '{"labels__name": "VIP"}',
"Subscribed via Website": '{"subscribed_via_signup__isnull": false}',
"New This Month": '{"created_at__gte": "2024-06-01"}',
"Premium + Not Blocked": '{"labels__name": "Premium", "is_blocked": false}',
},
}
Keys are human-readable dropdown labels. Values are JSON strings of Django ORM filter() kwargs applied to WhatsAppContact.
How it Works in the Campaign Form
- Create a new Campaign and set Audience Type to
contacts. - A Filter Preset dropdown appears (only if you've defined
CONTACT_FILTERSin settings). - Select a preset and the Audience Filters JSON field is auto-populated.
- You can still manually edit the JSON for one-off custom queries.
Supported Filter Examples
Filter by Label
{"labels__name": "VIP"}
Filter by Multiple Labels
{"labels__name__in": ["VIP", "Premium"]}
Filter Contacts Created After a Date
{"created_at__gte": "2024-01-01"}
Exclude Opted-Out Contacts
{"opted_out": false, "is_blocked": false}
WhatsAppContact.objects.filter(**kwargs). Standard Django ORM lookups like __gt, __gte, __in, __isnull, __contains are all supported.
REST API Endpoints
You can interact with the package using standard HTTP REST APIs. All endpoints require an X-API-Key header (generate these in the dashboard under Settings → API Keys).
Endpoints
- POST
/whatsapp/api/send-message/- Send text message - POST
/whatsapp/api/send-location/- Send location pin - POST
/whatsapp/api/send-template/- Send approved template - GET
/whatsapp/api/chats/- List recent conversations - GET
/whatsapp/api/campaigns/- List campaigns
Send Text (cURL)
curl -X POST http://yourdomain.com/whatsapp/api/send-message/ \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"phone": "919876543210", "message": "Hello!"}'
Send Template (cURL)
curl -X POST http://yourdomain.com/whatsapp/api/send-template/ \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"phone": "919876543210",
"template_name": "order_confirmation",
"language": "en",
"body_params": ["Rakesh", "ORD-1234"]
}'
Django Signals
Hook into the WhatsApp lifecycle directly in your Django app.
from django.dispatch import receiver
from django_meta_whatsapp.signals import (
whatsapp_message_received,
whatsapp_message_sent,
whatsapp_campaign_completed,
)
@receiver(whatsapp_message_received)
def on_inbound(sender, message, **kwargs):
print(f"New message from {message.phone_number}: {message.message_body}")
@receiver(whatsapp_campaign_completed)
def on_campaign_done(sender, campaign, sent, failed, **kwargs):
print(f"Campaign '{campaign.name}' finished: {sent} sent, {failed} failed")
Management Commands
# Sync templates from Meta
python manage.py wa_sync_templates
# Sync for a specific account (if multi-account)
python manage.py wa_sync_templates --account-id 1
Models Reference
| Model | Purpose |
|---|---|
| WhatsAppAccount | Multi-account credentials |
| WhatsAppContact | Contact directory |
| WhatsAppConversation | Grouped chat threads |
| WhatsAppMessage | Individual messages (text, media, location, etc.) |
| WhatsAppTemplate | Meta-approved message templates |
| WhatsAppCampaign | Bulk send campaigns |
| WhatsAppCampaignRecipient | Per-recipient status tracking |
| WhatsAppMedia | Uploaded media library |
| WhatsAppWebhookLog | Raw webhook event logs |
| WhatsAppAPIKey | REST API authentication keys |
Environment Variables
If you prefer 12-factor app architecture, you can configure the package using environment variables instead of settings.py.
META_WA_ACCESS_TOKEN=EAAx...
META_WA_PHONE_NUMBER_ID=1234567890
META_WABA_ID=9876543210
META_WA_VERIFY_TOKEN=my_secret_verify_token