Subscriptions Reference
Real-time GraphQL subscriptions for receiving live updates in Unified Commerce Platform.
Overview
Subscriptions enable real-time, bidirectional communication between client and server over WebSockets. When data changes on the server, subscriptions automatically push updates to connected clients.
When to Use Subscriptions
✅ Use subscriptions for:
- Order status updates
- Real-time inventory changes
- Live notifications
- Chat/messaging
- Live dashboards
❌ Don't use subscriptions for:
- One-time data fetching (use queries)
- Infrequent updates (use polling)
- Historical data (use queries with pagination)
WebSocket Connection
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';
// HTTP link for queries and mutations
const httpLink = new HttpLink({
uri: 'https://gateway.unifiedcommerce.app/graphql',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
// WebSocket link for subscriptions
const wsLink = new GraphQLWsLink(createClient({
url: 'wss://gateway.unifiedcommerce.app/graphql',
connectionParams: {
authToken: 'YOUR_API_KEY',
},
}));
// Split based on operation type
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
Order Subscriptions
orderStatusChanged
Subscribe to order status changes.
subscription OrderStatusChanged($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
orderId
status
previousStatus
updatedAt
updatedBy {
id
name
}
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
orderId | ID | Yes | Order ID to watch |
Usage:
const { data, loading } = useSubscription(ORDER_STATUS_CHANGED, {
variables: { orderId: 'ord_123' },
onData: ({ data }) => {
console.log('Order status changed:', data.orderStatusChanged);
// Update UI, show notification, etc.
}
});
orderCreated
Subscribe to new orders (merchant dashboard).
subscription OrderCreated {
orderCreated {
id
orderNumber
total
currency
status
customer {
name
email
}
items {
product {
name
}
quantity
}
createdAt
}
}
No arguments required (listens to all new orders for your merchant account)
Usage:
useSubscription(ORDER_CREATED, {
onData: ({ data }) => {
const order = data.orderCreated;
// Show notification
toast.success(`New order #${order.orderNumber} from ${order.customer.name}`);
// Play sound
playNotificationSound();
}
});
orderUpdated
Subscribe to any order updates.
subscription OrderUpdated($orderIds: [ID!]) {
orderUpdated(orderIds: $orderIds) {
id
orderNumber
status
total
updatedAt
changes {
field
oldValue
newValue
}
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
orderIds | [ID!] | No | Specific orders to watch (omit to watch all) |
Inventory Subscriptions
inventoryChanged
Subscribe to inventory level changes.
subscription InventoryChanged($productIds: [ID!]) {
inventoryChanged(productIds: $productIds) {
productId
variantId
available
reserved
previousAvailable
warehouseId
updatedAt
reason # e.g., "SALE", "RESTOCK", "ADJUSTMENT"
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
productIds | [ID!] | No | Specific products to watch |
Usage:
// Watch specific products
useSubscription(INVENTORY_CHANGED, {
variables: {
productIds: ['prod_123', 'prod_456']
},
onData: ({ data }) => {
const inv = data.inventoryChanged;
if (inv.available === 0) {
// Product out of stock
showOutOfStockBadge(inv.productId);
} else if (inv.available < 10) {
// Low stock warning
showLowStockWarning(inv.productId, inv.available);
}
}
});
lowStockAlert
Subscribe to low stock alerts.
subscription LowStockAlert {
lowStockAlert {
productId
product {
name
sku
}
available
lowStockThreshold
warehouseId
alertedAt
}
}
No arguments (alerts for all products below threshold)
Notification Subscriptions
notificationReceived
Subscribe to user notifications.
subscription NotificationReceived {
notificationReceived {
id
type
title
message
data # JSON with notification-specific data
priority
read
createdAt
}
}
Notification Types:
enum NotificationType {
ORDER_UPDATE
PAYMENT_RECEIVED
SHIPMENT_UPDATE
LOW_STOCK
CUSTOMER_MESSAGE
SYSTEM_ALERT
}
Usage:
useSubscription(NOTIFICATION_RECEIVED, {
onData: ({ data }) => {
const notification = data.notificationReceived;
// Show toast notification
toast.info(notification.title, {
description: notification.message,
duration: 5000
});
// Update notification badge count
incrementNotificationCount();
// Play sound for high-priority notifications
if (notification.priority === 'HIGH') {
playNotificationSound();
}
}
});
messageReceived
Subscribe to customer messages (chat/support).
subscription MessageReceived($conversationId: ID!) {
messageReceived(conversationId: $conversationId) {
id
conversationId
sender {
id
name
avatar
}
content
attachments {
url
type
name
}
sentAt
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
conversationId | ID | Yes | Conversation ID to watch |
Booking Subscriptions
appointmentStatusChanged
Subscribe to appointment status changes.
subscription AppointmentStatusChanged($appointmentId: ID!) {
appointmentStatusChanged(appointmentId: $appointmentId) {
appointmentId
status
previousStatus
startTime
endTime
updatedAt
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
appointmentId | ID | Yes | Appointment ID to watch |
appointmentReminder
Subscribe to upcoming appointment reminders.
subscription AppointmentReminder {
appointmentReminder {
appointment {
id
service {
name
}
startTime
customer {
name
phone
}
}
reminderType # "1_HOUR_BEFORE", "24_HOUR_BEFORE"
}
}
No arguments (staff member receives reminders for their appointments)
Cart Subscriptions
cartUpdated
Subscribe to cart changes (useful for cart abandonment).
subscription CartUpdated($cartId: ID!) {
cartUpdated(cartId: $cartId) {
cartId
items {
product {
name
price
}
quantity
}
total
updatedAt
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
cartId | ID | Yes | Cart ID to watch |
Analytics Subscriptions
realtimeMetrics
Subscribe to real-time analytics metrics.
subscription RealtimeMetrics {
realtimeMetrics {
timestamp
activeUsers
ordersToday
revenueToday
averageOrderValue
conversionRate
}
}
No arguments (merchant dashboard metrics)
Usage:
useSubscription(REALTIME_METRICS, {
onData: ({ data }) => {
const metrics = data.realtimeMetrics;
updateDashboardCharts(metrics);
}
});
Payment Subscriptions
paymentStatusChanged
Subscribe to payment status updates.
subscription PaymentStatusChanged($paymentId: ID!) {
paymentStatusChanged(paymentId: $paymentId) {
paymentId
status
previousStatus
amount
currency
failureReason
updatedAt
}
}
Arguments:
Name | Type | Required | Description |
---|---|---|---|
paymentId | ID | Yes | Payment ID to watch |
Implementation Examples
React Hook
import { useSubscription } from '@apollo/client';
import { gql } from '@apollo/client';
const ORDER_STATUS_CHANGED = gql`
subscription OrderStatusChanged($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
orderId
status
updatedAt
}
}
`;
function OrderTracker({ orderId }) {
const { data, loading, error } = useSubscription(ORDER_STATUS_CHANGED, {
variables: { orderId }
});
if (loading) return <p>Connecting...</p>;
if (error) return <p>Connection error: {error.message}</p>;
return (
<div>
<h2>Order Status: {data?.orderStatusChanged.status}</h2>
<p>Last updated: {data?.orderStatusChanged.updatedAt}</p>
</div>
);
}
Vue Composition API
import { useSubscription } from '@vue/apollo-composable';
import gql from 'graphql-tag';
export default {
setup() {
const { result, loading, error } = useSubscription(
gql`
subscription InventoryChanged($productIds: [ID!]) {
inventoryChanged(productIds: $productIds) {
productId
available
}
}
`,
{ productIds: ['prod_123', 'prod_456'] }
);
return {
inventoryData: result,
loading,
error
};
}
};
Plain JavaScript
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://gateway.unifiedcommerce.app/graphql',
connectionParams: {
authToken: 'YOUR_API_KEY',
},
});
// Subscribe
const unsubscribe = client.subscribe(
{
query: `
subscription {
orderCreated {
id
orderNumber
total
}
}
`,
},
{
next: (data) => {
console.log('New order:', data.orderCreated);
},
error: (error) => {
console.error('Subscription error:', error);
},
complete: () => {
console.log('Subscription completed');
},
}
);
// Unsubscribe later
unsubscribe();
Python
from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
transport = WebsocketsTransport(
url='wss://gateway.unifiedcommerce.app/graphql',
init_payload={
'authToken': 'YOUR_API_KEY'
}
)
client = Client(transport=transport)
subscription = gql('''
subscription OrderStatusChanged($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
orderId
status
updatedAt
}
}
''')
for result in client.subscribe(
subscription,
variable_values={'orderId': 'ord_123'}
):
print(f"Order status: {result['orderStatusChanged']['status']}")
Go
package main
import (
"context"
"fmt"
"github.com/hasura/go-graphql-client"
)
func main() {
client := graphql.NewSubscriptionClient("wss://gateway.unifiedcommerce.app/graphql")
defer client.Close()
var sub struct {
OrderStatusChanged struct {
OrderID string
Status string
UpdatedAt string
} `graphql:"orderStatusChanged(orderId: $orderId)"`
}
variables := map[string]interface{}{
"orderId": graphql.ID("ord_123"),
}
_, err := client.Subscribe(&sub, variables, func(data []byte, err error) error {
if err != nil {
return err
}
fmt.Printf("Status: %s\n", sub.OrderStatusChanged.Status)
return nil
})
if err != nil {
panic(err)
}
// Keep connection open
select {}
}
Connection Management
Authentication
Pass API key in connection params:
const wsLink = new GraphQLWsLink(createClient({
url: 'wss://gateway.unifiedcommerce.app/graphql',
connectionParams: {
authToken: 'YOUR_API_KEY',
},
}));
Reconnection
Handle connection drops:
const wsLink = new GraphQLWsLink(createClient({
url: 'wss://gateway.unifiedcommerce.app/graphql',
connectionParams: {
authToken: 'YOUR_API_KEY',
},
retryAttempts: 5,
shouldRetry: () => true,
on: {
connected: () => console.log('Connected to WebSocket'),
closed: () => console.log('Disconnected from WebSocket'),
error: (error) => console.error('WebSocket error:', error),
},
}));
Cleanup
Always unsubscribe when component unmounts:
useEffect(() => {
const subscription = client.subscribe({
query: ORDER_STATUS_CHANGED,
variables: { orderId }
}).subscribe({
next: (data) => {
// Handle data
}
});
// Cleanup on unmount
return () => subscription.unsubscribe();
}, [orderId]);
Best Practices
1. Limit Active Subscriptions
Don't create too many concurrent subscriptions:
// ❌ Bad - subscribing to hundreds of orders
orders.forEach(order => {
useSubscription(ORDER_STATUS_CHANGED, {
variables: { orderId: order.id }
});
});
// ✅ Good - subscribe to relevant orders only
const activeOrderIds = orders
.filter(o => ['PENDING', 'PROCESSING'].includes(o.status))
.map(o => o.id);
useSubscription(ORDER_UPDATED, {
variables: { orderIds: activeOrderIds }
});
2. Handle Connection Errors
useSubscription(ORDER_STATUS_CHANGED, {
variables: { orderId },
onError: (error) => {
console.error('Subscription error:', error);
// Show error to user
toast.error('Lost connection. Retrying...');
}
});
3. Use Subscriptions with Queries
Combine query + subscription for initial data + updates:
// Initial data
const { data } = useQuery(GET_ORDER, {
variables: { orderId }
});
// Real-time updates
useSubscription(ORDER_STATUS_CHANGED, {
variables: { orderId },
onData: ({ data: subscriptionData }) => {
// Update cache
client.cache.modify({
id: client.cache.identify({ __typename: 'Order', id: orderId }),
fields: {
status: () => subscriptionData.orderStatusChanged.status
}
});
}
});
4. Throttle High-Frequency Updates
import { throttle } from 'lodash';
const handleInventoryUpdate = throttle((data) => {
updateUI(data);
}, 1000); // Max once per second
useSubscription(INVENTORY_CHANGED, {
onData: ({ data }) => {
handleInventoryUpdate(data);
}
});
Rate Limits
Subscriptions have separate rate limits:
Plan | Max Concurrent Subscriptions | Max Messages/Minute |
---|---|---|
Free | 5 | 100 |
Pro | 50 | 1,000 |
Enterprise | Unlimited | Unlimited |
Testing Subscriptions
Mock Subscriptions
import { MockedProvider } from '@apollo/client/testing';
const mocks = [
{
request: {
query: ORDER_STATUS_CHANGED,
variables: { orderId: 'ord_123' }
},
result: {
data: {
orderStatusChanged: {
orderId: 'ord_123',
status: 'SHIPPED',
updatedAt: new Date().toISOString()
}
}
}
}
];
test('renders order status updates', () => {
render(
<MockedProvider mocks={mocks}>
<OrderTracker orderId="ord_123" />
</MockedProvider>
);
// ... assertions
});
Next Steps
- Queries Reference - Fetch data
- Mutations Reference - Modify data
- WebSocket Guide - WebSocket configuration
- Interactive Playground - Test subscriptions
Questions about subscriptions? Ask in Discord!