Skip to main content

GET /api/push/vapid-public-key

Get the VAPID public key for web push subscriptions. Authentication: Required

Response

publicKey
string
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

endpoint
string
required
Push service endpoint URL
keys
object
required

Response

{"ok": true}

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

endpoint
string
required
Push service endpoint URL to remove

Response

{"ok": true}

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:
~/.hapi/vapid-keys.json
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
}

Build docs developers (and LLMs) love