GET /api/push/vapid-public-key
Get the VAPID public key for web push subscriptions.
Authentication: Required
Response
VAPID public key in base64url format
Example
curl -H "Authorization: Bearer <token>" \
http://127.0.0.1:3006/api/push/vapid-public-key
{
"publicKey": "BKxN8ZQ..."
}
POST /api/push/subscribe
Subscribe to push notifications.
Authentication: Required
Request Body
Push service endpoint URL
Client public key (base64url)
Authentication secret (base64url)
Response
Example
curl -X POST -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"keys": {
"p256dh": "BDd3_hVL...",
"auth": "BTkDgZq..."
}
}' \
http://127.0.0.1:3006/api/push/subscribe
Errors
400 - Invalid body (missing endpoint or keys)
DELETE /api/push/subscribe
Unsubscribe from push notifications.
Authentication: Required
Request Body
Push service endpoint URL to remove
Response
Example
curl -X DELETE -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://fcm.googleapis.com/fcm/send/..."
}' \
http://127.0.0.1:3006/api/push/subscribe
Errors
400 - Invalid body (missing endpoint)
Web Push Integration
Step 1: Get VAPID Public Key
const response = await fetch('/api/push/vapid-public-key', {
headers: {
'Authorization': 'Bearer ' + jwtToken
}
})
const { publicKey } = await response.json()
Step 2: Request Permission
const permission = await Notification.requestPermission()
if (permission !== 'granted') {
throw new Error('Notification permission denied')
}
Step 3: Subscribe to Push Service
const registration = await navigator.serviceWorker.ready
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
})
Step 4: Send Subscription to Hub
const json = subscription.toJSON()
await fetch('/api/push/subscribe', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + jwtToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
endpoint: json.endpoint,
keys: {
p256dh: json.keys.p256dh,
auth: json.keys.auth
}
})
})
Helper Function
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4)
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/')
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
Notification Events
The hub sends push notifications for:
Permission Requests
When an agent requests permission:
{
"title": "Permission Request",
"body": "Claude wants to edit src/main.ts",
"data": {
"sessionId": "abc123",
"requestId": "req_456",
"url": "/sessions/abc123"
}
}
Session Ready
When a session becomes ready:
{
"title": "Session Ready",
"body": "My Project session is ready",
"data": {
"sessionId": "abc123",
"url": "/sessions/abc123"
}
}
Configuration
Optional Environment Variable
VAPID_SUBJECT=mailto:[email protected]
# or
VAPID_SUBJECT=https://your-domain.example
If not set, VAPID keys are auto-generated on first run.
VAPID Key Generation
The hub automatically generates VAPID keys and stores them in:
Keys are persisted across restarts.
Service Worker Example
// service-worker.js
self.addEventListener('push', (event) => {
const data = event.data.json()
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png',
badge: '/badge.png',
data: data.data
})
)
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil(
clients.openWindow(event.notification.data.url)
)
})
Browser Compatibility
Web Push is supported in:
- Chrome 50+
- Firefox 44+
- Edge 17+
- Safari 16+
- Opera 37+
Feature Detection
if ('serviceWorker' in navigator && 'PushManager' in window) {
// Push notifications supported
} else {
// Fall back to SSE or polling
}