Skip to main content

grubgenie-waba-sdk

Python SDK for the GrubGenie WABA middleware. Send WhatsApp messages, manage templates, handle media, and receive inbound webhooks.

Full integration guide (API routes, webhook format, migration from direct Meta API): docs/integration-guide.md

Install

pip install grubgenie-waba-sdk

With FastAPI webhook support:

pip install grubgenie-waba-sdk[fastapi]

Quick Start

import os
from waba_sdk import WabaClient

waba = WabaClient(
    base_url="https://dev-wabamiddleware.grubgenie.ai",
    service_key=os.environ["WABA_SERVICE_KEY"],
)

Messages

# Text message
await waba.messages.send_text("phone_number_id", "919999999999", "Hello!")

# Template message
await waba.messages.send_template(
    "phone_number_id", "919999999999",
    template_name="hello_world",
    language_code="en_US",
)

# Media message (image, video, document, audio)
await waba.messages.send_media(
    "phone_number_id", "919999999999",
    media_type="image",
    media_url="https://example.com/photo.jpg",
    caption="Check this out",
)

# Interactive message (buttons, lists)
await waba.messages.send_interactive("phone_number_id", "919999999999", {
    "type": "button",
    "body": {"text": "Pick one"},
    "action": {
        "buttons": [
            {"type": "reply", "reply": {"id": "yes", "title": "Yes"}},
            {"type": "reply", "reply": {"id": "no", "title": "No"}},
        ]
    },
})

# Mark message as read
await waba.messages.mark_as_read("phone_number_id", "wamid.xxx")

# Raw send (full control over body)
await waba.messages.send("phone_number_id", {
    "messaging_product": "whatsapp",
    "to": "919999999999",
    "type": "text",
    "text": {"body": "raw"},
})

Templates

WABA_ID = "your_waba_id"
PN_ID = "phone_number_id"

# List templates (single page)
page = await waba.templates.list(PN_ID, WABA_ID)

# List all templates (auto-paginates)
all_templates = await waba.templates.list_all(PN_ID, WABA_ID)

# Get template by name
template = await waba.templates.get(PN_ID, WABA_ID, "hello_world")

# Get template by ID
template = await waba.templates.get_by_id(PN_ID, WABA_ID, "123456789")

# Create template
await waba.templates.create(PN_ID, WABA_ID, {
    "name": "order_update",
    "language": "en_US",
    "category": "UTILITY",
    "components": [{"type": "BODY", "text": "Your order {{1}} is ready."}],
})

# Update template
await waba.templates.update(PN_ID, WABA_ID, "123456789", {
    "components": [{"type": "BODY", "text": "Order {{1}} updated."}],
})

# Delete template
await waba.templates.delete(PN_ID, WABA_ID, "order_update")

Media

# Upload
result = await waba.media.upload("phone_number_id", file_bytes, "photo.png", "image/png")
media_id = result["id"]

# Download
data = await waba.media.download(media_id, "phone_number_id")

Phone Numbers

# List phone numbers for a WABA
numbers = await waba.phone_numbers.list("phone_number_id", "waba_id")

# Register a phone number
await waba.phone_numbers.register("phone_number_id", pin="123456")

Timeouts and errors

client = WabaClient(base_url, service_key, timeout=15.0)   # seconds; default 30, None disables (httpx default is 5 s)

await client.messages.send_text(pn_id, to, "hi", idempotency_key=job_id, timeout=30.0)   # per-call override
await client.accounts.unsubscribe(waba_id=waba_id, timeout=5.0)
  • Non-2xx raises WabaApiError (a RuntimeError): status_code, code, outcome, body, retry_after_ms.
  • A timeout raises WabaTimeoutError (a TimeoutError), not WabaApiError. For a send the outcome is unknown, so retry with the same idempotency key. httpx applies the value per phase (connect, write, read), not as a total deadline.
  • templates.list_all pages by cursor through the middleware and raises if any page fails; timeout applies per page.

Cloud API Proxy

Direct access to any WhatsApp Cloud API endpoint through the middleware:

response = await waba.proxy("phone_number_id", "GET", "{waba_id}/phone_numbers")
print(response.json())

Inbound Webhooks (FastAPI)

from fastapi import FastAPI, Request
from waba_sdk import WabaWebhookHandler

app = FastAPI()

async def on_event(event):
    context = event["context"]  # { partnerId, phoneNumberId, guest? }
    raw = event["raw"]          # original Meta webhook body
    # guest is present only when the sender matched a Guest for the partner
    print(f"Message from partner {context['partnerId']}, guest: {context.get('guest')}")

handler = WabaWebhookHandler(
    secret=os.environ["WABA_WEBHOOK_SECRET"],
    on_event=on_event,
)

@app.post("/webhooks/waba")
async def waba_webhook(request: Request):
    return await handler.handle(request)

WabaWebhookHandler raises ValueError on an empty secret and takes an optional on_error callback (the response is still a plain 500).

Signature Verification

from waba_sdk import verify_signature

is_valid = verify_signature(payload_bytes, signature_header, secret)

Requirements

  • Python >= 3.10
  • httpx >= 0.27
  • fastapi >= 0.115 (optional, for webhook handler)

Release files for grubgenie-waba-sdk 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for grubgenie-waba-sdk 0.9.0
File Size Uploaded
grubgenie_waba_sdk-0.9.0.tar.gz 12.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for grubgenie-waba-sdk 0.9.0
File Interpreter ABI Platform
grubgenie_waba_sdk-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 22.0 kB

Release files / grubgenie_waba_sdk-0.9.0.tar.gz

Download URL grubgenie_waba_sdk-0.9.0.tar.gz
Size 12.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9e9288139f1a55bd7e5b614110625c993e6244049cb464254d0307416b2a145c
BLAKE2b-256 checksum
How to use checksums
ea8d2d695717c291ac9b097e0a7cfca06747bcb37e5326a085584d29721de7cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / grubgenie_waba_sdk-0.9.0-py3-none-any.whl

Download URL grubgenie_waba_sdk-0.9.0-py3-none-any.whl
Size 9.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d9754466c3b3c908e922f2a8fee9bbf58232ae5e6cf03178b7e147fa2b92041e
BLAKE2b-256 checksum
How to use checksums
116ba494e2658cdf41109687f6aa9f0d856c968310eefc3106d1b81d58c28f2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page