Web Push API: Echte Push-Benachrichtigungen implementieren
AI generated
JS
() =>
JavaScript · Browser-APIs · Service Worker
Web Push API
Echte Push-Benachrichtigungen implementieren

Die Notifications API zeigt nur an, was bereits im Browser liegt. Wer Nutzer wirklich erreichen will, auch wenn der Tab laengst geschlossen ist, braucht die Web Push API: VAPID-Identitaet, Push-Subscription und einen Server, der ueber den Push-Dienst des Browsers zustellt.

17 Min. Lesezeit Web Push API VAPID Push Subscription Service Worker

1. Notifications API vs. Web Push API: zwei verschiedene Baustellen

Viele verwechseln die Notifications API mit Push-Benachrichtigungen, dabei loesen beide unterschiedliche Probleme. Die Notifications API zeigt ein System-Notification-Fenster an, wenn im Browser bereits JavaScript laeuft, das new Notification(...) aufruft. Sie ist reine Anzeige-Logik ohne Netzwerk-Anteil und funktioniert nur, solange eine Seite oder ein Service Worker aktiv ist.

Die Web Push API loest ein anderes Problem: Sie erlaubt einem Server, eine Nachricht an einen Browser zu schicken, selbst wenn kein Tab offen ist und die Website seit Tagen nicht besucht wurde. Moeglich wird das durch den Push-Dienst des Browserherstellers (etwa den Firebase Cloud Messaging Endpunkt bei Chrome oder den Mozilla Push Service bei Firefox), der als staendig erreichbarer Vermittler zwischen Server und Endgeraet fungiert. Der Server kennt diesen Dienst nicht direkt, sondern nur die individuelle Push-Subscription-URL des Nutzers.

2. VAPID-Schluesselpaar erzeugen: die Identitaet des Servers

VAPID (Voluntary Application Server Identification) ist ein Standard, mit dem sich ein Anwendungsserver gegenueber dem Push-Dienst ausweist, ohne dass der Push-Dienst-Betreiber vorher eine Registrierung verlangt. Das Schluesselpaar besteht aus einem oeffentlichen und einem privaten Schluessel auf Basis elliptischer Kurven (P-256). Der oeffentliche Schluessel wandert an den Client und wird beim Anlegen der Subscription mitgeschickt, der private Schluessel bleibt ausschliesslich auf dem Server und signiert spaeter jede ausgehende Push-Nachricht.

Die Erzeugung erfolgt einmalig, meist mit einer Bibliothek wie web-push in Node.js, und die Schluessel werden dauerhaft gespeichert, denn ein Wechsel invalidiert alle bestehenden Subscriptions. Wichtig ist, den privaten Schluessel wie ein Passwort zu behandeln und niemals im Client-Bundle auszuliefern, waehrend der oeffentliche Schluessel bedenkenlos im Frontend-Code stehen darf, da er nur zur Verifikation dient.


// Node.js: VAPID-Schluesselpaar einmalig erzeugen (Setup-Skript)
import webpush from 'web-push';

const vapidKeys = webpush.generateVAPIDKeys();

console.log('Public Key: ', vapidKeys.publicKey);
console.log('Private Key:', vapidKeys.privateKey);

// Beide Werte sicher ablegen, z. B. als Umgebungsvariablen:
// VAPID_PUBLIC_KEY=...
// VAPID_PRIVATE_KEY=...
webpush.setVapidDetails(
  'mailto:kontakt@mironsoft.de',
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY,
);

3. Push-Subscription im Browser anlegen

Auf Client-Seite braucht die Web Push API zwingend einen registrierten Service Worker, denn nur der Service Worker kann Push-Events empfangen, waehrend die Seite geschlossen ist. Ueber pushManager.subscribe() fordert der Client eine Subscription beim Push-Dienst des Browsers an und uebergibt dabei applicationServerKey, also den oeffentlichen VAPID-Schluessel in Uint8Array-Form. Das Ergebnis ist ein PushSubscription-Objekt mit einer eindeutigen endpoint-URL sowie den Verschluesselungsschluesseln p256dh und auth.

Diese drei Werte, endpoint, p256dh und auth, muessen an den eigenen Server geschickt und dort einem Nutzerkonto zugeordnet werden. Ohne vorherige Nutzer-Erlaubnis per Notification.requestPermission() schlaegt subscribe() fehl, daher sollte die Berechtigungsanfrage immer im Kontext einer klaren Nutzeraktion erfolgen, etwa einem Klick auf 'Benachrichtigungen aktivieren', statt automatisch beim Seitenaufruf.


