Core Concepts
This page explains the mental model behind Astra: how the pieces fit together,
what happens when you call client.start(), and why things are designed the
way they are.
The Client lifecycle
Every Astra program revolves around a single Client
instance. The client moves through a predictable set of states:
OFFLINE --> STARTING --> AUTHENTICATING --> READY
|
SHUTTING_DOWN
OFFLINE – the client object exists but nothing is running.
STARTING – the browser is launching, the bridge is being injected.
AUTHENTICATING – waiting for QR scan or phone pairing.
READY – fully connected. Events flow, commands work.
SHUTTING_DOWN – browser and resources are being released.
You can check the current state at any time with client.status.
The Protocol Bridge
Astra does not speak the WhatsApp protocol directly. Instead it:
Opens WhatsApp Web in a real Chromium browser (via Playwright).
Injects a JavaScript engine (
AstraEngine) into the page.Communicates with that engine through Playwright’s
page.evaluate()calls.
This means Astra always speaks to the same WhatsApp Web code that your browser does – no reverse engineering of encrypted protocols.
Python (your code)
|
v
Client --> ProtocolBridge --> page.evaluate()
|
v
WhatsApp Web (JS)
|
v
WhatsApp servers
The bridge is self-healing: if the page reloads or the JS context is lost, Astra re-injects the engine automatically.
Sessions
When you authenticate for the first time, WhatsApp creates a linked device
entry on your phone. Astra persists this session on disk (in
.astra_sessions/<session_id>/) so you can restart without scanning again.
Each session is identified by its session_id string. You can run multiple
bots on the same machine by using different session IDs:
bot_a = Client(session_id="personal")
bot_b = Client(session_id="business")
The SyncEngine
After authentication, the SyncEngine runs in the background and handles:
Heartbeats – periodic checks that the bridge is still alive.
Stall detection – if no events arrive for 90 seconds, Astra re-binds
listeners and, if needed, restarts the bridge.
Cache synchronization – keeping a local SQLite cache of chats and contacts
in sync with the browser.
You rarely interact with the SyncEngine directly, but you can inspect its diagnostics at any time:
diag = client.sync_engine.get_diagnostics()
print(diag) # uptime, WA version, heartbeat counts, etc.
Events and handlers
Astra uses a decorator-based event system. You register handlers on the client and they fire whenever a matching event arrives:
@client.on_message(Filters.text_contains("hello"))
async def greet(msg):
await msg.reply("Hey there!")
The event pipeline:
WhatsApp Web fires a JS event (new message, status change, etc.).
The bridge forwards it to Python via an exposed callback.
The EventDispatcher matches it against registered filters.
Your handler function runs with the matched event context.
See Event System and Filters for the full story.
Error model
Every error Astra raises is a subclass of AstraError.
Each exception carries:
code– a structured code likeE3001(message send failed).hint– a human-readable suggestion for fixing the problem.retryable– whether the operation can be safely retried.severity–INFO,WARNING,ERROR, orFATAL.
from astra.errors import MessageSendError
try:
await client.chat.send_message(jid, "Hello")
except MessageSendError as e:
print(e.code, e.hint, e.retryable)
See Error Codes for the complete catalogue.
The context manager
The cleanest way to use Astra is as an async with block. This ensures
the browser is always closed, even if your code crashes:
async with Client(session_id="demo") as client:
# client.start() has been called for you
await client.run_forever()
# client.stop() is called automatically here