{"id":3254,"library":"pywebpush","title":"pywebpush","description":"pywebpush is a Python library for publishing WebPush notifications, handling the encryption and sending of messages to push services. It currently supports version 2.3.0 and is actively maintained with periodic releases.","status":"active","version":"2.3.0","language":"en","source_language":"en","source_url":"https://github.com/web-push-libs/pywebpush","tags":["webpush","notifications","vapid","fcm"],"install":[{"cmd":"pip install pywebpush","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Requires Python 3.10 or newer.","package":"python","optional":false}],"imports":[{"note":"Convenience function for a single push notification send.","symbol":"webpush","correct":"from pywebpush import webpush"},{"note":"Class for more granular control, especially for resending to the same recipient or encoding data separately.","symbol":"WebPusher","correct":"from pywebpush import WebPusher"},{"note":"Exception class raised on push failures.","symbol":"WebPushException","correct":"from pywebpush import WebPushException"}],"quickstart":{"code":"import os\nimport json\nfrom pywebpush import webpush, WebPushException\n\n# --- Configuration --- #\n# Get VAPID private key from environment variable for security\nVAPID_PRIVATE_KEY = os.environ.get('WEBPUSH_VAPID_PRIVATE_KEY', 'your-vapid-private-key-here')\n# Your email or a mailto: URL for VAPID identification\nVAPID_SENDER_INFO = os.environ.get('WEBPUSH_SENDER_INFO', 'mailto:admin@example.com')\n\n# Example PushSubscription object (obtained from the client-side)\n# In a real application, this would be retrieved from a database.\nsubscription_info = {\n    \"endpoint\": \"https://fcm.googleapis.com/fcm/send/SOME_ENDPOINT_ID\",\n    \"keys\": {\n        \"auth\": \"SOME_AUTH_KEY\",\n        \"p256dh\": \"SOME_P256DH_KEY\"\n    }\n}\n\n# The payload data to send\nmessage_data = {\n    \"title\": \"Hello from pywebpush!\",\n    \"body\": \"Your notification arrived.\",\n    \"icon\": \"/images/notification-icon.png\"\n}\n\ntry:\n    # Define VAPID claims. The 'aud' (audience) must be the origin of the push service endpoint.\n    # pywebpush attempts to guess 'aud' but it's best to be explicit.\n    # 'exp' (expiration) is set to 12 hours by default if not provided.\n    vapid_claims = {\n        \"sub\": VAPID_SENDER_INFO,\n        \"aud\": subscription_info['endpoint'].split('/fcm/send/')[0] # Extract origin for FCM\n    }\n\n    print(\"Attempting to send push notification...\")\n    response = webpush(\n        subscription_info=subscription_info,\n        data=json.dumps(message_data), # Data should be a JSON string for common use cases\n        vapid_private_key=VAPID_PRIVATE_KEY,\n        vapid_claims=vapid_claims\n    )\n\n    print(f\"Push notification sent successfully! Status: {response.status_code}\")\n    print(f\"Response: {response.text}\")\n\nexcept WebPushException as e:\n    print(f\"Failed to send push notification: {e}\")\n    print(f\"Response body: {e.response_body}\")\n    # Handle specific errors, e.g., expired subscription, invalid VAPID keys\nexcept Exception as e:\n    print(f\"An unexpected error occurred: {e}\")\n\n# Example of using WebPusher class for more control (e.g., encoding data separately)\n# try:\n#     pusher = WebPusher(subscription_info)\n#     encoded_data = pusher.encode(json.dumps(message_data))\n#     # You can now send encoded_data with custom HTTP client if needed\n#     # Or use pusher.send() if you want pywebpush to handle HTTP request\n#     # response = pusher.send(json.dumps(message_data), headers=headers_dict) # headers_dict would include VAPID auth\n#     print(\"Data encoded successfully via WebPusher.\")\n# except WebPushException as e:\n#     print(f\"Error encoding data: {e}\")","lang":"python","description":"This quickstart demonstrates sending a WebPush notification using the `webpush` convenience function. It requires a `subscription_info` object (obtained from the client-side browser), a VAPID private key, and VAPID claims. The VAPID private key and sender info are typically loaded from environment variables for security. Data is sent as a JSON string. Error handling for `WebPushException` is included."},"warnings":[{"fix":"Ensure that `Webpusher.encode()` is always called with data, or explicitly handle the `NoData` exception if empty payloads are expected.","message":"In version 2.0.0, the `Webpusher.encode()` method was changed to raise a `NoData` exception if no data is present, instead of returning `None`. This impacts scenarios where empty payloads were implicitly allowed or handled differently.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"When calling `webpush()` or `WebPusher.send()`, pass `vapid_claims={'sub': 'mailto:your@example.com', 'aud': 'https://your-push-service-origin.com'}`. Extract the origin from the `subscription_info['endpoint']`.","message":"VAPID 'aud' claim is critical: Push services, especially FCM, require the `aud` claim in the VAPID token to explicitly match the origin of the push service endpoint (e.g., 'https://fcm.googleapis.com'). While `pywebpush` attempts to auto-fill this, explicitly setting it in `vapid_claims` is highly recommended to avoid `403 Forbidden` errors.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always use or ensure `aes128gcm` content encoding. pywebpush defaults to this, but be aware if manually configuring content types.","message":"The `aesgcm` content encoding is deprecated by RFC 8188. While pywebpush might still support it, the standard and default is `aes128gcm`. Not all user agents may decrypt `aesgcm` correctly.","severity":"deprecated","affected_versions":"All versions"},{"fix":"Ensure your push subscriptions are for FCM endpoints and use VAPID authentication instead of deprecated GCM keys.","message":"Google Cloud Messaging (GCM) has been sunset by Google. Users should migrate to Firebase Cloud Messaging (FCM). This library does not directly support sending messages to FCM using an `gcm_key` for authentication, which was disabled in June 2024.","severity":"deprecated","affected_versions":"All versions"},{"fix":"For robust applications, consider regenerating the VAPID token more frequently or explicitly setting a reasonable, shorter expiration (e.g., 5 minutes from `time.time()`) in `vapid_claims` if encountering authentication issues.","message":"VAPID 'exp' (expiration) claim: If not specified or set in the past, `pywebpush` will set it to 12 hours from now. However, invalid or expired JWTs (often due to clock skew or too long an expiration) can lead to `401` or `403` errors. A VAPID header can live for up to 24 hours.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-11T00:00:00.000Z","next_check":"2026-07-10T00:00:00.000Z"}