Skip to main content

rmyndharis-openwa

This document describes SDK 0.7.0. Send idempotency, webhook failure redrive, message-window filters, poll choices, last-message types, and the event.response webhook event name require OpenWA 0.25.0 or later. The message.receipt and poll.vote webhook event names require OpenWA 0.26.0 or later.

Official Python SDK for OpenWA, the open-source WhatsApp API Gateway. OpenWA is an independent project, not affiliated with or endorsed by WhatsApp or Meta.

A synchronous client built on httpx, with bundled type hints (PEP 561).

Install

pip install rmyndharis-openwa

Requires Python 3.9+. The importable module is openwa.

Usage

from openwa import OpenWAClient

client = OpenWAClient(
    base_url="https://your-gateway.example.com",
    api_key="owa_k1_…",
)

# Sessions are addressed by the UUID that create() returns, not by name. Create a session once;
# afterwards, find its id with client.sessions.list({"name": "my-session"}).
session = client.sessions.create({"name": "my-session"})
client.sessions.start(session["id"])

# Link the account before sending: scan sessions.get_qr_code (or use sessions.request_pairing_code for a number with no linked session),
# then wait for status "ready". An unlinked session answers the send with 409.
result = client.messages.send_text(session["id"], {
    "chatId": "628123456789@c.us",
    "text": "Hello from the OpenWA Python SDK!",
})
print(result["messageId"])

The client is also a context manager (it closes the underlying connection pool on exit):

with OpenWAClient(base_url="…", api_key="…") as client:
    client.messages.send_text(session_id, {"chatId": "…@c.us", "text": "hi"})

For tests, pass an httpx transport — no global monkey-patching required:

import httpx
client = OpenWAClient(base_url="…", api_key="…", transport=httpx.MockTransport(handler))

GET /search is wrapped as client.search.search(params). Only q is required; the rest (sessionId, chatId, direction, type, from, dateFrom, dateTo, limit, offset) are optional. dateFrom / dateTo are epoch-ms. The active search provider (built-in DB full-text, or a plugin) answers; if none is configured the server returns 501.

res = client.search.search({"q": "invoice", "sessionId": session_id, "limit": 20})
for hit in res["hits"]:
    print(hit["snippet"], hit.get("score"))

score is provider-dependent and optional: the built-in provider on SQLite leaves it out for a key restricted to sessions or chats, so read it with get.

Messaging

Voice notes: pass ptt=True inside the body dict to send_audio to send a real WhatsApp voice note (PTT). Supply audio/ogg; codecs=opus audio for reliable playback; the server defaults the mimetype to that when ptt is set without one.

Errors

A non-2xx response raises a typed OpenWAApiError subclass — OpenWAAuthError (401), OpenWAForbiddenError (403), OpenWANotFoundError (404), OpenWAConflictError (409), OpenWARateLimitError (429), OpenWANotImplementedError (501), OpenWAServiceUnavailableError (503) — each carrying .status and the parsed .body. A timeout raises OpenWATimeoutError. 503 means the gateway could not complete the request right now: the path to WhatsApp failed, the gateway is draining or short of a dependency, or, on Baileys, WhatsApp throttled (code 429) or timed out (code 408) the request. Back off and retry later instead of at once, because repeating a throttled request straight away prolongs the throttle; a catalog 503 can persist because WhatsApp may never answer that query, so bound any retry. A 429 from the global rate limiter lifts when its window expires (seconds for the per-second tier, up to an hour for the hourly tier by default), and .retry_after_seconds carries its Retry-After header. A 429 whose .code is "SEND_PACING_LIMITED" is usually not transient: do not retry it before .retry_after_seconds, which then comes from the body: a few seconds when only sends still in flight caused it, the rest of the failure breaker's cooldown (SEND_PACING_BREAKER_COOLDOWN_MS, 15 minutes by default) after a run of send failures, otherwise up to the next UTC day. Every API error also exposes the response .headers. A 503 does not always prove a write was never carried out: when WhatsApp timed out, the change may still have been applied, so re-read the state before repeating it. A throttle on a send or a single write is the exception: it was turned away before it ran, so nothing was sent or applied, and a keyed send frees its Idempotency-Key. A group settings update with several fields applies them one at a time, so a 503 on a later field, a throttle included, leaves the earlier ones applied; its message lists them after already applied:, so re-read the settings before retrying, or send one field per request. In a routed deployment a forward that fails before reaching the owner node answers 503, one that fails after the request reached it answers 502 or 504, and a 503 from the owner itself is relayed unchanged.

from openwa import OpenWANotFoundError

try:
    client.sessions.get("00000000-0000-0000-0000-000000000000")
except OpenWANotFoundError as e:
    print(e.status)  # 404

Notes

  • Use HTTPS in production — the API key is sent as X-API-Key and is bearer-equivalent.
  • The SDK does not retry, and never follows redirects (so the key is never re-sent to a redirect target). Path segments are percent-encoded; a base-URL path prefix (e.g. behind a reverse proxy) is preserved.
  • Escape hatch for endpoints the SDK does not wrap: client.request(method, path, query=…, body=…).

