Python integration
Send WhatsApp messages with Django
A complete, runnable Django example for sending WhatsApp messages through the SPWP REST API. There is no SDK to install — Django talks to the API over ordinary HTTP, so you can paste this straight into an existing project.
About 5 minutes to your first delivered message
1. Setup
Create an API key in the dashboard under Accounts → your account → API keys, then make it available to your Django code as an environment variable. Never commit the key or ship it in client-side code.
pip install requests
# settings.py
SPWP_API_KEY = os.environ["SPWP_API_KEY"]
SPWP_WEBHOOK_SECRET = os.environ["SPWP_WEBHOOK_SECRET"]2. Send a text message
One POST with a recipient and a body. The API replies 202 Accepted with a message id in QUEUED state; delivery happens asynchronously and you can track it by id or webhook.
# notifications/spwp.py
import requests
from django.conf import settings
API_BASE = "https://api.spwp.app/api/v1"
def send_whatsapp_text(to: str, text: str) -> dict:
res = requests.post(
f"{API_BASE}/public/messages/text",
headers={"X-API-Key": settings.SPWP_API_KEY},
json={"to": to, "text": text},
timeout=10,
)
res.raise_for_status()
return res.json()["data"]
# Call it from a signal, a view, or a Celery task:
# send_whatsapp_text(order.customer_phone, f"Order {order.id} shipped.")3. Send an image, document or other media
Media endpoints take the same shape, with a source that is either a public HTTPS URL we fetch server-side or a base64 data URL you upload inline. Swap the path segment for video, audio, document or location.
def send_whatsapp_document(to: str, url: str, file_name: str) -> dict:
res = requests.post(
f"{API_BASE}/public/messages/document",
headers={"X-API-Key": settings.SPWP_API_KEY},
json={
"to": to,
"source": url,
"fileName": file_name,
"mimeType": "application/pdf",
},
timeout=30,
)
res.raise_for_status()
return res.json()["data"]4. Receive replies and delivery receipts
Inbound messages and status changes arrive as signed POSTs to your webhook URL. Recompute the HMAC over the timestamp and the raw request body — re-serialised JSON will not match — and reject anything older than five minutes.
# notifications/views.py
import hmac, hashlib, json, time
from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def spwp_webhook(request):
ts = request.headers.get("X-WA-Timestamp")
sig = request.headers.get("X-WA-Signature", "")
v1 = next((p[3:] for p in sig.split(",") if p.startswith("v1=")), None)
if not ts or not v1:
return HttpResponseBadRequest()
if abs(time.time() * 1000 - int(ts)) > 5 * 60_000:
return HttpResponseBadRequest()
expected = hmac.new(
settings.SPWP_WEBHOOK_SECRET.encode(),
f"{ts}.{request.body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(v1, expected):
return HttpResponseForbidden()
event = json.loads(request.body)
# …hand off to a task queue and return immediately…
return HttpResponse(status=200)Error handling
Errors carry a non-2xx status and a stable code string you can branch on: VALIDATION_FAILED, INVALID_API_KEY, ACCOUNT_NOT_CONNECTED, RATE_LIMITED, MEDIA_FETCH_FAILED. Retry on 429 while honouring Retry-After; treat 401 as a configuration problem rather than a transient one.
Next steps
The full reference covers every endpoint, field and error code, plus the webhook payload shapes.