{"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":"python","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,"search_vec":"'2.3.0':25 'activ':28 'current':22 'encrypt':13 'fcm':36 'handl':11 'librari':6 'maintain':29 'messag':17 'notif':10,34 'period':31 'publish':8 'push':19 'python':5 'pywebpush':1,2 'releas':32 'send':15 'servic':20 'support':23 'vapid':35 'version':24 'webpush':9,33","created_at":"2026-04-11T09:27:37.797961+00:00","updated_at":"2026-04-16T20:37:33.683883+00:00","problems":[{"fix":"Verify that your `vapid_public_key` and `vapid_private_key` are correctly generated, stored, and passed as base64 URL-safe strings, and ensure `vapid_claims` contains a valid `sub` (e.g., `mailto:your@email.com`).","cause":"The VAPID keys (public or private) are incorrect, mismatched, or improperly formatted, leading to an authentication failure with the push service.","error":"pywebpush.webpush_errors.RequestError: Push service returned error 401: Unauthorized"},{"fix":"Reduce the content of the `message` sent to the push service, as larger payloads cannot be delivered via Web Push.","cause":"The size of the encrypted message payload exceeds the maximum limit (typically 4096 bytes) allowed by the push service provider.","error":"pywebpush.webpush_errors.WebPushException: Payload too large"},{"fix":"Ensure the `vapid_claims` dictionary includes a valid `sub` field (e.g., `{\"sub\": \"mailto:your@email.com\"}`) and that all values are correctly formatted.","cause":"The `vapid_claims` dictionary provided to `send_web_push` is missing required fields (like `sub`) or contains malformed values, causing the push service to reject the request.","error":"pywebpush.webpush_errors.RequestError: Push service returned error 400: Bad Request"},{"fix":"Change the import statement from `import webpush` to `import pywebpush` or from `from webpush import send_web_push` to `from pywebpush import send_web_push`.","cause":"The user is attempting to import the library using `webpush`, but the correct package name for the installed library is `pywebpush`.","error":"ModuleNotFoundError: No module named 'webpush'"}],"ecosystem":"pypi","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"2.4.0","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/web-push-libs/pywebpush","docs":null,"changelog":null,"pypi":"https://pypi.org/project/pywebpush/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["web-framework","http-networking","auth-security"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-08-29","next_check":"2026-07-28","install_tag":null}}