hibira
Python server library for Hibira, hosted realtime that speaks the Pusher Channels protocol. Publish events, authorize private and presence channels, verify webhooks, send push notifications, ask a person for an approval, and stream LLM output to the browser as it is written.
pip install hibira
Python 3.9+, one dependency (httpx). Both a
regular and an asyncio client. pusher-js, Laravel Echo and the
@hibira-io/* front-end packages connect to Hibira unchanged.
Set up
Create an app at hibira.io/portal (the free plan needs no card) and put its keys in the environment:
HIBIRA_APP_ID=...
HIBIRA_APP_KEY=...
HIBIRA_APP_SECRET=...
import hibira
client = hibira.Client.from_env() # or hibira.Client(app_id, key, secret)
client.trigger("orders", "created", {"id": 1042})
async with hibira.AsyncClient.from_env() as client:
await client.trigger(["orders", "dashboard"], "created", {"id": 1042})
| Setting | Environment | Default |
|---|---|---|
app_id, key, secret |
HIBIRA_APP_ID, HIBIRA_APP_KEY, HIBIRA_APP_SECRET |
|
host |
HIBIRA_HOST |
ws.hibira.io (or the host shown on your app page) |
port, use_tls |
HIBIRA_PORT, HIBIRA_USE_TLS |
443, True |
push_instance_id, push_secret_key |
HIBIRA_PUSH_INSTANCE_ID, HIBIRA_PUSH_SECRET_KEY |
|
push_endpoint |
HIBIRA_PUSH_ENDPOINT |
https://<host> |
timeout |
10.0 seconds |
Keyword arguments to from_env() win over the environment. Both clients
accept http_client= to reuse your own httpx.Client / httpx.AsyncClient.
Stream LLM output to the browser
stream_to_channel sends a stream of text to a channel as it is produced, in
the format useStream() in @hibira-io/react
and @hibira-io/vue puts back together.
Tokens are batched (about 10 events a second rather than one per token), each
event stays under the plan's message size, and the last event carries the
whole text when it fits, so people who open the page late catch up. With
channel history on (paid plans), useStream(channel, { rewind: true })
replays a stream after a reload.
from openai import OpenAI
import hibira
client = hibira.Client.from_env()
openai = OpenAI()
stream = openai.chat.completions.create(model="gpt-5-mini", messages=messages, stream=True)
result = hibira.stream_to_channel(client, f"private-conversation-{conversation_id}", stream, meta={"role": "assistant"})
save_message(conversation_id, result.text)
// React
const { text, done } = useStream(`private-conversation-${conversationId}`);
text_of reads the text out of chunks from the common SDKs, so you can pass
their streams as they are:
| Source | Pass |
|---|---|
| OpenAI chat completions | client.chat.completions.create(..., stream=True) |
| OpenAI Responses | client.responses.create(..., stream=True) |
| Anthropic | client.messages.create(..., stream=True) (or the async client) |
| OpenAI Agents SDK | Runner.run_streamed(agent, input).stream_events() |
| LangGraph / LangChain | graph.astream(input, stream_mode="messages"), llm.astream(...) |
| Gemini (google-genai) | client.models.generate_content_stream(...) |
| Anything else | an iterable of str or bytes, or pass pick=lambda chunk: ... |
With asyncio, use astream_to_channel (it takes async or plain iterables,
and an AsyncClient or a Client):
async def answer(conversation_id: str, question: str) -> str:
stream = graph.astream({"messages": [("user", question)]}, stream_mode="messages")
result = await hibira.astream_to_channel(client, f"private-conversation-{conversation_id}", stream)
return result.text
To stream only one LangGraph node, filter with pick:
pick=lambda c: hibira.text_of(c) if c[1].get("langgraph_node") == "agent" else "".
| Option | Default | |
|---|---|---|
id |
random UUID | stream id, also in the result |
event |
"ai-stream" |
event name |
interval |
0.1 |
seconds to gather tokens before sending them |
max_bytes |
3500 |
largest event payload (Free plans allow 4 KB per message) |
meta |
JSON sent with the first event (model, message id, author) | |
pick |
text_of |
text of one chunk |
socket_id |
leave out this connection (the sender's own tab) | |
stop |
a threading.Event; set it to end the stream with error "aborted" (sync only; cancel the task with asyncio) |
If the source raises, the final event carries the error (useStream shows it)
and the exception is raised again. Both return a StreamResult(id, text, events).
Authorize private and presence channels
client.authenticate_channel("private-user-42", socket_id)
# {"auth": "key:signature"}
client.authenticate_channel("presence-room-7", socket_id, {"user_id": "42", "user_info": {"name": "Ann"}})
# {"auth": "...", "channel_data": "{\"user_id\":\"42\",...}"}
client.authenticate_user(socket_id, {"id": "42", "name": "Ann"}) # user sign-in
Return the dict as JSON from the endpoint pusher-js calls
(channelAuthorization.endpoint). It receives socket_id and
channel_name as form fields. These methods need no network and are plain
functions on AsyncClient too.
FastAPI
pip install "hibira[fastapi]"
from fastapi import Depends, FastAPI
from hibira import AsyncClient
from hibira.fastapi import auth_router, webhook
client = AsyncClient.from_env()
app = FastAPI()
async def authorize(request, channel: str):
user = await current_user(request)
if channel == f"private-user-{user.id}":
return True
if channel.startswith("presence-room-"):
return {"user_id": str(user.id), "user_info": {"name": user.name}}
return False
app.include_router(auth_router(client, authorize)) # POST /hibira/auth
@app.post("/hibira/webhook")
async def on_webhook(payload: dict = Depends(webhook(client))):
for event in payload["events"]:
...
auth_router(client, authorize, user=...) also answers user sign-in at
/hibira/user-auth. Django support is in
django-hibira.
Webhooks
payload = client.verify_webhook(
request.headers.get("X-Pusher-Key"),
request.headers.get("X-Pusher-Signature"),
request.body, # the raw body, bytes or str
)
Raises hibira.InvalidWebhook when the key or signature does not match.
Channels API
client.trigger_batch([{"channel": "a", "name": "e", "data": {"x": 1}}])
client.channels(prefix="presence-", info="user_count")
client.channel_info("presence-room-7", info=["user_count", "subscription_count"])
client.users("presence-room-7")
client.terminate_user_connections("42")
trigger takes one channel or a list of up to 100. Data that is not a str
is sent as JSON. socket_id= leaves out the sender's own connection.
Push notifications
Turn on Push for the app in the portal and set HIBIRA_PUSH_INSTANCE_ID and
HIBIRA_PUSH_SECRET_KEY. Bodies use the Beams format.
client.push_to_interests(
["news"],
{
"web": {"notification": {"title": "New post", "deep_link": "https://example.com/p/1"}},
"fcm": {"notification": {"title": "New post"}},
"apns": {"aps": {"alert": {"title": "New post"}}},
},
)
client.push_to_users(["42"], {...})
client.push_token("42") # {"token": ...} for setUserId in the web, Android and iOS SDKs
client.delete_push_user("42")
Approvals
Ask a person for a decision from a script, worker or agent. Hibira hosts the answer page.
approval = client.request_approval(
"Refund order #1042 for $89?",
details="Customer says the parcel never arrived.",
options=["Refund", "Decline"], # the first one approves
push_interests=["ops"], # or push_users=[...], or share approval["url"]
channel="private-hibira-approvals.ops", # shows in the @hibira-io/approvals inbox
)
result = client.wait_for_approval(approval["approval_id"], timeout=3600)
if result["status"] == "approved":
refund(1042)
status is pending, approved, rejected or expired. get_approval(id, wait=25)
reads it once, waiting up to 25 seconds for an answer.
Errors
Every error is a hibira.HibiraError. HTTP errors are hibira.HTTPError
(status, message, body), with subclasses AuthenticationError (401,
403), NotFound (404), PayloadTooLarge (413, the event is over the plan's
message size) and RateLimited (429). Network failures (DNS, connect,
TLS, proxy, timeout) raise hibira.ConnectionError. Missing settings raise
ConfigError.
Development
python -m venv .venv && .venv/bin/pip install -e . pytest fastapi ruff mypy
.venv/bin/pytest
.venv/bin/ruff check . && .venv/bin/mypy
tests/test_e2e.py runs against a real server when HIBIRA_E2E=1 and the
HIBIRA_* settings point at it (it needs pip install websockets).
中文摘要
hibira 是 Hibira 的 Python 伺服器端套件(Python 3.9 以上,同步與 asyncio 兩種
client):發送事件、私人 / presence 頻道授權、驗證 webhook、推播通知、人工核准,
以及用 stream_to_channel 把 LLM 的輸出即時串流到瀏覽器(React / Vue 的
useStream() 直接接收)。支援 OpenAI、Anthropic、OpenAI Agents SDK、LangGraph、
Gemini 的串流格式,並附 FastAPI 授權路由與 webhook 驗證。
License
MIT
Metadata
Release files for hibira 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 | Provenance | Actions |
|---|---|---|---|---|
| hibira-0.1.0.tar.gz | 25.6 kB | With provenance | Details for hibira-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| hibira-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for hibira-0.1.0-py3-none-any.whl |
Total release size: 46.4 kB
Release files / hibira-0.1.0.tar.gz
| Download URL | hibira-0.1.0.tar.gz |
|---|---|
| Size | 25.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99ed533a0d919316d3bc957c51e176ce203227146f75c543d43db1f0887c3716
|
|
BLAKE2b-256 checksum How to use checksums |
57b7ac1cba9f2ce767749acf455cfbc68e5018584e3e24e747e5877d5ac9418b
|
| 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 Nov 1, 2026.
Transparency logRelease files / hibira-0.1.0-py3-none-any.whl
| Download URL | hibira-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d58470ba88c4a7279fae97b27827b63fe9135159c915b8b24f542ab5160437e
|
|
BLAKE2b-256 checksum How to use checksums |
a9588debbf4fc487cad9c9074674cbd9e091bd9dfa56d81fcbb759de1006efbe
|
| 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 Nov 1, 2026.
Transparency logSecurity for hibira 0.1.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
Both files in this release have been uploaded with provenance metadata.
Attestation for hibira-0.1.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 Nov 1, 2026.
Transparency logA 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.