Workspace Events
Status: Implemented. Workspace
Events deliver resource-change notifications (NOT Chat interactions) as
CloudEvents through Pub/Sub push. They have an independent
EventsRouter/EventsDispatcher runtime; WorkspaceEvent is deliberately
not an ordinary chattice.events.Event.
The wire format (official Pub/Sub binding)
Google delivers Workspace Events exclusively as Pub/Sub push messages.
The CloudEvents context attributes travel in message.attributes with
ce- keys, while base64-decoded message.data holds ONLY the event
resource data (or resource names for names-only payloads):
{
"message": {
"data": "eyJtZXNzYWdlIjp7Im5hbWUiOiJzcGFjZXMvQUFBL21lc3NhZ2VzL0IifX0=",
"messageId": "m-2",
"attributes": {
"ce-id": "evt-1",
"ce-source": "//chat.googleapis.com/spaces/AAA",
"ce-specversion": "1.0",
"ce-time": "2000-01-01T00:00:00Z",
"ce-type": "google.workspace.chat.message.v1.created"
}
},
"subscription": "projects/p/subscriptions/s"
}
parse_workspace_envelope(payload) validates the envelope
(WorkspaceEventError: required ce-id/ce-source/ce-specversion/
ce-type, specversion 1.0, google.workspace.-prefixed type,
application/json datacontenttype, base64+JSON data) and produces a
WorkspaceEvent domain event: event_id, source, subject,
event_time, data, cloud_type — with event_type="workspace_event"
and the FULL push envelope in raw.
parse_workspace_event(payload) still accepts a STRUCTURED CloudEvent
(all fields at the top level) for offline use (fixtures, replays, tests) —
a structured CloudEvent POSTed to the push endpoint is NOT a supported
delivery mode and is rejected with 400.
Type constants and forward compatibility
WorkspaceEventType documents the current stable Chat types (verified
against the official event guides): message
created/updated/deleted, reaction created/deleted, membership
created/updated/deleted, space updated/deleted, space/thread read-state
updated, availability updated, and the subscription lifecycle types
(suspended / expirationReminder / expired).
Parsing requires only the google.workspace. prefix — unknown
google.workspace.* types (including OUTPUT-ONLY batch variants like
*.batchCreated / *.batchUpdated / *.batchDeleted, which Google
delivers automatically alongside single-resource subscriptions) parse
fine (forward compatibility), so new Google types never break an app;
string filters simply don't match them until the app registers a
handler. NOTE: WorkspaceEventType enumerates the SINGLE-RESOURCE
set, not the complete official set — batch types are preserved
through the generic parser and routable by raw string.
Independent events runtime
EventsRouter.workspace_event accepts a string shortcut that filters on the
CloudEvent type:
from chattice.workspace_events import (
EventsDispatcher,
EventsRouter,
WorkspaceEvent,
WorkspaceEventType,
)
router = EventsRouter()
@router.workspace_event(WorkspaceEventType.MESSAGE_CREATED)
async def on_created(event: WorkspaceEvent) -> None: ...
@router.workspace_event() # any google.workspace.* type
async def on_any(event: WorkspaceEvent) -> None: ...
dispatcher = EventsDispatcher()
dispatcher.include_router(router)
await dispatcher.feed_event(event)
The interaction Router has no workspace_event observer, and interaction
Dispatcher.feed_update() rejects WorkspaceEvent; applications use the
separate runtime and call feed_event() instead. Handlers must be defined at
module level (annotation
resolution). The HTTP surface is create_workspace_events_router (204 ack,
400 malformed, no sync response channel) and now accepts an
EventsDispatcher. The router is secure by default: pass
verifier=GooglePubSubVerifier(...) (authenticated push) or an explicit
allow_unverified=True; with idempotency_storage redeliveries dedupe by
the Pub/Sub message id with claim/complete/release semantics.
Subscriptions via the Workspace Events API
Subscriptions are created OUTSIDE this framework, through the Workspace Events REST API. A subscription combines:
targetResource— the resource to watch (e.g. a space);eventTypes— thegoogle.workspace.*types to deliver;notificationEndpoint— a Pub/Sub topic the app owns (the ONLY supported endpoint kind; there is no HTTPS delivery mode);payloadOptions— payload filtering (full data or names only).
Subscriptions EXPIRE (hours to days) and must be renewed; Google also
sends lifecycle events (google.workspace.events.subscription.v1.*) and
recommends querying missed events via spaces.spaceEvents.get/list after
outages. The app's push endpoint receives the CloudEvents on the topic.
Applications provide their own client for subscription creation and renewal;
the framework does not include a lifecycle manager.
Workspace Events vs interaction events
Workspace Events describe resource changes (message created/updated/deleted, space updated, membership changed, reactions, read states). They can be broad — many spaces, no interaction context — and there is NO synchronous response: no ack of the user's action, no dialog, no sync card update. Interaction events are single-request/single-response dialogues (a message the user typed, a card click) over the HTTP transport. The two ingresses are isolated end-to-end: separate domain models, adapters, observers, dispatchers, routers, and tests pin that neither runtime accepts the other family.