Language: English
AppSync Events Doesn't Carry State — Use It as a Refetch Signal
When implementing screen update notifications with AWS AppSync Events, we chose a design where events act purely as signals triggering refetches instead of carrying state. This article explains why, how consistency is preserved with the database as the source of truth, and how to combine it with the outbox pattern.
A requirement as modest as “show updates on screen” suddenly gets heavy once you implement it.
You stand up a WebSocket or SSE server, manage connection state, implement fanout. AuthN and authZ apply to connections too, plus reconnection, timeouts, and synchronization across instances. All we actually wanted was to deliver “something changed.”
What Cloud Tasks / Pub/Sub Cover
Cloud Tasks excels at deferred execution, HTTP tasks, retries, and rate control. As a mechanism for doing work later, it’s well designed.
Pub/Sub is an event backbone between backends, well suited to keeping services loosely coupled.
Neither reaches the browser. The last mile is left open.
AppSync Events fills that gap. It’s an Event API usable without GraphQL: publish over HTTP or WebSocket, subscribe over WebSocket. Routing is by channel and namespace, and authorization can be an API key, IAM, Cognito, OIDC, or a Lambda authorizer. Roughly speaking, it’s serverless WebSocket pub/sub.
Events as Signals
There’s exactly one key design decision.
With Events, you don’t carry state; you trigger a refetch.
event received
-> invalidate query
-> refetch from API
-> render canonical state
The DB and API are the source of truth, and Events stays a pure signal.
Two reasons for sending signals only.
On security: nothing sensitive rides on the event layer. Even if it leaks, all that’s exposed is “something updated.” Fetching the payload is pushed down to ordinary API authorization.
On consistency: even if events are lost, the DB remains correct. After reconnecting, a refetch converges the UI, so the UI doesn’t depend solely on push. We avoid having to build delivery guarantees into the client.
An LLM Chat Example
User
-> Backend: send message
<- Backend: 202 Accepted
Backend
-> DB: save user message
-> LLMs: parallel request
-> DB: save model responses
-> AppSync Events: publish { sessionId, type }
Frontend
<- AppSync Events: update signal
-> API: refetch messages
<- API: canonical state
Persist the user’s message and return 202 right away. Queries to the models go out asynchronously, in parallel. Once responses are saved, publish only the fact that something changed.
The generated LLM output itself never flows through Events. The frontend receives the notification and refetches the list.
Combining with the Outbox Pattern
Where publishing must be reliable, insert an outbox row in the same transaction as the write, and a relay reads it and publishes to AppSync Events. Reliability lives in the DB and the outbox; fanout lives in AppSync Events.
I wrote up the outbox pattern in detail, in the context of confirming audit log delivery, in Who Confirms That Audit Logs Actually Arrived?
Pricing and Operations
AppSync Events pricing is $1.00 per million Event API operations and $0.08 per million connection minutes. Inbound and outbound traffic is metered in 5KB increments.
The comparison point is holding WebSockets on Cloud Run. While a connection is open, the instance counts as active, so billing continues for that duration. You need request timeout and reconnection handling, plus state synchronization across instances.
If all you want is the notification line, the area you manage shrinks considerably.
When to Use What
| What you want | Reach for |
|---|---|
| Deferred execution, retries, rate control | Cloud Tasks / SQS |
| Durable eventing between backends | Pub/Sub / EventBridge |
| Real-time notifications to the browser | AppSync Events |
| Fetching the correct state for a screen | Regular APIs and the DB |
LLM UIs reporting generation completion or partial updates, dashboards showing job progress or aggregate updates, lightweight collaboration like comments and presence — these are the good fits.
As a rule of thumb, thinking of AppSync Events as invalidateQueries as a Service makes it hard to get the design wrong.
References
- AWS AppSync Events documentation: https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html
- AWS AppSync pricing: https://aws.amazon.com/appsync/pricing/
- Cloud Run WebSockets documentation: https://docs.cloud.google.com/run/docs/triggering/websockets
Based on an LT talk themed around AppSync Events.