PushItGood - web push notifications¶
Integration guide¶
Before starting make sure you have a pushitgood.eu client_id and apikey.
The apikey must be kept secret and must never appear in client side code.
Your web application needs to provide:
The service worker script, hosted at the top level directory of your site
An optional webhook endpoint
Include the javascript snippet on the page where you want users to subscribe to notifications.
The service worker¶
Add a javascript file in the root directory of your website called /worker.js, containing the following line:
importScripts("https://pushitgood.eu/v1/worker.js");
If you already have a file called /worker.js you can use another name,
but be sure to amend the calls to registerServiceWorker in the later JS code snippets.
The user-details endpoint¶
The user-details endpoint returns your client_id and information about the user currently being subscribed, formatted as a HS256-signed Javascript Web Token (JWT). A sample Python implementation looks like this:
def user_details(request):
return Response(
jwt.encode(
{
"client_id": "my client id",
"uid": "user identifier as a str",
"tags": ["list", "of", "tags"],
"webhook": "https://site.example/pushitgood-webhook",
},
key=APIKEY,
algorithm="HS256"
)
)
The payload is as follows:
uid: (string) uniquely identifies the user.tags: (list of strings) arbitrary tags that you can use to target a notification to a group of usersclient_id: (string) your pushitgood client idwebhook: (string) webhook endpoint url
The webhook endpoint¶
This endpoint is optional, but if present pushitgood will ping it on each of the following events:
Event |
event_type |
State |
|---|---|---|
User subscribes |
|
|
User unsubscribes |
|
|
Push sent to a user |
|
|
Push rejected by the upstream webpush service |
|
|
Push acknowledged by the browser |
|
|
Push timed out (no browser acknowledgement within timeout period) |
|
|
The endpoint must accept a POST request containing a Javascript Web Token. A sample Python implementation looks like this:
def webhook(request):
data = jwt.decode(request.get_body(), key=APIKEY)
# Either 'subscription' or 'notification'
event_type = data["event_type"]
# The user id as supplied by your user-details endpoint
uid = data["uid"]
# The user's subscription id
sid = data["sid"]
# If event_type is 'push', contains the notification's unique id
# This will match the id returned by the api endpoint when you sent the notification
nid = data["nid"]
# The push id
pid = data["pid"]
# If event_type is 'subscription', this will be one of subscribed, unsubscribed
#
# If event_type is 'notification', this will be one of:
# - sent: the notification has been accepted by the vendor supplied endpoint
# - failed: the notification could not be sent
# - received:the notification was received by the service worker on a user's device
# - timeout: the notification was not received by the user's device after the specified delay
state = data["state"]
print(
f"Push with id {pid} to user {uid} now has state {state}"
)
# Return an empty response
return Response()
Note that the browser allows users to stop notifications by disabling the notification permission – and in some cases encourages the user to do this if the site sends repeated notifications.
When this happens, the browser does not inform our service, and so the unsubscribe web hook is not called. The next time you send a notification you will receive a timeout webhook for this device.
Javascript snippet for subscribing users¶
Non-interactive version¶
The following JS snippet automatically subscribes the user on page load.
The user-details endpoint path must be coded into the the call to subscribeUser.
<script type="module">
import * as pig from "https://pushitgood.eu/v1/static/susbcribe.js";
pig.registerServiceWorker("/worker.js");
// Automatically subscribe the user, requesting permission as required
pig.getSubscription().then(
(subscription) {
if (subscription === null) {
pig.subscribeUser("/path/to/user-details-endpoint.json");
}
}
}
</script>
Interactive version¶
The following JS snippet displays a subscribe / unsubscribe button, and
requires positive user interaction before the user is subscribed.
Again, the user-details endpoint path must be coded into the the call to subscribeUser.
<div id="ButtonContainer"></div>
<script type="module">
import * as pig from "https://pushitgood.eu/v1/static/subscribe.js";
pig.registerServiceWorker("/worker.js");
let button = document.createElement("button");
document.getElementById("ButtonContainer").append(button);
button.addEventListener(
"click",
(event) => {
if (event.target.name == "subscribe") {
pig.subscribeUser("/path/to/user-details-endpoint");
} else {
pig.unsubscribeUser("/path/to/user-details-endpoint");
}
}
);
pig.registerSubscriptionCallback(
(subscriptionResponse) => {
button.innerHTML = (subscriptionResponse.subscription) ? "Stop notifications!" : "Get notifications!";
button.name = (subscriptionResponse.subscription) ? "unsubscribe" : "subscribe";
}
)
</script>
registerSubscriptionCallback and handling failed subscriptions¶
The function pig.registerSubscriptionCallback registers a function to be
called:
When the service worker is registered (typically on page load)
Whenever
pig.subscribeUseris calledWhenever
pig.unsubscribeUseris called
The function will be called with an object containing the following keys:
subscriptionThe PushSubscription object, or null if the user is not subscribed
actionOne of
'subscribe','unsubscribe', or'register_serviceworker'resultFor
subscribeactions, this will be one of'granted'ordenied, reflecting the user’s browser notification permission. For other actions this istrue.
If a user has denied the notification permission in their browser settings, subscription requests will fail silently – the browser does not give any indication that the subscription request was denied and does not prompt the user to grant the notification permission.
In this case you may want to use a subscription callback to display a suitable message:
pig.registerSubscriptionCallback(
(subscriptionResponse) => {
if (subscriptionResponse.action === "subscribe" && subscriptionResponse.result != "granted") {
alert("Please enable notifications for this site before trying again");
}
}
)
Sending a notification¶
Send a POST request to https://pushitgood.eu/v1/notify with the
following payload, encoded as a JWT signed with your APIKEY.
A sample Python implementation looks like this:
import requests
data = {
"client_id": CLIENT_ID,
"uid": "555",
"title": "Notification title"
"body": "Notification content"
"url": "URL to open when notification is clicked"
"tags": ["tags", "to", "target"],
"actions": [
{
"action": "custom-action",
"title": "Custom action",
"icon": "https://site.example/action.png"
}
],
"timeout": 3600,
"webhook": "https://site.example/pushitgood-webhook",
}
requests.post(
"https://pushitgood.eu/v1/notify",
jwt.encode(data, key=APIKEY, algorithm="HS256")
)
# Result of the notification, see below for explanation
result = requests.json()
You can also send the payload as a JSON payload, with the APIKEY specified in an authorization header, for example:
$ curl \
--header "Content-Type: application/json" \
--header "Authorization: Bearer $APIKEY" \
--request POST \
--data '{"uid":"555","title":"Notification title", ... }' \
https://pushitgood.eu/v1/notify
If uid is specified, a notification will be sent only to the user with that uid.
If tags is specified, notifications will be sent to any user that has one
or more of the listed tags.
If neither is specified, notifications will be sent to all your subscribed users.
If timeout is specified, this is the time in seconds that pushitgood will
wait for a confirmation that the notification has been received before updating
the notification’s state to timeout and calling your webhook.
The notify endpoint will send notifications to each device subscribed for the specified user(s), and return a JSON data structure in the following format:
{
// Unique identifier for the notification
"nid": "...",
// Each notification fans out to multiple Push API
// requests, one per subscribed device.
"pushes": [
{
// Identifier for this push
"pid": "...",
// user the push was sent to
"uid": "...",
// subscription the push was sent to
"sid": "...",
},
...
]
}
Custom actions¶
The notifications API allows you to define custom actions to be displayed under the notification body.
Note: notification actions are only available in Chrome and Edge. Browser may also limit how many actions are displayed.
Use the actions property of the notify api payload
to define notification actions:
data = {
...
"actions": [
{
"action": "custom-action",
"title": "Custom action",
"icon": "https://site.example/action.png"
}
],
}
The action property is used by javascript code to identify the
selected action. The title and icon properties define the text and
image used for the button that the user clicks on,
The default pushitgood event listener does not handle custom actions,
so to use notification actions you must supply your own.
Amend your /worker.js script as follows:
importScripts("https://pushitgood.eu/v1/worker.js");
// Remove pushitgood's default event listener
self.removeDefaultNotificationClickListener();
// Register an event listener to handle your site's custom actions
self.addEventListener(
"notificationclick",
(event) => {
event.notification.close();
switch (event.action) {
// Replace this with your custom action name.
// Edit the openWindow call to include the url to load
case "custom-action":
event.waitUntil(self.openWindow(...));
break;
// If you have multiple custom actions,
// add more cases as necessary
case "custom-action-2":
event.waitUntil(self.openWindow(...));
break;
// The default case will be used if the user does not
// select an action, or if the browser does not support
// notification actions
default:
event.waitUntil(self.openWindow(event.notification.data.url));
}
}
);
API sequence diagrams¶
Registration:
Browser
JS Service Worker Vendor pushitgood.eu Your webapp
| : : | :
| : : | :
| GET userDetails | |
|------------------------------------------------------------------->|
| : : | |
| 200 OK <JWT of user details> | |
|<-------------------------------------------------------------------|
| : | | |
| : | | :
| request subscription | | :
|-------------------------->| | :
| : | | :
|<---subscription obj-------| | :
| : : | :
| : : | :
| POST /register <subscription+user> | :
| (contains unique notification vendor endpoint | :
| required to later send a notification) | :
|----------------------------------------------->| :
| : : | POST webhook :
| : : |------------------>:
| : : | :
Send notification:
Browser
Your webapp pushitgood.eu Vendor(s) User agent
| | : :
| POST /notify | : :
|--------------------->| : :
| | : :
|<---notification obj--| : :
| | | : \
| | WebPush request | : |
| |----------------->| | |
| POST <your-webhook> | | push event | |
| state="sent|failed" | |------------->| |
|<---------------------| | | | Repeated
| | | | | for
| | | | | each
| | POST /ping | | | subscription
| POST <your-webhook> |<--------------------------------| |
| state="received" | | | |
|<----push obj---------| | | /