All integration guides

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-… QUEUED

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_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 "", 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.

Other languages and frameworks