---
title: "Send web push with VAPID and an encrypted · Agent Wranglers"
url: https://agentwranglers.se/playbook/notifications/web-push/web-push-vapid
id: NTF-10
level: Recommended
updated: 2026-10-21
build: a2a6bdfed830
tokens: 1080
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Send web push with VAPID and an encrypted payload

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Web push](https://agentwranglers.se/playbook/notifications/web-push.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: NTF-10
- Level: Recommended
- Chapter: [Web push](https://agentwranglers.se/playbook/notifications/web-push.md)
- Rule: 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.
- 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.
- How: 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 also accepts declarative web push messages, which show without running a service worker. That works from Safari 18.4 on iPhone and iPad for web apps on the Home Screen, and from Safari 18.5 on macOS. The private key belongs where [Keep secrets in a secrets store](https://agentwranglers.se/playbook/engineering/running-in-production/secrets-store.md) says.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
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 });
```

### Python example

```python
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"]},
    )
```

## Sources

- [RFC 8030, Generic Event Delivery Using HTTP Push](https://www.rfc-editor.org/rfc/rfc8030)
- [RFC 8291, Message Encryption for Web Push](https://www.rfc-editor.org/rfc/rfc8291)
- [RFC 8292, VAPID for Web Push](https://www.rfc-editor.org/rfc/rfc8292)

## Related rules

- NTF-9 [Ask for permission after a clear action](https://agentwranglers.se/playbook/notifications/web-push/push-permission-in-context.md) in Notifications
- ENG-26 [Keep secrets in a secrets store](https://agentwranglers.se/playbook/engineering/running-in-production/secrets-store.md) in Engineering
- NTF-41 [Send declarative push messages with a service worker fallback](https://agentwranglers.se/playbook/notifications/web-push/declarative-web-push.md) in Notifications
- STK-76 [Use the Web Push standard for browser push](https://agentwranglers.se/playbook/stack/product-services/web-push-standard.md) in Software choices

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/notifications/web-push/web-push-vapid">Agent Wranglers notifications playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Ask for permission after a clear action](https://agentwranglers.se/playbook/notifications/web-push/push-permission-in-context.md)
- Next: [Open the exact page the push is about](https://agentwranglers.se/playbook/notifications/web-push/push-deep-links.md)
- [Web push](https://agentwranglers.se/playbook/notifications/web-push.md)
