converger (Python)
Server-side Python SDK for Converger: a REST client (sync and async, httpx),
a Protocol v1 WebSocket client with resume (websockets), Pydantic
models, and outbound webhook signature verification.
pip install converger
Python 3.10+. Full documentation: converger.aimtune.dev/sdk/python.
REST
from converger import Converger
cvg = Converger("https://chat.example.com", api_key="cvg_live_...", channel_secret="...")
# Client API (converger tokens): your backend mints a token for an end user
token = cvg.client_api.generate_token(user_id="user-42")
conv = cvg.client_api.create_conversation(token.token) # conversation-bound token
cvg.client_api.post_activity(conv.token, conv.conversation_id, text="Hi!", idempotency_key="msg-1")
# Tenant API (x-api-key): bots and CRMs
reply = cvg.tenant_api.post_activity(conv.conversation_id, sender="bot", text="Hello, how can I help?")
page = cvg.tenant_api.list_activities(conv.conversation_id)
AsyncConverger has the same methods as coroutines. Errors raise converger.ApiError with status, code,
message, details, request_id and retry_after, whatever error shape the server used.
WebSocket (Protocol v1)
import asyncio
from converger import AsyncConverger
async def main():
async with AsyncConverger("https://chat.example.com", channel_secret="...") as cvg:
token = await cvg.client_api.generate_token(user_id="user-42")
conv = await cvg.client_api.create_conversation(token.token)
async with cvg.socket(conv.token, conversation_id=conv.conversation_id) as sock:
ack = await sock.send_text("Where is my order?") # waits for the ack (id, seq)
async for frame in sock: # persistent frames in seq order, once
print(frame["type"], frame.get("data"))
asyncio.run(main())
The socket reconnects with jittered backoff, resumes from its watermark (sock.watermark; pass watermark= to
resume a stored position), re-sends unacknowledged sends with their original clientId, and answers gaps with
sync. Pass a callable as token to fetch a fresh token on every connect.
Webhook signature verification
from converger import WebhookSignatureError, verify_signature
verify_signature(headers, raw_body, secret) # raises WebhookSignatureError(reason)
Always pass the raw body bytes. secret may be a list while you rotate secrets; tolerance (default 300 s)
bounds the timestamp age.
FastAPI
from fastapi import FastAPI, HTTPException, Request
from converger import WebhookEvent, WebhookSignatureError, verify_signature
app = FastAPI()
@app.post("/converger/webhook")
async def webhook(request: Request):
body = await request.body()
try:
verify_signature(request.headers, body, CONVERGER_WEBHOOK_SECRET)
except WebhookSignatureError as exc:
raise HTTPException(status_code=401, detail=exc.reason)
event = WebhookEvent.model_validate_json(body)
# deduplicate on request.headers["x-converger-delivery-id"]
return {"ok": True}
Flask
from flask import Flask, abort, request
from converger import WebhookEvent, WebhookSignatureError, verify_signature
app = Flask(__name__)
@app.post("/converger/webhook")
def webhook():
body = request.get_data()
try:
verify_signature(request.headers, body, CONVERGER_WEBHOOK_SECRET)
except WebhookSignatureError:
abort(401)
event = WebhookEvent.model_validate_json(body)
return {"ok": True}
Development
pip install -e '.[test]'
pytest # unit + conformance (packages/conformance)
CONVERGER_FIXTURES=../conformance/integration/fixtures.json pytest -m integration
Models are hand-written today; scripts/generate_models.sh regenerates them from the OpenAPI document once
#45 ships priv/static/openapi.json.
Releases are tagged client-python/vX.Y.Z and published to PyPI by .github/workflows/sdk-python-release.yml.
Metadata
Release files for converger 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| converger-0.1.1.tar.gz | 21.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| converger-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.8 kB
Release files / converger-0.1.1.tar.gz
| Download URL | converger-0.1.1.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6282b75aeb22e6936f43fee720d4ee0b2a1fc4e07e153961b484a08a8ba6a817
|
|
BLAKE2b-256 checksum How to use checksums |
e30190eaff318fe23c7210f027e65fd17b029f681c693b4fc73f17e167b1dd9a
|
| 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 Oct 15, 2026.
Transparency logRelease files / converger-0.1.1-py3-none-any.whl
| Download URL | converger-0.1.1-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa82e3816bcba95aab81d206f1933458fa49960c8e60290be9b101fcf27d4f97
|
|
BLAKE2b-256 checksum How to use checksums |
f59a50989503ff82d2d008189f934343f31fb4d906a4a432d731eb9a2e027f20
|
| 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 Oct 15, 2026.
Transparency log