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.0
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.0.tar.gz | 21.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| converger-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.8 kB
Release files / converger-0.1.0.tar.gz
| Download URL | converger-0.1.0.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a14c21a9f20bb9e438fff9a7d0fd8e2f6b94a82394c1e075d94cb229971e820c
|
|
BLAKE2b-256 checksum How to use checksums |
7c1ffb054b6857d8dcc1fec28ed68271efcc057f7a29ce73f127fd994862ac0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / converger-0.1.0-py3-none-any.whl
| Download URL | converger-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dcb5e1c63140142728e752f531d5b4b1744a7eabd047ea07c59d511c01671fb9
|
|
BLAKE2b-256 checksum How to use checksums |
d68dc933a06e50302a3b98cb65f0d5d0365431a99e60d0379f62a2013bec71bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|