Receiving webhooks

A webhook configured with a secret signs each delivery in its X-OpenWA-Signature header. Check it with verify_webhook_signature against the raw, unparsed request body (bytes or str), exactly as received, and parse the JSON only after the check passes: a re-serialized body can differ byte for byte and will not verify. The helper returns False (never raises) for a missing, malformed or non-matching signature. WebhookDelivery (in openwa.types) types the parsed body.

import json

from flask import Flask, request
from openwa import verify_webhook_signature
from openwa.types import WebhookDelivery

app = Flask(__name__)


@app.post("/openwa/webhook")
def openwa_webhook():
    raw_body = request.get_data()
    if not verify_webhook_signature(raw_body, request.headers.get("X-OpenWA-Signature"), secret):
        return "Invalid signature", 401
    delivery: WebhookDelivery = json.loads(raw_body)
    # Process delivery["event"] and delivery["data"] here.
    return "OK", 200

Releasing

Publishing to PyPI is done by the python-sdk-release.yml workflow, which authenticates with PyPI Trusted Publishing (OIDC). There is no PyPI token in the workflow or in the repository secrets: PyPI mints a short-lived credential from the GitHub OIDC token, so nothing long-lived exists to leak or rotate.

One-time setup, required before the first tag — on pypi.org, open the project's publishing settings and add a GitHub trusted publisher:

  • Owner: rmyndharis
  • Repository: OpenWA
  • Workflow name: python-sdk-release.yml

There are no repository secrets to add. Until the trusted publisher exists PyPI rejects the upload, so configure it first.

Cutting a release:

  1. Bump version in pyproject.toml and land it on main.
  2. Tag that commit py-sdk-v<version> (e.g. py-sdk-v0.7.0) and push the tag. The SDK has its own version line — the monorepo's v* tags are the app version and never trigger an SDK publish.
  3. The workflow re-runs the test suite, builds the sdist and wheel, and uploads. The artifacts published are the ones those tests passed against.

Send idempotency

The example below requires SDK 0.6.0 or later and OpenWA 0.25.0 or later. SDK 0.5.1 does not expose this per-send key argument.

Supply a unique caller-owned key for one logical send and reuse it when retrying that same call. Keys contain 1-255 visible ASCII characters; unrelated calls must use different keys. The SDK forwards the key without changing its retry policy or generating one automatically.

client.messages.send_text(session_id, {"chatId": "628123@c.us", "text": "Hello"}, idempotency_key="order-123")

The gateway supports text, image, video, audio, document, sticker, location, contact, template, poll, reply and forward sends. Completed calls replay their response; a 409 with the error code IDEMPOTENCY_KEY_IN_PROGRESS means the first request is still running, so retry with the same key later; a 409 IDEMPOTENCY_OUTCOME_UNKNOWN means the message may have been sent, so check the chat and, if it did not arrive, send again with a new key (the code tells them apart from the 409 for a session that is not connected). A changed request with the same key returns 422. The gateway retains keys for 24 hours. Bulk, product and button operations have no such guarantee. Omit the key to preserve existing behavior.

License

MIT

Metadata

Release files for rmyndharis-openwa 0.7.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 rmyndharis-openwa 0.7.0
File Size Uploaded Provenance Actions
rmyndharis_openwa-0.7.0.tar.gz 64.0 kB With provenance Details for rmyndharis_openwa-0.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rmyndharis-openwa 0.7.0
File Interpreter ABI Platform Actions
rmyndharis_openwa-0.7.0-py3-none-any.whl Python 3 none any Details for rmyndharis_openwa-0.7.0-py3-none-any.whl

Total release size: 117.1 kB

Release files / rmyndharis_openwa-0.7.0.tar.gz

Download URL rmyndharis_openwa-0.7.0.tar.gz
Size 64.0 kB
Tags Source
SHA-256 checksum
How to use checksums
ed7cb19810344dd0af271601c5bb9d58b097aa7bf33bc80faa53188915c375a2
BLAKE2b-256 checksum
How to use checksums
f32f054b5209896f5397751499d2773ee8718063dbd1890a17b4964f8447396e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 28, 2026.

Transparency log

Release files / rmyndharis_openwa-0.7.0-py3-none-any.whl

Download URL rmyndharis_openwa-0.7.0-py3-none-any.whl
Size 53.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fc0f6ef410abde63b6f9c4e7748fd652eb92a459f7bc0e96736d7f7e6622d84
BLAKE2b-256 checksum
How to use checksums
5834f22bcfa92cc44eaa8e321da23cdca445797e7551784be35ae87219fb31d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 28, 2026.

Transparency log

Security for rmyndharis-openwa 0.7.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rmyndharis_openwa-0.7.0.tar.gz

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 Oct 28, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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