Agent Wranglers

Push notifications Required

Send web push with VAPID and an encrypted payload

Send web push through each subscription's push service with VAPID keys (RFC 8292) and an encrypted payload (RFC 8291). Never build your own polling or socket for background alerts.

How to do it

Make one VAPID key pair and keep the private key in the secret store. Put a mailto: or https: contact in the sub claim, so the push service can reach you. Send with a maintained library, such as web-push for Node.js, pywebpush for Python or webpush-go. It encrypts the payload with the keys in the subscription and signs the request. Store each subscription's endpoint and keys against the user, with a unique endpoint. A Topic value holds at most 32 characters from the URL-safe base64 alphabet. Safari 18.4 and later also accept declarative web push messages, which show without running a service worker.

Example

import webpush from 'web-push';

webpush.setVapidDetails('mailto:push@example.com', process.env.VAPID_PUBLIC_KEY, process.env.VAPID_PRIVATE_KEY);

export const sendPush = (subscription, message) =>
  webpush.sendNotification(subscription, JSON.stringify(message), { TTL: 3600, urgency: 'normal', topic: message.topic });
import json
import os
from pywebpush import webpush

def send_push(subscription: dict, message: dict):
    return webpush(
        subscription_info=subscription,
        data=json.dumps(message),
        vapid_private_key=os.environ["VAPID_PRIVATE_KEY"],
        vapid_claims={"sub": "mailto:push@example.com"},
        ttl=3600,
        headers={"Urgency": "normal", "Topic": message["topic"]},
    )

How to check

Send a test push to a staging subscription. The push service answers 201, and the notification shows on the device with the payload text.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers notifications playbook: https://agentwranglers.se/playbook/notifications/push-notifications/web-push-vapid.