HTTP and Pub/Sub
The same interaction parser and Dispatcher sit behind both transports, but
their response capabilities are not interchangeable.
| Behavior | HTTP interactions | Pub/Sub push / streaming pull |
|---|---|---|
| Delivery | HTTPS request | Pub/Sub message |
| Ack | HTTP response | 2xx push ack or subscriber ack |
| Synchronous text/card response | yes, within the interaction deadline | no |
| Dialog and App Home | yes, for eligible events | no |
Outbound Bot calls |
yes | yes |
| Handler return value | serialized to Chat | ignored (push) / mapped outbound where supported (pull runner) |
HTTP
Use create_chat_router(dispatcher, GoogleTokenVerifier(...)). Verification is
fail-closed and failures return 401. Handler returns are serialized by event
type, including sender-sensitive card updates and HTTP-only dialogs/App Home.
Pub/Sub push
Use create_pubsub_router with a GooglePubSubVerifier. Pub/Sub expects an
ack, not a Chat response, so handler results cannot answer the interaction.
Use an injected Bot for a later message or update. Failed deliveries can be
retried; make effects idempotent.
Pub/Sub streaming pull
Install chattice[pubsub]. The full application is a long-lived process —
visually familiar if you come from aiogram's start_polling, but
technologically it is a persistent Pub/Sub subscriber stream with
acknowledgements and delivery attempts, NOT Telegram-style long polling
(Google Chat has no polling API):
import asyncio
import os
from chattice import Dispatcher, Router
from chattice.auth import ServiceAccountCredentialsProvider
from chattice.client import Bot
from chattice.events import MessageEvent
router = Router()
@router.message()
async def hello(message: MessageEvent) -> None:
await message.reply("Hello from Google Chat!")
async def main() -> None:
service_account_file = os.environ["CHATTICE_SERVICE_ACCOUNT_FILE"]
app_credentials = ServiceAccountCredentialsProvider.from_service_account_file(
service_account_file
)
pull_credentials = ServiceAccountCredentialsProvider.from_service_account_file(
service_account_file, scopes=["https://www.googleapis.com/auth/pubsub"]
)
async with Bot(app_credentials_provider=app_credentials) as bot:
dispatcher = Dispatcher(bot=bot)
dispatcher.include_router(router)
await dispatcher.run_pubsub(
os.environ["CHATTICE_SUBSCRIPTION"],
bot=bot,
credentials_provider=pull_credentials,
)
if __name__ == "__main__":
asyncio.run(main())
Set CHATTICE_SERVICE_ACCOUNT_FILE and CHATTICE_SUBSCRIPTION, then run
python app.py. The service account needs the Pub/Sub Subscriber IAM role;
the example supplies subscriber and Chat credentials separately. The method name is deliberately explicit:
run_pubsub names the concrete technology instead of hiding the transport
behind a generic start(). Dialog and App Home responses remain
unsupported on Pub/Sub.
Choose HTTP for interactive UI. Choose Pub/Sub when infrastructure policy or behind-VPN consumption requires it and the application can respond through authenticated Chat API calls.
Next: Authentication and capabilities.
Why Pub/Sub push when HTTP already has a public endpoint?
Push is a webhook with a durable queue in front of it:
- Delivery guarantees — if your endpoint is down, Pub/Sub buffers events (retries with backoff, dead-letter support) instead of losing them like a direct webhook eventually does.
- At-least-once + duplicates — Pub/Sub guarantees at-least-once
delivery, so duplicates are normal; use the built-in idempotency
storage in
create_pubsub_router. - Backpressure — the topic absorbs bursts; your endpoint does not have to keep up with the interaction deadline.
- One topic, many consumers — attach extra subscriptions (compliance, analytics, a second bot) without touching the Chat configuration.
- Serverless-friendly — push works with Cloud Run/Functions that only serve HTTPS when called; pull requires a long-lived process.
Rule of thumb: HTTP for interactive UI (dialogs, synchronous card updates); Pub/Sub push for durable delivery to a fragile/sleeping public endpoint; Pub/Sub pull for no-public-endpoint deployments.