JSON-RPC Notifications Example
This example demonstrates how to implement and use JSON-RPC 2.0 notifications - a special type of request that doesn't require a response from the server, enabling fire-and-forget communication patterns.
What You'll Learn
- What JSON-RPC notifications are and when to use them
- How to send notifications from a client
- How to handle notifications on the server
- Best practices for implementing notification-based systems
- Performance benefits of using notifications
JSON-RPC Notifications
Understand JSON-RPC notifications - one-way requests that don't expect a response.
What are Notifications?
A JSON-RPC notification is a request object without an "id" member. The client is not interested in the response and does not expect one. The server must not reply to a notification, including when the notification is invalid.
Regular Request
{
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23],
"id": 1
}
Expects a response
Notification
{
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23]
}
No response expected
Common Use Cases
1. Logging and Analytics
Send analytics data or log events without waiting for confirmation.
{
"jsonrpc": "2.0",
"method": "log.event",
"params": {
"userId": "12345",
"action": "page_view",
"page": "/products/laptop",
"timestamp": "2024-01-15T10:30:00Z",
"metadata": {
"userAgent": "Mozilla/5.0...",
"referrer": "https://google.com"
}
}
}
2. Cache Invalidation
Notify cache servers to invalidate specific data without waiting for confirmation.
{
"jsonrpc": "2.0",
"method": "cache.invalidate",
"params": {
"keys": ["user:12345", "user:12345:profile"],
"reason": "profile_updated"
}
}
3. Background Tasks
Trigger background processing without blocking the client.
{
"jsonrpc": "2.0",
"method": "email.send",
"params": {
"to": "[email protected]",
"subject": "Welcome to our service",
"template": "welcome",
"variables": {
"name": "John Doe",
"activationLink": "https://example.com/activate/abc123"
}
}
}
4. Real-time Updates
Send real-time updates to connected clients (often via WebSocket).
{
"jsonrpc": "2.0",
"method": "user.status.changed",
"params": {
"userId": "12345",
"status": "online",
"lastSeen": "2024-01-15T10:30:00Z"
}
}
Batch Notifications
You can send multiple notifications in a single batch request. This is useful for optimizing network traffic.
Multiple Events Example
[
{
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"userId": "12345",
"timestamp": "2024-01-15T10:30:00Z",
"ip": "192.168.1.1"
}
},
{
"jsonrpc": "2.0",
"method": "analytics.track",
"params": {
"event": "session_start",
"userId": "12345",
"properties": {
"platform": "web",
"browser": "Chrome"
}
}
},
{
"jsonrpc": "2.0",
"method": "cache.warm",
"params": {
"userId": "12345",
"preload": ["preferences", "recent_items"]
}
}
]
Implementation Examples
JavaScript Client
class JsonRpcClient {
constructor(endpoint) {
this.endpoint = endpoint;
}
// Send notification (no response expected)
async notify(method, params) {
const notification = {
jsonrpc: '2.0',
method,
params
};
try {
await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(notification)
});
// No response handling - fire and forget
} catch (error) {
// Only handle network errors
console.error('Network error sending notification:', error);
}
}
// Send multiple notifications in batch
async notifyBatch(notifications) {
const batch = notifications.map(({ method, params }) => ({
jsonrpc: '2.0',
method,
params
}));
try {
await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(batch)
});
} catch (error) {
console.error('Network error sending batch notifications:', error);
}
}
}
// Usage examples
const client = new JsonRpcClient('https://api.example.com/jsonrpc');
// Single notification
client.notify('user.activity', {
userId: '12345',
action: 'click',
element: 'subscribe-button'
});
// Batch notifications
client.notifyBatch([
{ method: 'analytics.track', params: { event: 'page_view', page: '/home' }},
{ method: 'cache.invalidate', params: { key: 'homepage_data' }},
{ method: 'log.info', params: { message: 'User visited homepage' }}
]);
Python Client
import requests
import json
from typing import Any, Dict, List
class JsonRpcClient:
def __init__(self, endpoint: str):
self.endpoint = endpoint
self.session = requests.Session()
def notify(self, method: str, params: Any = None) -> None:
"""Send a notification (no response expected)"""
notification = {
'jsonrpc': '2.0',
'method': method
}
if params is not None:
notification['params'] = params
try:
self.session.post(
self.endpoint,
json=notification,
timeout=5 # Short timeout for notifications
)
except requests.RequestException as e:
# Only log network errors, don't raise
print(f"Network error sending notification: {e}")
def notify_batch(self, notifications: List[Dict[str, Any]]) -> None:
"""Send multiple notifications in batch"""
batch = []
for notif in notifications:
notification = {
'jsonrpc': '2.0',
'method': notif['method']
}
if 'params' in notif:
notification['params'] = notif['params']
batch.append(notification)
try:
self.session.post(
self.endpoint,
json=batch,
timeout=10
)
except requests.RequestException as e:
print(f"Network error sending batch notifications: {e}")
# Usage examples
client = JsonRpcClient('https://api.example.com/jsonrpc')
# Single notification
client.notify('user.logout', {'userId': '12345', 'sessionId': 'abc123'})
# Batch notifications
client.notify_batch([
{'method': 'analytics.track', 'params': {'event': 'logout'}},
{'method': 'session.cleanup', 'params': {'sessionId': 'abc123'}},
{'method': 'audit.log', 'params': {'action': 'user_logout', 'userId': '12345'}}
])
Node.js Server
const express = require('express');
const app = express();
app.use(express.json());
// Notification handlers
const notificationHandlers = {
'log.event': async (params) => {
console.log('Event logged:', params);
// Store in analytics database
await analytics.track(params);
},
'cache.invalidate': async (params) => {
console.log('Invalidating cache:', params.keys);
// Invalidate cache entries
await cache.del(params.keys);
},
'email.send': async (params) => {
console.log('Sending email to:', params.to);
// Queue email for background processing
await emailQueue.add('send-email', params);
},
'user.status.changed': async (params) => {
console.log('User status changed:', params);
// Broadcast to connected WebSocket clients
websocketServer.broadcast('user.status.changed', params);
}
};
// JSON-RPC handler
app.post('/jsonrpc', async (req, res) => {
try {
const request = req.body;
// Handle batch requests
if (Array.isArray(request)) {
await handleBatchNotifications(request);
// No response for notifications (even in batch)
res.status(204).end();
return;
}
// Single request
if (isNotification(request)) {
await handleNotification(request);
// No response for notifications
res.status(204).end();
} else {
// Handle regular request (with response)
const result = await handleRequest(request);
res.json(result);
}
} catch (error) {
console.error('Error handling request:', error);
// For notifications, still don't respond even on error
if (isNotification(req.body)) {
res.status(204).end();
} else {
res.json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal error' },
id: req.body.id || null
});
}
}
});
function isNotification(request) {
return request && !('id' in request);
}
async function handleNotification(notification) {
const handler = notificationHandlers[notification.method];
if (handler) {
try {
await handler(notification.params);
} catch (error) {
// Log error but don't respond (notifications are fire-and-forget)
console.error(`Error handling notification ${notification.method}:`, error);
}
} else {
console.warn(`Unknown notification method: ${notification.method}`);
}
}
async function handleBatchNotifications(batch) {
const promises = batch
.filter(isNotification)
.map(handleNotification);
// Handle all notifications concurrently
await Promise.allSettled(promises);
}
Best Practices
✅ Do
- Use notifications for fire-and-forget operations
- Batch multiple notifications to reduce network overhead
- Handle errors gracefully on the server side without responding
- Use short timeouts for notification requests
- Log notification failures for debugging
- Design your system to be resilient to lost notifications
❌ Don't
- Send responses to notifications (violates the spec)
- Use notifications for critical operations that require confirmation
- Include an 'id' field in notifications
- Expect error handling from the client side
- Use notifications for operations that need return values
- Rely on notifications for data consistency
WebSocket Integration
Notifications work particularly well with WebSocket connections for real-time communication.
WebSocket Notification Example
// Client-side WebSocket handler
const ws = new WebSocket('wss://api.example.com/websocket');
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
// Handle different notification types
switch (notification.method) {
case 'user.message.received':
displayNewMessage(notification.params);
break;
case 'user.status.changed':
updateUserStatus(notification.params);
break;
case 'system.maintenance.scheduled':
showMaintenanceNotice(notification.params);
break;
}
};
// Server can send notifications like:
{
"jsonrpc": "2.0",
"method": "user.message.received",
"params": {
"messageId": "msg_123",
"from": "alice",
"content": "Hello there!",
"timestamp": "2024-01-15T10:30:00Z"
}
}