DEV Community

Bipin C
Bipin C

Posted on

# Self-Hosted Push Notifications Part-8

Self-Hosted Push Notifications Specification

Part 8: Complete Reference & FAQ

Version: 1.0
Last Updated: October 2025
Prerequisites: Part 7: Best Practices, Security & Optimization
Author: Bunty9
License: MIT (Free to use and adapt)


Table of Contents

  1. Complete API Reference
  2. Browser Compatibility Matrix
  3. Frequently Asked Questions (FAQ)
  4. Troubleshooting Guide
  5. Migration Guides
  6. Glossary of Terms
  7. Additional Resources
  8. Specification Summary

Complete API Reference

Backend Endpoints

POST /api/push/subscribe

Subscribe to push notifications.

Request:

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/...",
  "keys": {
    "p256dh": "BGt...",
    "auth": "abc..."
  },
  "userAgent": "Mozilla/5.0..."
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "success": true,
  "message": "Subscribed successfully",
  "data": {
    "subscriptionId": "550e8400-e29b-41d4-a716-446655440000",
    "deviceId": "web-abc123",
    "deviceName": "Chrome on macOS"
  }
}
Enter fullscreen mode Exit fullscreen mode

Status Codes:

  • 200 OK - Subscription successful
  • 400 Bad Request - Invalid request body
  • 401 Unauthorized - Authentication required
  • 429 Too Many Requests - Rate limit exceeded

POST /api/push/unsubscribe

Unsubscribe from push notifications.

Request:

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/..."
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "success": true,
  "message": "Unsubscribed successfully"
}
Enter fullscreen mode Exit fullscreen mode

GET /api/push/devices

Get all devices subscribed for the authenticated user.

Response:

