Apollo client GraphQL subscriptions
Apollo GraphQL subscriptions tutorial
Apollo server event driven
Apollo server GraphQL subscriptions
Apollo server real time architecture
Apollo server webhooks
asynchronous API communication
backend communication patterns
backend event delivery webhooks
backend microservice event communication
backend to backend webhooks
browser WebSockets GraphQL subscriptions
building scalable event driven APIs
enterprise event driven integration
event driven API design patterns
event driven API GraphQL
event driven API patterns
event driven architecture webhooks
event driven microservices GraphQL
GraphQL client side real time
GraphQL event driven architecture
GraphQL pub sub backend
GraphQL push notifications
GraphQL query subscription difference
GraphQL real time client updates
GraphQL real time updates
GraphQL subscription performance
GraphQL subscriptions browser clients
GraphQL subscription scalability
GraphQL subscription server load
GraphQL subscriptions stateful server
GraphQL subscriptions vs webhooks
GraphQL subscriptions vs WebSockets
GraphQL subscription vs polling
GraphQL WebSocket overhead
persistent connection vs HTTP callback
persistent open connections GraphQL
real time web applications GraphQL
scalable event driven APIs
serverless webhooks vs GraphQL
stateless backend webhooks
webhook infrastructure design
webhooks architecture best practices
webhooks event triggers
webhooks HTTP callbacks
webhooks reliability and retries
webhooks server to server
webhooks vs event driven API
webhooks vs GraphQL subscriptions
webhooks vs GraphQL system integration
webhooks vs WebSockets backend
webhooks vs WebSockets performance
WebSockets vs webhooks
when to use webhooks vs GraphQL subscriptions
Graph QL Subscriptions Vs Webhooks Choosing The Right Event Driven Pattern
GraphQL Subscriptions vs. Webhooks: Choosing the Right Event-Driven Pattern
When you're designing an event-driven API, one of the biggest architectural decisions is how you propagate state changes across your system. Two patterns dominate real-time data distribution: GraphQL Subscriptions and Webhooks.
Both patterns replace the resource-wasteful practice of HTTP short-polling, but they solve fundamentally different problems at opposite ends of the architectural spectrum. GraphQL Subscriptions maintain persistent WebSocket (or SSE) connections designed primarily for client-to-server, UI-facing interactions. Webhooks rely on stateless, asynchronous HTTP POST callbacks optimized for decoupled backend-to-backend communication.
This guide breaks down the mechanics, resource efficiency, serverless compatibility, security models, and code implementations of each — including how they intersect in a typical Apollo Server setup, and how the tooling around both has shifted heading into 2026.
Executive Summary & Comparison Table
Feature / Dimension GraphQL Subscriptions Webhooks
Primary Use Case Real-time UI updates (Client-to-Server) Asynchronous notifications (Server-to-Server)
Transport Protocol WebSockets (ws:///wss://) or Server-Sent Events Stateless HTTP / HTTPS POST
Connection Type Stateful, persistent, long-lived Stateless, request-response, ephemeral
Directionality Bidirectional handshake / server-push after that Unidirectional (server pushing to server)
Payload Customization Dynamic — defined by the client's GraphQL selection set Static — defined by the publisher's schema
Server Resource Overhead Higher memory/file-descriptor footprint per client Low; scales with request volume, not connection count
Serverless Compatibility Historically awkward; improving via managed pub/sub (see below) Native — maps cleanly to Lambda/Edge functions
Reliability & Retries Connection-bound; client must resubscribe on drop Publisher-side retry queues and dead-letter queues
Security Mechanism Auth during the connection handshake (tokens/headers) HMAC signatures, IP allow-listing, mutual TLS
What Are GraphQL Subscriptions?
GraphQL Subscriptions are a GraphQL operation type that lets a server push real-time updates to subscribed clients whenever a specific event occurs.
Unlike Queries (read) and Mutations (write), which run over a standard request-response cycle, Subscriptions are long-lived operations that stay open for the lifetime of the client's interest in that data.
Code example
Copy code
+------------------+ 1. WebSocket Connection Handshake +-------------------+
| | -----------------------------------------------> | |
| | 2. SUBSCRIBE: subscription { ... } | |
| Browser / Client | -----------------------------------------------> | Apollo Server |
| (Apollo/Urql) | | (Pub/Sub Engine)|
| | <----------------------------------------------- | |
+------------------+ 3. Real-Time Data Push (Event Payload) +-------------------+
Technical Mechanics
Protocol handshake: The client initiates an HTTP upgrade to establish a WebSocket connection (using the graphql-transport-ws protocol, implemented by the graphql-ws library), or opens a Server-Sent Events stream via graphql-sse.
Subscription registration: The client sends a subscription query defining exactly the shape of data it wants:
Code example
Copy code
subscription OnOrderUpdated($orderId: ID!) {
orderUpdated(id: $orderId) {
id
status
estimatedDelivery
}
}
Event publishing: When an event occurs (say, an order status changes via a mutation), the server's pub/sub layer triggers the subscription resolver and pushes the transformed result to every matching active connection.
Major Strengths
Zero over-fetching: The client specifies exactly the fields it needs, so a bandwidth-constrained mobile client only pulls down status, not the whole object graph.
Unified schema: Subscriptions share the same schema, types, and auth context as your existing queries and mutations.
Cache integration: Apollo Client and Urql automatically merge incoming subscription payloads into their normalized caches, triggering UI re-renders without extra plumbing.
Major Drawbacks
Higher memory footprint: Every open socket consumes kernel buffer memory and a file descriptor, so servers holding tens of thousands of concurrent connections need real capacity planning (OS ulimit tuning, load balancer socket limits, etc.) — the exact bytes-per-socket vary by OS and TCP buffer settings, but the direction is always "more connections, more baseline memory," unlike stateless HTTP.
Distributed state complexity: Scaling subscriptions across multiple server instances requires an external message broker (Redis Pub/Sub, NATS, Kafka) so an event published on one node reaches clients connected to another.
Note on tooling: subscriptions-transport-ws, the original Apollo-created WebSocket transport, has been unmaintained since 2018 and its repository is now archived. If you see it in a tutorial, treat that as a signal the content is outdated — the actively maintained, Apollo-recommended replacement is graphql-ws, which implements the newer graphql-transport-ws protocol. The two protocols are not wire-compatible, so migrating means upgrading both client and server.
What Are Webhooks?
Webhooks (sometimes called "reverse APIs" or HTTP callbacks) are subscriber-defined HTTP endpoints that react to events in someone else's system. When an event happens in the publishing system, it serializes the event and POSTs it directly to a pre-registered URL owned by the subscriber.
Code example
Copy code
+-------------------+ +-------------------+
| | 1. Event Occurs (e.g., Payment) | |
| Publishing System | ---------------------------------------------> | Receiving Server |
| (e.g., Stripe) | HTTP POST payload + HMAC Signature | (Webhook Handler)|
| | | |
| | <---------------------------------------------- | |
+-------------------+ 2. HTTP 200 OK Response +-------------------+
Technical Mechanics
Registration: System B registers an HTTPS endpoint (https://api.system-b.com/webhooks/orders) with System A.
Event dispatch: When an event occurs in System A, it serializes the event data to JSON and sends a POST request to System B's URL.
Acknowledgment and retries: System B processes the payload and returns a status code (200 OK or 202 Accepted). If it returns a 5xx or times out, System A queues the event for retry with exponential backoff.
Major Strengths
Stateless and scalable: No open connections to maintain — receivers handle each delivery as ordinary HTTP traffic and can scale to zero when idle.
Reliability infrastructure is standard practice: Mature webhook publishers pair delivery with a queue (SQS, Kafka) plus retries and dead-letter queues, so temporary receiver downtime doesn't mean lost events.
Language- and protocol-agnostic: Any server that can parse an HTTP POST body can receive a webhook.
Major Drawbacks
Fixed payload shapes: Subscribers get whatever JSON the publisher decided to send, which often means a follow-up API call to fetch missing context.
Public exposure: Receiving endpoints must be reachable from the public internet, which makes signature verification (not just "security through obscurity") mandatory.
No standardization, historically: Every provider invented its own header names, signing scheme, and retry cadence — this is exactly the gap the Standard Webhooks initiative (covered below) is trying to close.
The Architectural Conflict: Statefulness vs. Scalability
Choosing between GraphQL Subscriptions and Webhooks comes down to balancing connection statefulness against infrastructure scalability.
Code example
Copy code
+-------------------------------------+
| Is the consumer a frontend app |
| or web browser needing instant UI? |
+-------------------------------------+
/ \
/ \
YES NO
/ \
v v
+--------------------------+ +--------------------------+
| Use GraphQL Subscriptions| | Is it a server-to-server |
| (over WebSockets / SSE) | | integration across system |
+--------------------------+ | boundaries? |
+--------------------------+
|
| YES
v
+--------------------------+
| Use Webhooks |
+--------------------------+
Connection Exhaustion: The WebSocket Bottleneck
A single Node.js process running Apollo Server can handle a very large volume of stateless HTTP requests per second, because each connection closes as soon as the response is sent.
Over WebSockets, it's different: every subscribed client holds a persistent TCP socket open indefinitely. That has real costs:
Memory: Each open socket reserves kernel send/receive buffers, so total memory scales roughly linearly with connection count — the specific per-socket number depends heavily on your OS and network stack tuning, but it's a cost stateless HTTP simply doesn't have.
File descriptors: Unix treats sockets as file descriptors, so high concurrency runs into ulimit -n and load-balancer connection caps unless you tune for it deliberately.
Heartbeats: To detect dead connections (a phone switching from Wi-Fi to cellular, for instance), servers send periodic pings, which adds constant low-level CPU and bandwidth overhead.
The Serverless & Edge Disconnect
Modern infrastructure leans heavily on serverless runtimes — AWS Lambda, Cloudflare Workers, Vercel Functions — that spin up on demand and terminate when the response finishes.
Webhooks thrive here: an incoming webhook triggers a stateless function execution, processes the payload in tens of milliseconds, returns 200 OK, and terminates. Cost tracks execution time directly.
GraphQL Subscriptions historically struggled here: a standard serverless function can't hold a WebSocket open for hours, so teams offloaded connection management to an external stateful layer — managed WebSocket gateways backed by a database, or third-party real-time platforms like Ably, Pusher, or AWS AppSync.
That gap has narrowed. As of March 2025, AWS AppSync Events provides a managed, serverless WebSocket pub/sub API specifically so teams don't have to hand-roll connection management on top of Lambda and DynamoDB — you publish events over HTTP and AppSync handles fan-out to connected WebSocket clients. It's a purpose-built pub/sub product that sits alongside (not strictly inside) AppSync's original GraphQL subscription model, and it's one of a few signs that "subscriptions on serverless" is becoming a solved problem rather than a workaround.
Code Implementations
- GraphQL Subscription Implementation (graphql-ws + Apollo Server) This sets up a subscription server using Node.js, graphql-ws, and Apollo Server, letting clients subscribe to real-time commentAdded events. Note this already uses graphql-ws, not the deprecated subscriptions-transport-ws — that's the correct, currently supported approach.
Code example
Copy code
import { createServer } from 'http';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub } from 'graphql-subscriptions';
import bodyParser from 'body-parser';
const pubsub = new PubSub();
const COMMENT_ADDED = 'COMMENT_ADDED';
// 1. Schema Definition
const typeDefs = `#graphql
type Comment {
id: ID!
content: String!
author: String!
}
type Query {
comments: [Comment!]!
}
type Mutation {
addComment(content: String!, author: String!): Comment!
}
type Subscription {
commentAdded: Comment!
}
`;
// 2. Resolvers
const resolvers = {
Query: {
comments: () => [],
},
Mutation: {
addComment: (_, { content, author }) => {
const newComment = { id: Date.now().toString(), content, author };
// Publish event to subscribers
pubsub.publish(COMMENT_ADDED, { commentAdded: newComment });
return newComment;
},
},
Subscription: {
commentAdded: {
subscribe: () => pubsub.asyncIterator([COMMENT_ADDED]),
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
// 3. HTTP and WebSocket Server Setup
const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
// Bind WebSocket server with GraphQL schema
const serverCleanup = useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
],
});
await server.start();
app.use('/graphql', bodyParser.json(), expressMiddleware(server));
httpServer.listen(4000, () => {
console.log('🚀 Query/Mutation server ready at http://localhost:4000/graphql');
console.log('🚀 Subscription server ready at ws://localhost:4000/graphql');
});
- Secure Webhook Publisher & Receiver with HMAC Verification Below is a webhook receiver verifying an HMAC-SHA256 signature with a custom header, plus a publisher dispatching events. This is the "roll your own" pattern most companies used before any standardization existed — and it's still perfectly valid for a single internal integration.
Webhook Receiver (Express + TypeScript)
Code example
Copy code
import express, { Request, Response } from 'express';
import crypto from 'crypto';
const app = express();
// Capture raw buffer for cryptographic signature verification
app.use(express.json({
verify: (req: any, _res, buf) => {
req.rawBody = buf;
}
}));
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'super-secret-key-123';
function verifyHmacSignature(rawBody: Buffer, signatureHeader: string | undefined): boolean {
if (!signatureHeader) return false;
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const trustedBuffer = Buffer.from(sha256=${expectedSignature}, 'utf8');
const untrustedBuffer = Buffer.from(signatureHeader, 'utf8');
if (trustedBuffer.length !== untrustedBuffer.length) return false;
// Use timingSafeEqual to protect against timing attacks
return crypto.timingSafeEqual(trustedBuffer, untrustedBuffer);
}
app.post('/webhooks/payment-events', (req: Request, res: Response) => {
const signature = req.headers['x-signature-256'] as string;
const rawBody = (req as any).rawBody;
if (!verifyHmacSignature(rawBody, signature)) {
console.error('❌ Invalid Webhook Signature. Rejecting payload.');
return res.status(401).json({ error: 'Invalid cryptographic signature' });
}
const event = req.body;
console.log(✅ Valid Webhook Received: ${event.eventType}, event.data);
// Perform background task asynchronously or enqueue to SQS/Redis
// Acknowledge immediately with 200 OK
return res.status(200).json({ received: true });
});
app.listen(3000, () => console.log('Webhook receiver running on port 3000'));
Webhook Publisher Dispatch Logic
Code example
Copy code
import axios from 'axios';
import crypto from 'crypto';
interface WebhookEvent {
id: string;
eventType: string;
timestamp: number;
data: Record;
}
async function dispatchWebhook(targetUrl: string, secret: string, payload: WebhookEvent) {
const jsonBody = JSON.stringify(payload);
// Calculate SHA256 HMAC Signature
const signature = crypto
.createHmac('sha256', secret)
.update(jsonBody)
.digest('hex');
try {
const response = await axios.post(targetUrl, jsonBody, {
headers: {
'Content-Type': 'application/json',
'X-Signature-256': sha256=${signature},
'User-Agent': 'MyApp-Webhook-Dispatcher/1.0',
},
timeout: 5000, // 5-second timeout safeguard
});
console.log(`Webhook delivered successfully. Status: ${response.status}`);
} catch (error: any) {
console.error(Webhook delivery failed: ${error.message}. Schedule retry in worker queue.);
// Push to retry queue with exponential backoff
}
}
- The Standardized Alternative: Verifying with the Standard Webhooks Spec If you're building a new webhook integration in 2026, it's worth signing with the open Standard Webhooks specification instead of a bespoke header. It's a small but meaningful upgrade: instead of a single X-Signature-256 header, the receiver gets three standardized headers — webhook-id, webhook-timestamp, and webhook-signature — and the signed content is {webhook-id}.{webhook-timestamp}.{raw-body}, hashed with HMAC-SHA256 and base64-encoded with a v1, prefix. Verification libraries also enforce a timestamp tolerance (5 minutes by default) to reject replayed requests. Using a maintained SDK instead of hand-rolled crypto.createHmac calls means one less place to get constant-time comparison or replay protection wrong:
Code example
Copy code
import express from 'express';
import { Webhook } from 'standardwebhooks';
const app = express();
app.use(express.raw({ type: 'application/json' }));
const wh = new Webhook(process.env.WEBHOOK_SECRET!); // e.g. "whsec_..."
app.post('/webhooks/orders', (req, res) => {
try {
const event = wh.verify(req.body, {
'webhook-id': req.headers['webhook-id'] as string,
'webhook-timestamp': req.headers['webhook-timestamp'] as string,
'webhook-signature': req.headers['webhook-signature'] as string,
});
console.log('✅ Verified event:', event);
res.status(200).json({ received: true });
} catch (err) {
console.error('❌ Signature verification failed');
res.status(401).json({ error: 'Invalid signature' });
}
});
Apollo Server & Webhooks: Bridging the Gap
A common point of confusion is whether Apollo Server and webhooks can be combined into a single event architecture.
In enterprise environments, Apollo Server often acts as a GraphQL Gateway or BFF (Backend-For-Frontend). In this role, it receives inbound webhooks from external SaaS vendors (Stripe, GitHub, Shopify) and relays those updates to frontend clients via GraphQL Subscriptions.
Code example
Copy code
+-------------------+ HTTP POST Webhook +-----------------------------+
| External SaaS | -----------------------------------> | Inbound Webhook Endpoint |
| (Stripe / GitHub) | | (Express / Serverless API) |
+-------------------+ +-----------------------------+
|
| Triggers Event
v
+-------------------+ WebSocket / SSE Subscription +-----------------------------+
| Frontend Client | <----------------------------------- | Redis Pub/Sub Engine |
| (React/Apollo) | | & Apollo Server Gateway |
+-------------------+ +-----------------------------+
Pattern: Inbound Webhook to GraphQL Subscription Relay
Ingestion endpoint: An Express endpoint or serverless function dedicated to handling incoming third-party webhooks (e.g., /api/webhooks/stripe).
Payload validation: Authenticate the incoming request signature using the third party's secret.
Publish to message bus: Push the validated event into a shared pub/sub channel (Redis, AWS EventBridge).
Broadcast via subscriptions: The Apollo Server instance listening to that channel triggers a GraphQL Subscription update, pushing refined data to connected browser clients.
This bridge pattern plays to each protocol's strengths: webhooks give you a resilient server-to-server integration boundary; subscriptions give you clean, client-driven real-time updates for active frontend sessions.
If You're on Apollo Federation / GraphOS
If your subscriptions need to span a federated graph, there are two things worth knowing that changed the picture recently:
Federation version matters. Subscription operations require Apollo Federation 2.4 or later in your subgraph schemas — earlier Federation versions don't support them at all.
The router talks two different protocols. The GraphOS Router communicates with your subgraphs using the graphql-transport-ws WebSocket protocol, but it typically serves clients over multipart HTTP responses rather than a client-facing WebSocket — so browser clients don't need a WebSocket library at all in that setup.
Cloud routers are being retired. Apollo is discontinuing its GraphOS Serverless and Dedicated cloud-router plans (Serverless after February 1, 2026; Dedicated after March 15, 2026). If you're relying on a cloud router for subscription support, plan a migration to a self-hosted router well ahead of those dates.
What's Changed Heading Into 2026
A few developments are worth folding into how you think about this decision today:
The WebSocket transport question is settled — mostly. graphql-ws is the de facto standard for GraphQL-over-WebSocket; subscriptions-transport-ws is archived and shouldn't be used in new projects. Some GraphQL federation runtimes (WunderGraph Cosmo, for example) now support WebSockets, Server-Sent Events, and multipart HTTP as interchangeable subscription transports, treating the choice as a deployment detail rather than a schema-level commitment.
Serverless-native real-time is real now, not just a workaround. AWS AppSync Events (GA since March 2025) gives teams a managed WebSocket pub/sub layer without operating their own connection state, closing much of the historical gap between "needs persistent connections" and "wants to run on Lambda."
Webhooks finally have a real standard. The Standard Webhooks specification — driven by Svix with a steering group that includes Zapier, Twilio, ngrok, and Supabase — defines the webhook-id / webhook-timestamp / webhook-signature header scheme described above. It's been adopted well beyond its original backers: OpenAI, Anthropic, and Google Gemini all sign their webhooks this way, alongside smaller platforms like Clerk and GrowthBook. If you're building a webhook publisher today, adopting the spec (or one of its open-source SDKs) instead of inventing your own header format is close to a free win for interoperability.
Webhook infrastructure is now its own category. Dedicated platforms — Svix (outbound, multi-tenant), Hookdeck (inbound routing, replay, fan-out), and the open-source, self-hostable Convoy — exist specifically so teams don't have to build retry queues and dead-letter handling from scratch.
Architectural Decision Matrix
Choose GraphQL Subscriptions when:
The consumer is a browser or mobile app: You need live UI updates — chat, collaborative editing, live tickers, notification bells.
Clients need payload granularity: Bandwidth-constrained clients benefit from field-level selection instead of a fixed JSON blob.
You already run a GraphQL ecosystem: Apollo Client, Relay, or Urql can ingest subscription updates directly into their existing cache/state layers.
Connection count is predictable: Concurrent connections fit your server's memory budget, or you're using a managed real-time platform (AppSync Events, Ably, Pusher) instead of self-hosting sockets.
Choose Webhooks when:
Communication is backend-to-backend: Connecting microservices, integrating third-party SaaS, or triggering CI/CD.
You run on serverless/edge compute: Lambda, Vercel, or Cloudflare Workers, where persistent connections are impractical.
Guaranteed delivery matters more than instant delivery: Combined with a queue, webhooks give you at-least-once delivery even across receiver downtime.
You need to absorb bursty traffic: Receivers can buffer into a background queue (BullMQ, SQS) to smooth spikes without dropping connections.
Frequently Asked Questions (FAQ)
Is GraphQL over Server-Sent Events (SSE) better than WebSockets?
For many web applications, yes, in specific ways. SSE runs over standard HTTP, making it easier to route through corporate firewalls, proxies, and load balancers, and the browser's EventSource API handles reconnection automatically. Its trade-off is that SSE is unidirectional (server-to-client only), while WebSockets support bidirectional messages over one connection. In practice, several GraphQL runtimes now let you choose either transport for the same schema rather than forcing a single choice up front.How do you handle authentication in GraphQL Subscriptions?
Because standard browser WebSocket APIs don't support custom HTTP headers during the connection upgrade, auth is usually handled inside the protocol handshake payload. With graphql-ws, the client sends an auth token (a JWT, say) inside connectionParams during connection initialization, and the server validates it in the onConnect callback before accepting any subscription requests.Can I use GraphQL Subscriptions for microservice-to-microservice communication?
Technically possible, but it's generally considered an anti-pattern. Persistent WebSockets between backend nodes add coupling and connection-drop failure modes that internal services don't need. For internal sync, reach for webhooks, gRPC, or an event stream like Kafka or EventBridge instead.What happens if a receiver is down when a webhook fires?
A well-designed publisher queues the event and retries with exponential backoff rather than dropping it. Stripe is a useful concrete example: on a delivery failure it retries immediately, then again after roughly 5 minutes, 30 minutes, 2 hours, 5 hours, and 10 hours, then every 12 hours after that, for up to 3 days total — after which it disables the endpoint and notifies you. Exact schedules vary by provider, but "retry with growing delays, then eventually dead-letter or disable" is the near-universal pattern.What is the Standard Webhooks specification, and should I use it?
It's an open specification for signing and structuring webhook deliveries consistently across providers, so a receiver can verify signatures from any conforming sender using one SDK instead of custom logic per integration. It defines three headers (webhook-id, webhook-timestamp, webhook-signature), an HMAC-SHA256 signing scheme, and built-in replay protection via timestamp tolerance. It's worth adopting for new webhook publishers — it costs little and several major API providers already sign this way.
Conclusion
Choosing between GraphQL Subscriptions and Webhooks isn't about which technology is newer or "better" — it's about which protocol matches your system's architecture and network boundaries.
Use GraphQL Subscriptions for real-time, client-facing experiences over WebSockets or SSE, where you want field-level query control and tight integration with your existing GraphQL client cache.
Use Webhooks as the backbone for scalable, resilient backend-to-backend integrations, backed by stateless HTTP, cryptographic signatures (ideally Standard Webhooks in new builds), and durable retry infrastructure.
Used together — subscriptions for the last mile to the browser, webhooks for everything crossing a system boundary — you get an event-driven architecture that's scalable, cost-effective, and easier to reason about than forcing one pattern to do both jobs.
Further Reading
graphql-ws (the actively maintained WebSocket transport)
Apollo: subscriptions-transport-ws is deprecated
AWS AppSync Events documentation
Apollo GraphOS: GraphQL Subscriptions
Standard Webhooks specification
Stripe: webhook retry behavior
Top comments (0)