# WhatsApp API with Python: a FastAPI webhook that replies

To use the WhatsApp API from Python, expose one HTTP route for the webhook and call a REST API to reply. No SDK is required: the signature is an HMAC-SHA256 you verify in a few lines, and every call is JSON over HTTPS.

## The app

```python
# main.py — pip install fastapi uvicorn httpx
import hashlib, hmac, json, os, time

import httpx
from fastapi import FastAPI, Request, Response

app = FastAPI()
API = "https://nodium.io/api/v1"


def verify_nodium(raw_body: bytes, header: str, secret: str) -> bool:
    parts = header.split(",")
    t = next((p[2:] for p in parts if p.startswith("t=")), None)
    signatures = [p[3:] for p in parts if p.startswith("v1=")]
    if not t or abs(time.time() - int(t)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(sig, expected) for sig in signatures)


@app.post("/webhooks/nodium")
async def nodium_webhook(request: Request):
    raw_body = await request.body()  # the raw bytes, before any JSON parsing
    header = request.headers.get("x-nodium-signature", "")
    if not verify_nodium(raw_body, header, os.environ["NODIUM_WEBHOOK_SECRET"]):
        return Response(status_code=400)

    event = json.loads(raw_body)
    if event["event"] == "message.received" and event["data"]["type"] == "text":
        async with httpx.AsyncClient() as client:
            await client.post(
                f"{API}/messages",
                headers={"Authorization": f"Bearer {os.environ['NODIUM_KEY']}"},
                json={
                    "channelId": event["data"]["channel_id"],
                    "to": event["data"]["from"],
                    "type": "text",
                    "text": f"You wrote: {event['data']['text']}",
                    "idempotencyKey": f"reply-{event['id']}",
                },
            )
    return Response(status_code=200)
```

Run it with `uvicorn main:app`, with `NODIUM_KEY` and `NODIUM_WEBHOOK_SECRET` in the environment.

## Tell Nodium where to call you

```bash
curl https://nodium.io/api/v1/webhooks \
  -H "Authorization: Bearer $NODIUM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-app.example.com/webhooks/nodium", "events": ["message.received"] }'
# → data.secret (whsec_…): shown once
```

## Next

- Write first to a customer with an approved [template](https://nodium.io/en/features/templates-broadcasts): free text only goes out within the [24-hour window](https://nodium.io/en/guides/24-hour-window).
- Answer `200` quickly and do slow work afterwards: an event that is not acknowledged is delivered again, with the same `id`.
- Connect your customers' numbers with [one key for all of them](https://nodium.io/en/features/multi-tenant).

## Frequently asked questions

### Is there a Python SDK?

Not yet. The API is plain REST, described in OpenAPI 3.1, and the reference carries a Python example for every route.

### Does it work with Django or Flask?

Yes. Only two things matter: verify the signature on the raw request body, before any JSON parsing, and answer 200 quickly.

Updated 2026-11-10 · https://nodium.io/en/guides/whatsapp-api-python
