Documentation / Overview

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 labels column 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

  1. Create a new Campaign and set Audience Type to contacts.
  2. A Filter Preset dropdown appears (only if you've defined CONTACT_FILTERS in settings).
  3. Select a preset and the Audience Filters JSON field is auto-populated.
  4. 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}
Note: All filters are passed directly to Django's 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
WhatsAppAccountMulti-account credentials
WhatsAppContactContact directory
WhatsAppConversationGrouped chat threads
WhatsAppMessageIndividual messages (text, media, location, etc.)
WhatsAppTemplateMeta-approved message templates
WhatsAppCampaignBulk send campaigns
WhatsAppCampaignRecipientPer-recipient status tracking
WhatsAppMediaUploaded media library
WhatsAppWebhookLogRaw webhook event logs
WhatsAppAPIKeyREST 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

Developer & Author

Rahul Baberwal

Rahul Baberwal

Creator of django-meta-whatsapp. Passionate about building robust tools for developers.

Find this useful?

Support the project by leaving a star or forking it on GitHub!