// public/subscribe.js
const VAPID_PUBLIC_KEY = 'BEL9...oeffentlicherSchluessel...';

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const raw = atob(base64);
  return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)));
}

async function subscribeToPush() {
  const permission = await Notification.requestPermission();
  if (permission !== 'granted') return;

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

  await fetch('/api/push/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription),
  });
}

4. Push-Nachrichten im Service Worker empfangen

Trifft eine Nachricht beim Push-Dienst ein, weckt das Betriebssystem den Browser und der Service Worker erhaelt ein push-Event, selbst wenn keine Seite geoeffnet ist. Innerhalb des Handlers liegt die Nutzlast in event.data, meist als JSON kodiert, und muss ueber event.data.json() ausgelesen werden. Aus diesen Daten baut der Service Worker die Notification mit registration.showNotification() auf.

Entscheidend ist event.waitUntil(): Der Service Worker darf beendet werden, sobald der Event-Handler zurueckkehrt, daher muss die asynchrone showNotification()-Promise explizit uebergeben werden, damit der Browser wartet, bis die Benachrichtigung tatsaechlich sichtbar ist. Faellt dieser Schritt weg, kann es passieren, dass Nachrichten stillschweigend verloren gehen, weil der Prozess vorzeitig terminiert wird.


// sw.js
self.addEventListener('push', (event) => {
  const payload = event.data ? event.data.json() : { title: 'Neue Nachricht' };

  const options = {
    body: payload.body,
    icon: '/icons/push-icon-192.png',
    badge: '/icons/push-badge-72.png',
    data: { url: payload.url || '/' },
  };

  event.waitUntil(
    self.registration.showNotification(payload.title, options)
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(clients.openWindow(event.notification.data.url));
});

5. Server-seitiges Versenden ueber den Push-Service

Der eigene Server kommuniziert nie direkt mit dem Endgeraet, sondern schickt eine verschluesselte HTTP-Anfrage an die in der Subscription hinterlegte endpoint-URL des Push-Dienstes. Bibliotheken wie web-push uebernehmen dabei die komplette Verschluesselung nach dem Web-Push-Encryption-Standard (aes128gcm) sowie das Signieren mit dem privaten VAPID-Schluessel, sodass Entwickler sich nur um Payload und Empfaenger kuemmern muessen.

Antwortet der Push-Dienst mit Statuscode 404 oder 410, ist die Subscription abgelaufen, etwa weil der Nutzer die Berechtigung entzogen oder den Browser-Cache geloescht hat, und muss aus der eigenen Datenbank entfernt werden. Ein robustes Backend prueft diesen Fall bei jedem Versand und raeumt automatisch auf, statt wiederholt gegen tote Endpunkte zu senden.


// server/sendPush.js
import webpush from 'web-push';

export async function sendPushToUser(subscription, payload) {
  try {
    await webpush.sendNotification(
      subscription,
      JSON.stringify(payload),
    );
  } catch (error) {
    if (error.statusCode === 404 || error.statusCode === 410) {
      await removeExpiredSubscription(subscription.endpoint);
    } else {
      console.error('Push-Versand fehlgeschlagen:', error.statusCode, error.body);
    }
  }
}

6. Payload-Groesse, Nutzlast-Struktur und Ablauf

Push-Nachrichten sind bewusst klein gehalten: Der Standard erlaubt maximal 4 KB verschluesselte Nutzlast, deutlich zu wenig fuer Bilder oder komplette Inhalte. Der ueblich Weg ist daher, nur Titel, Kurztext und eine URL zu uebertragen, waehrend Icons und weitere Assets vom Client aus dem eigenen Cache oder Netzwerk nachgeladen werden, sobald die Notification gerendert wird.

Zusaetzlich laesst sich pro Nachricht ein TTL (Time To Live) setzen, das bestimmt, wie lange der Push-Dienst eine Nachricht fuer einen zeitweise offline befindlichen Client vorhaelt. Fuer zeitkritische Inhalte wie Live-Angebote sollte das TTL kurz gewaehlt werden, damit eine veraltete Nachricht nicht Tage spaeter verspaetet eintrifft, waehrend fuer allgemeine Hinweise ein laengeres TTL sinnvoll ist.


// TTL und Urgency beim Versand steuern
await webpush.sendNotification(subscription, JSON.stringify(payload), {
  TTL: 60 * 15,        // 15 Minuten gueltig
  urgency: 'high',     // low | normal | high, beeinflusst Batterieverhalten
});

7. Berechtigungs-UX: nicht beim Seitenaufruf fragen

Ein haeufiger Fehler ist, den Browser-Dialog fuer Notification-Berechtigungen sofort beim ersten Seitenbesuch aufzurufen. Nutzer klicken solche unerwarteten Prompts meist reflexartig weg, und einmal abgelehnt, kann die Berechtigung ohne manuellen Eingriff in den Browser-Einstellungen nicht erneut per JavaScript angefragt werden. Der Ablehnungsstatus ist praktisch dauerhaft.

Besser ist ein zweistufiger Ansatz mit eigener, gestaltbarer Voranfrage: Erst ein selbstgebautes UI-Element erklaert den Nutzen, etwa 'Erhalte eine Nachricht, wenn deine Bestellung versendet wird', und erst nach bewusster Zustimmung wird der native requestPermission()-Dialog ausgeloest. So steigt die Annahmequote spuerbar, weil Nutzer bereits wissen, worauf sie sich einlassen, bevor der Browser-Dialog erscheint.

8. Mehrere Geraete pro Nutzer und Sicherheitsaspekte

Ein Nutzer meldet sich haeufig von mehreren Geraeten an, und jedes Geraet erzeugt eine eigene, unabhaengige PushSubscription mit eigener endpoint-URL. Server-seitig sollte daher nicht eine einzelne Subscription pro Nutzer gespeichert werden, sondern eine Liste, verknuepft ueber die Nutzer-ID, damit eine Nachricht an alle aktiven Geraete gleichzeitig geht.

Beim Versand an mehrere Subscriptions eines Nutzers ist es sinnvoll, die Anfragen parallel statt sequenziell abzusetzen und jede einzeln gegen Fehler abzusichern, damit ein abgelaufener Endpunkt auf einem alten Handy nicht den Versand an die aktuell genutzten Geraete blockiert. Promise.allSettled() eignet sich hierfuer besser als Promise.all(), weil einzelne Fehlschlaege die uebrigen Zustellungen nicht abbrechen.

Push-Subscriptions enthalten personenbezogene, wenn auch pseudonyme Endpunkt-Daten und muessen entsprechend der DSGVO behandelt werden: Nutzer benoetigen eine klare Moeglichkeit, Push-Benachrichtigungen wieder abzubestellen, und der Server sollte Subscriptions loeschen, sobald ein Nutzerkonto geloescht wird. Die Ende-zu-Ende-Verschluesselung der Nutzlast schuetzt zwar den Inhalt vor dem Push-Dienst-Betreiber, nicht aber die Tatsache, dass ueberhaupt eine Nachricht gesendet wurde.

Auf Serverseite duerfen VAPID-Private-Key und Subscription-Datenbank niemals oeffentlich erreichbar sein. Ein kompromittierter privater Schluessel erlaubt es Angreifern, im Namen der Anwendung beliebige Nachrichten an alle Subscriber zu senden, weshalb der Schluessel wie andere Server-Secrets in einem Secret-Manager oder als Umgebungsvariable, nicht im Repository, verwaltet werden sollte.


// Versand an alle Geraete eines Nutzers, fehlertolerant
export async function sendPushToAllDevices(userId, payload) {
  const subscriptions = await getSubscriptionsForUser(userId);

  const results = await Promise.allSettled(
    subscriptions.map((sub) => sendPushToUser(sub, payload))
  );

  return results.filter((r) => r.status === 'fulfilled').length;
}

9. Fazit: Push richtig einsetzen statt nur implementieren

Die Web Push API ist kein Ersatz fuer E-Mail oder In-App-Benachrichtigungen, sondern ein zusaetzlicher, sehr direkter Kanal, der sparsam eingesetzt werden sollte. Technisch besteht der komplette Ablauf aus VAPID-Identitaet, Client-seitiger Subscription, Service-Worker-Empfang und Server-seitigem, verschluesseltem Versand, wobei jede Komponente unabhaengig getestet werden kann.

Wer die API produktiv einsetzt, sollte von Anfang an abgelaufene Subscriptions aufraeumen, TTL und Payload-Groesse bewusst waehlen und die Berechtigungsanfrage in eine durchdachte Nutzer-Erfahrung einbetten. Die folgende Tabelle fasst die zentralen Bausteine noch einmal zusammen.

Baustein Ort Aufgabe Wichtiges Detail
VAPID-Keys Server (einmalig) Identitaet gegenueber Push-Dienst Privater Schluessel niemals im Client
pushManager.subscribe() Client Subscription anlegen Benoetigt aktiven Service Worker
push-Event Service Worker Nachricht empfangen und anzeigen event.waitUntil() zwingend erforderlich
webpush.sendNotification() Server Verschluesselter Versand 404/410 = Subscription entfernen

Mironsoft

Moderne Browser-APIs, Performance und wartbares JavaScript

JavaScript, das im echten Browser robust bleibt, nicht nur im Tutorial?

Wir prüfen bestehenden Frontend-Code auf veraltete Patterns, unnötige Bibliotheken und Performance-Fallen und ersetzen sie durch moderne, native Browser-APIs, die weniger Bundle-Gewicht und weniger Wartungslast bedeuten.

Code-Review

Veraltete Patterns, unnötige Dependencies und Memory Leaks systematisch aufspüren.

Performance-Optimierung

Bundle-Größe, Ladezeit und Runtime-Performance mit modernen APIs verbessern.

Modernisierung

Native Browser-APIs statt schwerer Bibliotheken gezielt einführen.

10. Zusammenfassung

Web Push API: Das Wichtigste auf einen Blick

VAPID

Serveridentitaet per oeffentlichem und privatem Schluesselpaar, einmalig erzeugt

Subscription

Client-seitig ueber pushManager.subscribe() mit applicationServerKey angelegt

Empfang

Service Worker faengt push-Events ab, auch bei geschlossenem Tab

Versand

Server sendet verschluesselt an die endpoint-URL des Push-Dienstes

11. FAQ: Web Push API: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen der Notifications API und der Web Push API?
Die Notifications API zeigt nur ein Notification-Fenster an, wenn bereits JavaScript im Browser laeuft. Die Web Push API ermoeglicht zusaetzlich, dass ein Server eine Nachricht ueber den Push-Dienst des Browsers zustellt, selbst wenn kein Tab geoeffnet ist.
2Wozu dienen VAPID-Keys genau?
VAPID-Keys weisen den Anwendungsserver gegenueber dem Push-Dienst aus. Der oeffentliche Schluessel geht an den Client und wird bei der Subscription mitgeschickt, der private Schluessel signiert auf dem Server jede ausgehende Nachricht.
3Kann ich Push-Benachrichtigungen ohne Service Worker nutzen?
Nein. Eine PushSubscription wird ueber die pushManager-Schnittstelle des Service Workers angelegt, und nur der Service Worker kann push-Events empfangen, auch wenn kein Tab geoeffnet ist.
4Wie gross darf die Push-Payload sein?
Der Standard begrenzt die verschluesselte Nutzlast auf 4 KB. Fuer groessere Inhalte sollten nur Titel, Kurztext und eine URL uebertragen werden, der Rest wird clientseitig nachgeladen.
5Was bedeutet ein 404 oder 410 beim Versand an einen Endpunkt?
Diese Statuscodes zeigen an, dass die Subscription nicht mehr gueltig ist, etwa weil der Nutzer die Berechtigung entzogen hat. Die Subscription sollte dann aus der eigenen Datenbank entfernt werden.
6Warum wird der Notification-Dialog oft ignoriert oder abgelehnt?
Weil er haeufig ohne Kontext direkt beim Seitenaufruf erscheint. Eine eigene Voranfrage, die den Nutzen erklaert, bevor der native Dialog ausgeloest wird, erhoeht die Annahmequote deutlich.
7Wie funktioniert Push bei mehreren Geraeten desselben Nutzers?
Jedes Geraet erzeugt eine eigene PushSubscription mit eigener endpoint-URL. Der Server sollte alle Subscriptions eines Nutzers speichern und beim Versand parallel und fehlertolerant an alle senden.
8Was macht TTL bei einer Push-Nachricht?
Die Time To Live bestimmt, wie lange der Push-Dienst eine Nachricht fuer ein zeitweise offline befindliches Geraet vorhaelt. Zeitkritische Inhalte sollten ein kurzes TTL erhalten.
9Ist die Web Push API in allen Browsern verfuegbar?
Sie wird von allen modernen Browsern inklusive Safari ab macOS Ventura und iOS 16.4 unterstuetzt, allerdings verlangt Safari zusaetzlich, dass die Website als installierte Web-App genutzt wird.
10Welche Datenschutzaspekte muss ich beachten?
Subscriptions gelten als personenbezogene Daten und muessen abbestellbar sein. Beim Loeschen eines Nutzerkontos sollten auch dessen Subscriptions entfernt werden, und der private VAPID-Schluessel muss wie ein Secret geschuetzt werden.