Python integration
Send WhatsApp messages with Python
A complete, runnable Python example for sending WhatsApp messages through the SPWP REST API. There is no SDK to install — Python 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 Python code as an environment variable. Never commit the key or ship it in client-side code.
pip install requests
export SPWP_API_KEY="wak_live_your_key_here"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.
import os
import requests
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": os.environ["SPWP_API_KEY"]},
json={"to": to, "text": text},
timeout=10,
)
res.raise_for_status()
return res.json()["data"]
message = send_whatsapp_text("9647503505440", "Your verification code is 482310")
print(message["id"], message["status"]) # -> 9b1d7f6c-… QUEUED3. 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_image(to: str, source: str, caption: str | None = None) -> dict:
res = requests.post(
f"{API_BASE}/public/messages/image",
headers={"X-API-Key": os.environ["SPWP_API_KEY"]},
json={"to": to, "source": source, "caption": caption},
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.
import hmac, hashlib, os, time
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/spwp")
def webhook():
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:
abort(400)
# Replay protection: reject anything older than five minutes.
if abs(time.time() * 1000 - int(ts)) > 5 * 60_000:
abort(400)
secret = os.environ["SPWP_WEBHOOK_SECRET"].encode()
expected = hmac.new(
secret,
f"{ts}.{request.get_data(as_text=True)}".encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(v1, expected):
abort(401)
event = request.get_json()
print(event["event"], event["data"]["id"])
return "", 200Error 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.