GraphQL Subscriptions
FLXBL GraphQL subscriptions stream entity lifecycle events to applications over WebSockets. Use them when a user-facing screen should react as data is created, updated, or deleted without polling.
When To Use Subscriptions
- Live feeds: append new records as soon as another user or process creates them.
- Dashboards: refresh operational counters and tables after entity changes.
- Collaborative screens: update visible records when another session edits them.
- Notifications: show in-app notices for important entity events.
- Client cache refresh: invalidate or refetch local data after a matching event.
Durable server workflows: Use webhooks when another backend must receive a signed event even if it is offline. Use subscriptions for live clients that can reconnect and resubscribe.
Generated Subscription Fields
For each entity in your active schema, FLXBL generates lower-camel-case create, update, and delete subscription fields.
# Generated for every entity in your active schema
type Subscription {
productCreated: Product!
productUpdated(id: ID): Product!
productDeleted(id: ID): ID!
# Same lower-camel-case pattern for other entities:
# customerCreated, customerUpdated, customerDeleted
# blogPostCreated, blogPostUpdated, blogPostDeleted
}Basic Operations
Created and updated events return the selected entity fields. Delete events
return the deleted entity id. The current implementation supports an
optional id argument for update and delete subscriptions.
# Receive every new Product as it is created
subscription WatchProductCreates {
productCreated {
id
name
price
createdAt
}
}
# Receive updates for one Product
subscription WatchOneProduct($id: ID!) {
productUpdated(id: $id) {
id
name
price
updatedAt
}
}
# Variables
{
"id": "node_abc123"
}
# Receive delete events for one Product
subscription WatchProductDelete($id: ID!) {
productDeleted(id: $id)
}Event Payloads
FLXBL sends subscription results in standard graphql-transport-ws next messages. The GraphQL data shape matches your subscription
selection set.
# Created and updated events return the selected entity fields
{
"id": "products-created-1",
"type": "next",
"payload": {
"data": {
"productCreated": {
"id": "node_xyz789",
"name": "New Widget",
"price": 49.99,
"createdAt": "2026-04-29T10:30:00Z"
}
}
}
}
# Deleted events return the deleted entity id
{
"id": "products-deleted-1",
"type": "next",
"payload": {
"data": {
"productDeleted": "node_abc123"
}
}
}WebSocket Connection
Connect to the shared subscription endpoint with the
graphql-transport-ws WebSocket subprotocol, then authenticate in
the connection_init payload.
# WebSocket endpoint
wss://api.flxbl.dev/api/v1/dynamic-gql/ws
# Required WebSocket subprotocol
Sec-WebSocket-Protocol: graphql-transport-ws
# connection_init payload
{
"type": "connection_init",
"payload": {
"Authorization": "Bearer <admin-or-end-user-jwt>"
}
}
# subscribe payload
{
"id": "products-created-1",
"type": "subscribe",
"payload": {
"query": "subscription WatchProducts { productCreated { id name price } }",
"operationName": "WatchProducts"
}
}Authentication
WebSocket subscriptions currently authenticate with JWTs. Use an admin JWT only from trusted server-side code. Browser applications should subscribe with an end-user JWT from FLXBL end-user authentication.
API keys are still the right fit for CI, scripts, headless agents, REST, and HTTP GraphQL requests, but they do not authenticate the current WebSocket subscription gateway.
Connection Flow
- Open a WebSocket connection to
wss://api.flxbl.dev/api/v1/dynamic-gql/ws. - Send
connection_initwithAuthorization: Bearer <jwt>. - Wait for
connection_ack. - Send one or more
subscribemessages with unique ids. - Handle
nextmessages as events arrive. - Send
completefor a subscription id when you no longer need it.
What Triggers Events
Subscriptions receive events from entity changes made through both generated REST endpoints and GraphQL mutations. Event delivery is backed by Redis Pub/Sub; if Redis is unavailable, mutations continue but live delivery may not occur until Redis is available again.
Client Implementation
The FLXBL TypeScript SDK and CLI currently cover HTTP GraphQL queries and
mutations. For subscriptions, use a WebSocket client that supports
graphql-transport-ws.
JavaScript / TypeScript
npm install graphql-ws import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://api.flxbl.dev/api/v1/dynamic-gql/ws',
webSocketImpl: WebSocket,
connectionParams: async () => ({
Authorization: `Bearer ${await getJwt()}`,
}),
});
const unsubscribe = client.subscribe(
{
query: `subscription WatchProductCreates {
productCreated {
id
name
price
createdAt
}
}`,
},
{
next: (message) => {
console.log('New product:', message.data?.productCreated);
},
error: (error) => {
console.error('Subscription error:', error);
},
complete: () => {
console.log('Subscription completed');
},
}
);
// Later, when the page/component/job no longer needs updates:
unsubscribe();React Hook
import { useEffect, useMemo, useState } from 'react';
import { createClient } from 'graphql-ws';
type ProductCreated = {
productCreated?: {
id: string;
name: string;
price: number;
};
};
export function useProductCreates(getJwt: () => Promise<string>) {
const [latest, setLatest] = useState<ProductCreated['productCreated'] | null>(null);
const [connected, setConnected] = useState(false);
const [error, setError] = useState<unknown>(null);
const client = useMemo(
() =>
createClient({
url: 'wss://api.flxbl.dev/api/v1/dynamic-gql/ws',
connectionParams: async () => ({
Authorization: `Bearer ${await getJwt()}`,
}),
on: {
connected: () => setConnected(true),
closed: () => setConnected(false),
},
}),
[getJwt],
);
useEffect(() => {
const unsubscribe = client.subscribe(
{
query: `subscription ProductFeed {
productCreated {
id
name
price
}
}`,
},
{
next: (message) => {
const data = message.data as ProductCreated | undefined;
setLatest(data?.productCreated ?? null);
},
error: setError,
complete: () => setConnected(false),
},
);
return () => {
unsubscribe();
};
}, [client]);
return { latest, connected, error };
}Python
pip install websockets import asyncio
import json
import os
from websockets import connect
JWT = os.environ["FLXBL_END_USER_JWT"]
WS_URL = "wss://api.flxbl.dev/api/v1/dynamic-gql/ws"
async def watch_products():
async with connect(
WS_URL,
subprotocols=["graphql-transport-ws"],
) as websocket:
await websocket.send(json.dumps({
"type": "connection_init",
"payload": {
"Authorization": f"Bearer {JWT}",
},
}))
ack = json.loads(await websocket.recv())
if ack.get("type") != "connection_ack":
raise RuntimeError(f"Connection failed: {ack}")
await websocket.send(json.dumps({
"id": "products-created-1",
"type": "subscribe",
"payload": {
"query": """
subscription ProductFeed {
productCreated {
id
name
price
}
}
""",
},
}))
try:
async for raw_message in websocket:
message = json.loads(raw_message)
if message.get("type") == "next":
product = message["payload"]["data"]["productCreated"]
print(f"New product: {product['name']} ({product['price']})")
elif message.get("type") == "error":
raise RuntimeError(message["payload"])
elif message.get("type") == "complete":
break
finally:
await websocket.send(json.dumps({
"id": "products-created-1",
"type": "complete",
}))
asyncio.run(watch_products())Error Handling And Reconnection
Treat the socket as an online connection. Reconnect with backoff, refresh the JWT before reconnecting, and resubscribe to the operations your screen still needs.
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://api.flxbl.dev/api/v1/dynamic-gql/ws',
connectionParams: async () => ({
// Fetch a fresh JWT whenever the socket reconnects.
Authorization: `Bearer ${await getFreshJwt()}`,
}),
retryAttempts: Infinity,
retryWait: async (retries) => {
const delay = Math.min(1000 * 2 ** retries, 30000);
await new Promise((resolve) => setTimeout(resolve, delay));
},
shouldRetry: (errorOrCloseEvent) => {
if ('code' in errorOrCloseEvent && errorOrCloseEvent.code === 4401) {
console.error('Authentication failed. Refresh or replace the JWT.');
return false;
}
return true;
},
keepAlive: 30000,
});Common Close And Error Codes
| Code | Meaning | Action |
|---|---|---|
4401 | Authentication failed | Refresh or replace the JWT before retrying |
4400 | Invalid message or subscription query | Fix the JSON message or GraphQL operation |
4408 | Authentication timeout | Send connection_init promptly after connecting |
1006 | Abnormal closure | Reconnect with backoff |
Next Steps
- API Reference - generated REST and HTTP GraphQL APIs.
- End-User Authentication - issue end-user JWTs for browser subscriptions.
- Webhooks - durable server-to-server event delivery.
- Architecture Guide - realtime delivery architecture.