{
  "success": true,
  "count": 3,
  "data": {
    "devices": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "deviceId": "web-abc123",
        "deviceName": "Chrome on macOS",
        "browser": "Chrome",
        "os": "macOS",
        "deviceType": "desktop",
        "isActive": true,
        "lastUsedAt": "2025-10-22T10:30:00Z",
        "createdAt": "2025-10-01T08:00:00Z"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

POST /api/push/test/:userId (Admin Only)

Send a test notification to a specific user.

Request:

{
  "title": "Test Notification",
  "body": "This is a test notification",
  "url": "/",
  "deviceId": "web-abc123" // Optional: target specific device
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "success": true,
  "message": "Test notification sent successfully",
  "data": {
    "userId": "550e8400-e29b-41d4-a716-446655440000",
    "deviceId": "web-abc123"
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend API

navigator.serviceWorker.register()

Register service worker for push notifications.

// Usage
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service worker registered:', registration);
Enter fullscreen mode Exit fullscreen mode

Notification.requestPermission()

Request notification permission from user.

// Usage
const permission = await Notification.requestPermission();
console.log('Permission:', permission); // "granted", "denied", or "default"
Enter fullscreen mode Exit fullscreen mode

Returns: Promise<NotificationPermission>


registration.pushManager.subscribe()

Subscribe to push notifications with VAPID key.

// Usage
const subscription = await registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});

console.log('Subscription:', subscription.toJSON());
Enter fullscreen mode Exit fullscreen mode

Returns: Promise<PushSubscription>


registration.showNotification()

Show a notification (from service worker).

// Usage
self.registration.showNotification('Title', {
  body: 'Notification body',
  icon: '/icon-192x192.png',
  badge: '/icon-72x72.png',
  data: { url: '/bookings/123' },
  actions: [
    { action: 'view', title: 'View', icon: '/view-icon.png' },
    { action: 'dismiss', title: 'Dismiss', icon: '/dismiss-icon.png' }
  ]
});
Enter fullscreen mode Exit fullscreen mode

Push Payload Schema

interface PushPayload {
  // Required
  title: string;                      // Max 65 characters
  body: string;                       // Max 240 characters

  // Optional display
  icon?: string;                      // URL to icon (192x192 recommended)
  badge?: string;                     // URL to badge (72x72 recommended)
  image?: string;                     // URL to large image
  tag?: string;                       // Notification grouping ID

  // Behavior
  requireInteraction?: boolean;       // Keep until dismissed
  silent?: boolean;                   // Suppress sound
  vibrate?: number[];                 // Vibration pattern [ms]
  renotify?: boolean;                 // Re-alert on tag update

  // Navigation
  url?: string;                       // URL to open on click

  // Custom data
  data?: {
    [key: string]: any;               // Custom payload data
  };

  // Actions (max 2 on mobile, 4 on desktop)
  actions?: Array<{
    action: string;                   // Action identifier
    title: string;                    // Button text
    icon?: string;                    // Button icon
  }>;
}
Enter fullscreen mode Exit fullscreen mode

Browser Compatibility Matrix

Desktop Browsers

Feature Chrome Firefox Safari Edge Opera
Service Workers ✅ 40+ ✅ 44+ ✅ 11.1+ ✅ 17+ ✅ 27+
Push API ✅ 42+ ✅ 44+ ✅ 16+ ✅ 17+ ✅ 29+
Notification API ✅ 42+ ✅ 44+ ✅ 16+ ✅ 17+ ✅ 29+
Background Sync ✅ 49+ ❌ No ❌ No ✅ 79+ ✅ 36+
Notification Actions ✅ 48+ ❌ No ⚠️ Limited ✅ 18+ ✅ 35+

Mobile Browsers

Feature Chrome (Android) Firefox (Android) Safari (iOS) Samsung Internet
Service Workers ✅ 40+ ✅ 44+ ✅ 11.3+ ✅ 4.0+
Push API ✅ 42+ ✅ 44+ ⚠️ 16.4+ ✅ 4.0+
Notification API ✅ 42+ ✅ 44+ ⚠️ Limited ✅ 4.0+
PWA Required ❌ No ❌ No ✅ Yes ❌ No

Important Notes

iOS Safari:

  • Push notifications only work if app is added to home screen (PWA)
  • Must open app from home screen icon (not browser)
  • Requires iOS 16.4+ and Safari 16.4+
  • No support for notification actions
  • Limited customization

Detection Code:

const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isStandalone = window.navigator.standalone;
const isPWA = window.matchMedia('(display-mode: standalone)').matches;

if (isIOS && !isStandalone && !isPWA) {
  // Show "Add to Home Screen" instructions
  showPWAInstallPrompt();
}
Enter fullscreen mode Exit fullscreen mode

Frequently Asked Questions (FAQ)

General Questions

Q: Do I need FCM or other third-party services?

A: No! This is a completely self-hosted solution. The browser's push service (FCM for Chrome, Mozilla Push for Firefox) is used internally by the browser, but you don't need an FCM account or API key. VAPID keys are all you need.


Q: How much does it cost to run?

A: $0 for push notifications. You only pay for:

  • Your server infrastructure (backend + database)
  • Domain and SSL certificate
  • Optional monitoring tools

No per-message costs, no quotas, no vendor lock-in.


Q: What happens if my server goes down?

A: Notifications sent during downtime are lost. The Web Push Protocol has a TTL (Time To Live) of 30 days by default, but browser push services don't queue messages indefinitely. Implement:

  • High availability (multiple backend instances)
  • Health checks and auto-restart
  • Monitoring and alerting

Q: Can I send notifications to iOS users?

A: Yes, but with limitations:

  • Only works if app is added to home screen (PWA)
  • User must open app from home screen icon
  • Requires iOS 16.4+ and Safari 16.4+
  • No notification actions
  • About 30% of mobile users (as of 2025)

Consider fallback strategies like email or SMS for iOS users.


Technical Questions

Q: How do VAPID keys work?

A: VAPID (Voluntary Application Server Identification) uses asymmetric cryptography:

  1. Private Key: Kept secret on your server, signs push messages
  2. Public Key: Shared with browsers during subscription
  3. Signature: Browser validates that push came from your server

Think of it like SSH keys or SSL certificates.


Q: Can I reuse VAPID keys across environments?

A: Not recommended. Use different keys for:

  • Development
  • Staging
  • Production

This prevents cross-environment issues and improves security.


Q: How many devices can a user have?

A: By default, 5 devices per user/admin. This is configurable:

// Change device limit
if count >= 10 { // Increase to 10
    return errors.New("device limit reached")
}
Enter fullscreen mode Exit fullscreen mode

Each browser on each device counts as one subscription.


Q: What's the maximum notification size?

A: Approximately 4KB total payload. Recommendations:

  • Title: Max 65 characters
  • Body: Max 240 characters
  • Icon: URL only (image not embedded)
  • Keep data object small

Q: How fast are notifications delivered?

A: Typically < 1 second from your server to user's device:

  • Your server → Browser push service: ~50ms
  • Browser push service → User device: ~500ms
  • Service worker processing: ~50ms

Network conditions and device state affect delivery time.


Q: Can I send rich media notifications?

A: Yes! You can include:

  • ✅ Icons (192x192 recommended)
  • ✅ Badges (72x72)
  • ✅ Images (any size, but keep < 1MB)
  • ✅ Vibration patterns
  • ✅ Action buttons (up to 4)
  • ❌ Videos (not supported)
  • ❌ Interactive forms (not supported)

Security Questions

Q: Is it secure?

A: Yes, if implemented correctly:

  • ✅ VAPID authentication prevents spoofing
  • ✅ HTTPS required for service workers
  • ✅ End-to-end encryption (browser handles)
  • ✅ User consent required
  • ✅ Subscriptions tied to origin (can't spoof)

Follow security best practices in Part 7.


Q: Can someone steal my VAPID private key?

A: If compromised:

  1. Attacker can send push notifications as your app
  2. Rotate keys immediately (see Part 7)
  3. Users will need to resubscribe
  4. Monitor for unusual notification patterns

Store private keys securely:

  • Environment variables (not in code)
  • Secrets manager (AWS Secrets Manager, HashiCorp Vault)
  • Kubernetes secrets

Q: Can users unsubscribe?

A: Yes, users can:

  1. Click "Unsubscribe" in your app UI
  2. Revoke permissions in browser settings
  3. Clear browser data (removes subscription)

Always provide an easy unsubscribe option.


Scaling Questions

Q: How many notifications can I send per second?

A: Depends on your infrastructure:

Setup Notifications/second
Single server ~100-500
3 replicas ~300-1500
10 replicas ~1000-5000
With rate limiting Depends on limits

Bottlenecks:

  • Database queries
  • HTTP connections to push services
  • Goroutine limits

Q: Do I need a queue system?

A: Optional, but recommended for:

  • High volume (>1000 notifications/second)
  • Guaranteed delivery with retries
  • Priority queuing
  • Rate limiting across multiple servers

Use Redis, RabbitMQ, or AWS SQS.


Q: How do I handle multiple backend replicas?

A:

  1. Database Connection Pooling: Prevents connection exhaustion
  2. Stateless Services: No in-memory state
  3. Worker Coordination: Use distributed locks (Redis) for background workers
  4. Load Balancing: Round-robin or least-connections

See Part 5 for horizontal scaling strategies.


Troubleshooting Guide

Symptom: Notifications not showing up

Possible Causes:

  1. Permission denied
   // Check permission
   console.log('Permission:', Notification.permission);

   // If "denied", user must manually enable in browser settings
Enter fullscreen mode Exit fullscreen mode
  1. Service worker not registered
   // Check registration
   navigator.serviceWorker.getRegistrations().then(regs => {
     console.log('Registrations:', regs);
   });
Enter fullscreen mode Exit fullscreen mode
  1. Subscription expired
   // Check subscription
   navigator.serviceWorker.ready.then(async reg => {
     const sub = await reg.pushManager.getSubscription();
     console.log('Subscription:', sub);
   });
Enter fullscreen mode Exit fullscreen mode
  1. Backend error
   # Check backend logs
   kubectl logs -f deployment/user-service | grep "Push"
Enter fullscreen mode Exit fullscreen mode

Symptom: High failure rate

Debugging Steps:

-- Check error messages
SELECT
  error_message,
  http_status_code,
  COUNT(*) as count
FROM push_notification_logs
WHERE status = 'failed'
  AND sent_at > NOW() - INTERVAL '1 hour'
GROUP BY error_message, http_status_code
ORDER BY count DESC;
Enter fullscreen mode Exit fullscreen mode

Common Solutions:

Error Solution
410 Gone Subscription expired (auto-handled)
403 Forbidden Check VAPID keys
400 Bad Request Validate payload format
429 Too Many Requests Implement rate limiting

Symptom: Memory leak

Debugging:

# Check memory usage
go tool pprof http://localhost:6060/debug/pprof/heap

# Check goroutines
go tool pprof http://localhost:6060/debug/pprof/goroutine
Enter fullscreen mode Exit fullscreen mode

Common Causes:

  • Goroutine leaks (not closing channels)
  • Database connection leaks
  • Unbounded caches

Symptom: Slow notification delivery

Debugging:

-- Check p95 latency
SELECT
  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY (sent_at - created_at)) as p95_latency
FROM push_notification_logs
WHERE sent_at > NOW() - INTERVAL '1 hour';
Enter fullscreen mode Exit fullscreen mode

Optimization:

  • Add database indexes
  • Increase connection pool size
  • Use goroutine pooling
  • Implement caching

Migration Guides

From FCM to Self-Hosted

Step 1: Generate VAPID Keys

npm install -g web-push
web-push generate-vapid-keys
Enter fullscreen mode Exit fullscreen mode

Step 2: Update Backend

  • Remove FCM SDK
  • Install webpush-go
  • Implement PushService (see Part 2)

Step 3: Update Frontend

  • Remove FCM firebase-messaging-sw.js
  • Implement sw.js (see Part 4)
  • Update subscription flow

Step 4: Migrate Existing Subscriptions

-- Map FCM tokens to new subscriptions
-- Users will need to resubscribe
-- Consider grace period with both systems running
Enter fullscreen mode Exit fullscreen mode

From OneSignal to Self-Hosted

Key Differences:

Feature OneSignal Self-Hosted
Setup Time 10 minutes 2-4 hours
Cost Free tier, then $99+/mo Infrastructure only
Vendor Lock-in Yes No
Customization Limited Complete
Analytics Built-in DIY (Prometheus)

Migration Steps: Same as FCM migration above.


Glossary of Terms

VAPID

Voluntary Application Server Identification. Authentication mechanism using public/private key pairs to verify push notification origin.

Service Worker

JavaScript file that runs in the background, separate from web pages. Enables push notifications, offline support, and background sync.

Push Subscription

Object containing endpoint URL and encryption keys that identifies a device for push delivery.

Endpoint

HTTPS URL provided by browser's push service (e.g., FCM, Mozilla Push) where notifications are sent.

p256dh

Public Key for Diffie-Hellman. Used to encrypt notification payloads.

auth

Authentication Secret. Used alongside p256dh for encryption.

TTL

Time To Live. How long push services keep notifications if device is offline (default: 30 days).

PWA

Progressive Web App. Web application that can be installed like a native app and supports offline functionality.

Goroutine

Lightweight thread in Go for concurrent execution.

GORM

Go Object-Relational Mapping. Database ORM library for Go.

JWT

JSON Web Token. Token-based authentication standard.


Additional Resources

Official Specifications

Libraries & Tools

Backend (Go):

  • webpush-go - Web Push Protocol implementation
  • Gin - HTTP web framework
  • GORM - ORM library

Frontend (JavaScript/TypeScript):

Testing:

Monitoring:

Community & Support

  • Stack Overflow: Tag [web-push] or [service-worker]
  • GitHub Discussions: Create discussions on this repo
  • MDN Web Docs: Comprehensive browser API documentation
  • Can I Use: Browser compatibility checker (caniuse.com)

Learning Resources


Specification Summary

What You've Built

By following this 8-part specification, you've implemented:

Complete self-hosted push notification system
No third-party dependencies (FCM, OneSignal, etc.)
Production-ready backend (Go with GORM)
Production-ready frontend (Next.js with TypeScript)
Service worker with background handling
PWA manifest for installability
Multi-device support (up to 5 per user)
Advanced targeting (users, admins, roles, broadcast)
Notification scheduling with background workers
Rate limiting & throttling
Batch processing for scalability
Retry strategies with exponential backoff
Docker & Kubernetes deployment configs
Monitoring & alerting (Prometheus, Grafana)
Health checks and error tracking
Security best practices (VAPID, HTTPS, validation)
Performance optimization (indexing, pooling, caching)
Comprehensive testing (unit, integration, E2E, load)

Specification Parts Recap

Part Title Topics Covered
Part 1 Architecture & Setup System overview, VAPID, database schema, project structure
Part 2 Backend Implementation GORM models, PushService, controllers, routes, middleware
Part 3 Frontend Implementation API routes, hooks, components, TypeScript types
Part 4 Service Worker & PWA SW implementation, PWA manifest, action handlers, offline
Part 5 Advanced & Deployment Targeting, scheduling, rate limiting, Docker, Kubernetes
Part 6 Monitoring & Debugging Metrics, logging, profiling, health checks, alerting
Part 7 Best Practices & Security Security, VAPID management, validation, optimization, testing
Part 8 Reference & FAQ API reference, browser compatibility, FAQ, troubleshooting

Estimated Implementation Time

Component Time Estimate
Setup & Architecture 2-4 hours
Backend Implementation 8-12 hours
Frontend Implementation 6-10 hours
Service Worker & PWA 4-6 hours
Testing & QA 6-8 hours
Deployment & Monitoring 4-6 hours
Documentation 2-4 hours
Total 32-50 hours

With AI assistance (agents), implementation time can be reduced by 50-70%.

Production Readiness Checklist

Before going live:

  • [ ] VAPID keys generated and secured
  • [ ] Database migrations applied
  • [ ] All environment variables configured
  • [ ] Service workers registered and tested
  • [ ] PWA manifest configured
  • [ ] SSL/HTTPS enabled
  • [ ] Rate limiting configured
  • [ ] Monitoring and alerting set up
  • [ ] Error tracking integrated (Sentry)
  • [ ] Health checks passing
  • [ ] Load testing completed (target throughput achieved)
  • [ ] Security audit passed
  • [ ] Backup strategy in place
  • [ ] Documentation complete
  • [ ] Team trained
  • [ ] Rollback plan prepared

Next Steps After Implementation

  1. Monitor metrics for first 24 hours
  2. Gather user feedback on notification experience
  3. Optimize based on real-world usage patterns
  4. A/B test notification content and timing
  5. Iterate on targeting strategies
  6. Scale infrastructure as needed
  7. Plan key rotation (6-12 months)

Final Notes

This Specification is Open Source

Feel free to:

  • ✅ Use this specification for your projects
  • ✅ Adapt and modify as needed
  • ✅ Share with your team
  • ✅ Contribute improvements

Contributing

If you find issues or have improvements:

  1. Open an issue on GitHub
  2. Submit a pull request
  3. Share your implementation stories
  4. Help others in discussions

License

MIT License - Free to use, modify, and distribute.

Author

Researched and Implemented with ❤️ by Bunty9

Congratulations! 🎉

You've completed the Self-Hosted Push Notifications Specification.

You now have the knowledge and code to build a complete, production-ready push notification system without any third-party dependencies.

Happy coding! 🚀

Top comments (0)