The notification¶
To reach the client (web browser), you need to send a Notification to the Subscription.
The Notification should have a payload. In this case, the payload will be encrypted on server side and decrypted by the client.
That payload may be a string or a JSON object. The structure of the latter is described in the next section.
<?php
use WebPush\Notification;
$notification = Notification::create()
->withPayload('Hello world')
;
TTL (time-to-live)¶
With this feature, a value in seconds is added to the notification. It suggests how long a push message is retained by the push service. A value of 0 (zero) indicates the notification is delivered immediately.
Using TTL constants¶
The library provides predefined constants for common TTL values:
<?php
use WebPush\Notification;
// Using constants (recommended)
$notification = Notification::create()
->withTTL(Notification::TTL_ONE_HOUR);
// Available constants:
// TTL_IMMEDIATE = 0 (deliver immediately or not at all)
// TTL_ONE_MINUTE = 60
// TTL_FIVE_MINUTES = 300
// TTL_TEN_MINUTES = 600
// TTL_ONE_HOUR = 3600
// TTL_ONE_DAY = 86400
// TTL_ONE_WEEK = 604800
// TTL_FOUR_WEEKS = 2419200
Using custom TTL values¶
You can also specify custom TTL values in seconds:
<?php
use WebPush\Notification;
$notification = Notification::create()
->withTTL(3600); // 1 hour in seconds
Topic¶
A push message that has been stored by the push service can be replaced with new content. If the user agent is offline during the time the push messages are sent, updating a push message avoids the situation where outdated or redundant messages are sent to the user agent.
Only push messages that have been assigned a topic can be replaced. A push message with a topic replaces any outstanding push message with an identical topic.
<?php
use WebPush\Notification;
$notification = Notification::create()
->withTopic('user-account-updated')
;
Urgency¶
For a device that is battery-powered, it is often critical it remains dormant for extended periods.
Radio communication in particular consumes significant power and limits the length of time the device can operate.
To avoid consuming resources to receive trivial messages, it is helpful if an application server can communicate the urgency of a message and if the user agent can request that the push server only forwards messages of a specific urgency.
| Urgency | Device State | Examples |
|---|---|---|
| very-low | On power and Wi-Fi | Advertisements |
| low | On either power or Wi-Fi | Topic updates |
| normal | On neither power nor Wi-Fi | Chat or Calendar Message |
| high | Low battery | Incoming phone call or time-sensitive alert |
Warning
Be careful with the very-low urgency: it is not recognized by all Web-Push services
<?php
use WebPush\Notification;
$notification = Notification::create()
->veryLowUrgency()
->lowUrgency()
->normalUrgency()
->highUrgency()
;
Asynchronous response¶
Your application may prefer asynchronous responses to request confirmation from the push service when a push message is delivered and then acknowledged by the user agent. The push service MUST support delivery confirmations to use this feature.
<?php
use WebPush\Notification;
$notification = Notification::create()
->async() // Prefer async response
->sync() // Prefer sync response (default)
;
Warning
The async mode is not recognized by all Web Push services. In case of failure, you should try sending sync notifications.
JSON messages¶
As mentioned in the overview section, the specification defines a structure for the payload. This structure contains properties that the client should understand and render in an appropriate way.
The library provides a WebPush\Message class with convenient methods to ease the creation of a message.
<?php
use WebPush\Action;
use WebPush\Message;
use WebPush\Notification;
$message = Message::create('This is the title')
->mute() // Silent
->unmute() // Not silent (default)
->auto() //Direction = auto (default)
->ltr() //Direction = left to right
->rtl() //Direction = right to left
->addAction(Action::create('alert', 'Click me!'))
->interactionRequired()
->noInteraction()
->renotify()
->doNotRenotify() // Default
->withBody('Hello World!')
->withIcon('https://…')
->withImage('https://…')
->withData(['foo' => 'BAR']) // Arbitrary data
->withBadge('badge1')
->withLang('fr-FR')
->withTimestamp(time())
->withTag('foo')
->vibrate(300, 100, 400)
->toString() // Converts the Message object into a string
;
$notification = Notification::create()
->withPayload($message)
;
The resulting notification payload will look as follows:
{
"title":"This is the title",
"options":{
"actions":[
{
"action":"alert",
"title":"Click me!"
}
],
"badge":"badge1",
"body":"Hello World!",
"data":{
"foo":"BAR"
},
"dir":"rtl",
"icon":"https://…",
"image":"https://…",
"lang":"fr-FR",
"renotify":false,
"requireInteraction":false,
"silent":false,
"tag":"foo",
"timestamp":1629145424,
"vibrate":[
300,
100,
400
]
}
}
On client side, you can easily load that payload and display the notification:
self.addEventListener('push', function(event) {
const data = event.data.json();
const {title, ...options} = data;
event.waitUntil(
self.registration.showNotification(title, options)
);
});
Best practices¶
Choose appropriate TTL values¶
The Time-To-Live (TTL) determines how long notifications are retained if the user is offline:
// Time-sensitive: expires quickly
$urgentNotification = Notification::create()
->withPayload('Flash sale ends in 10 minutes!')
->withTTL(Notification::TTL_TEN_MINUTES);
// Important but not urgent
$normalNotification = Notification::create()
->withPayload('New message from John')
->withTTL(Notification::TTL_ONE_DAY);
// Persistent notification
$persistentNotification = Notification::create()
->withPayload('New feature available')
->withTTL(Notification::TTL_ONE_WEEK);
Use topics wisely¶
Topics prevent notification spam by replacing old notifications with new ones:
// Weather updates - only show the latest
$weatherUpdate = Notification::create()
->withTopic('weather-alert')
->withPayload($latestWeatherData);
// Stock price updates - replace with latest price
$stockUpdate = Notification::create()
->withTopic('stock-AAPL')
->withPayload($currentPrice);
// User mentions - don't replace, show all
$mention = Notification::create()
// No topic - each mention is shown separately
->withPayload('Sarah mentioned you in a comment');
Set appropriate urgency¶
Match urgency to content to optimize battery life:
// High urgency - user needs to act immediately
Notification::create()
->highUrgency()
->withPayload('Your bank account requires immediate attention')
->withTTL(Notification::TTL_ONE_HOUR);
// Normal urgency - standard messages
Notification::create()
->normalUrgency()
->withPayload('New comment on your post')
->withTTL(Notification::TTL_ONE_DAY);
// Low urgency - can wait
Notification::create()
->lowUrgency()
->withPayload('Weekly summary available')
->withTTL(Notification::TTL_ONE_WEEK);
// Very low urgency - promotional content
Notification::create()
->veryLowUrgency()
->withPayload('Check out our new products')
->withTTL(Notification::TTL_FOUR_WEEKS);
Craft effective messages¶
Good notification messages are: - Clear: User understands the content immediately - Actionable: User knows what to do next - Concise: Get to the point quickly - Relevant: Personalized to the user
use WebPush\Message;
use WebPush\Action;
// Example: E-commerce order notification
$message = Message::create('Order Shipped!')
->withBody('Your order #12345 is on its way')
->withIcon('/icons/shipping.png')
->withBadge('/icons/badge.png')
->withData([
'orderId' => '12345',
'trackingUrl' => 'https://example.com/track/12345'
])
->addAction(Action::create('track', 'Track Package'))
->addAction(Action::create('view', 'View Order'))
->interactionRequired(); // User must interact
$notification = Notification::create()
->withPayload($message->toString())
->normalUrgency()
->withTTL(Notification::TTL_ONE_DAY);
Optimize for mobile¶
Mobile devices have limited battery and screen space:
// Mobile-optimized notification
$mobileNotification = Message::create('New Message')
->withBody('John: Can we meet tomorrow?')
->withIcon('/icons/chat-64.png') // Small icon
->withBadge('/icons/unread-badge.png')
->withData(['chatId' => '123', 'userId' => '456'])
->addAction(Action::create('reply', 'Reply'))
->addAction(Action::create('view', 'View'))
->withTag('chat-123') // Group related notifications
->renotify() // Alert user even if previous notification exists
->vibrate(200, 100, 200); // Short vibration pattern
Common notification patterns¶
1. Chat message¶
$chatMessage = Message::create($senderName)
->withBody($messagePreview)
->withIcon($senderAvatar)
->withBadge('/icons/message-badge.png')
->withData([
'chatId' => $chatId,
'senderId' => $senderId,
'messageId' => $messageId
])
->addAction(Action::create('reply', 'Reply'))
->addAction(Action::create('view', 'View'))
->withTag("chat-{$chatId}") // Group by conversation
->renotify()
->withTimestamp(time());
$notification = Notification::create()
->withPayload($chatMessage->toString())
->highUrgency()
->withTTL(Notification::TTL_ONE_HOUR);
2. System alert¶
$alert = Message::create('System Maintenance')
->withBody('Scheduled maintenance in 30 minutes')
->withIcon('/icons/warning.png')
->withBadge('/icons/alert-badge.png')
->interactionRequired()
->withTag('system-maintenance')
->vibrate(300, 200, 300);
$notification = Notification::create()
->withPayload($alert->toString())
->highUrgency()
->withTTL(1800); // 30 minutes
3. News update¶
$news = Message::create('Breaking News')
->withBody($headline)
->withIcon('/icons/news.png')
->withImage($articleImage)
->withData(['articleId' => $articleId])
->addAction(Action::create('read', 'Read Article'))
->withTag('news')
->withTimestamp(time());
$notification = Notification::create()
->withPayload($news->toString())
->normalUrgency()
->withTopic('breaking-news')
->withTTL(Notification::TTL_ONE_DAY);
4. Silent background sync¶
// Silent notification for background sync
$syncNotification = Notification::create()
->withPayload(json_encode(['type' => 'sync', 'data' => $syncData]))
->async()
->lowUrgency()
->withTTL(Notification::TTL_IMMEDIATE); // Deliver immediately or not at all
Handling delivery failures¶
Always handle failures gracefully:
use WebPush\Notification;
use WebPush\WebPushService;
/** @var WebPushService $webPush */
/** @var Subscription $subscription */
try {
$notification = Notification::create()
->withPayload($message->toString())
->withTTL(Notification::TTL_ONE_DAY);
$report = $webPush->send($notification, $subscription);
if (!$report->isSuccess()) {
// Log the failure
$logger->error('Notification delivery failed', [
'subscription' => $subscription->getEndpoint(),
'error' => $report->getLocation()
]);
// Handle expired subscriptions
if ($report->isSubscriptionExpired()) {
$subscriptionRepository->remove($subscription);
}
}
} catch (\Throwable $e) {
$logger->error('Exception sending notification', [
'message' => $e->getMessage(),
'subscription' => $subscription->getEndpoint()
]);
}
Testing notifications¶
Always test your notifications before sending to production:
// Create a test notification
$testNotification = Message::create('Test Notification')
->withBody('This is a test')
->withIcon('/test-icon.png')
->toString();
// Send to your own subscription
$report = $webPush->send(
Notification::create()->withPayload($testNotification),
$yourTestSubscription
);
// Verify delivery
assert($report->isSuccess(), 'Test notification failed to send');
Performance considerations¶
Batch sending¶
When sending to many subscriptions, batch efficiently:
$subscriptions = $repository->getAllActive();
$notification = Notification::create()->withPayload($message);
foreach ($subscriptions as $subscription) {
// Send asynchronously when possible
$report = $webPush->send($notification, $subscription);
// Cleanup expired subscriptions immediately
if ($report->isSubscriptionExpired()) {
$repository->remove($subscription);
}
}
Respect rate limits¶
Push services have rate limits. Implement throttling:
$rateLimit = 100; // notifications per second
$delay = 1000000 / $rateLimit; // microseconds
foreach ($subscriptions as $subscription) {
$webPush->send($notification, $subscription);
usleep($delay);
}
Validation exceptions¶
The library throws specific exceptions when notification properties are invalid. Each exception provides direct access to the problematic value for better debugging.
Available exceptions¶
use WebPush\Exception\InvalidTopicException;
use WebPush\Exception\InvalidTTLException;
use WebPush\Exception\InvalidUrgencyException;
InvalidTopicException¶
Thrown when topic validation fails. Access the invalid topic via $e->topic:
try {
$notification = Notification::create()
->withTopic('invalid@topic'); // @ not allowed
} catch (InvalidTopicException $e) {
echo "Invalid topic: {$e->topic}";
// Output: Invalid topic: invalid@topic
}
Common causes:
- Topic exceeds 32 characters
- Topic contains invalid characters (only a-z, A-Z, 0-9, -, ., _, ~ allowed)
- Empty topic
InvalidTTLException¶
Thrown when TTL is negative. Access the invalid TTL via $e->ttl:
try {
$notification = Notification::create()
->withTTL(-1); // Must be >= 0
} catch (InvalidTTLException $e) {
echo "Invalid TTL: {$e->ttl}";
// Output: Invalid TTL: -1
}
InvalidUrgencyException¶
Thrown when urgency is not a valid value. Access the invalid urgency via $e->urgency:
try {
$notification = Notification::create()
->withUrgency('critical'); // Not a valid urgency
} catch (InvalidUrgencyException $e) {
echo "Invalid urgency: {$e->urgency}";
// Output: Invalid urgency: critical
}
Valid urgency values: very-low, low, normal, high
Handling multiple validations¶
Catch specific exceptions for targeted error handling:
use WebPush\Exception\InvalidTopicException;
use WebPush\Exception\InvalidTTLException;
use WebPush\Exception\ValidationException;
try {
$notification = Notification::create()
->withTopic($userTopic)
->withTTL($userTTL)
->withPayload($message);
} catch (InvalidTopicException $e) {
return "Topic '{$e->topic}' is invalid: use only letters, numbers, and - . _ ~";
} catch (InvalidTTLException $e) {
return "TTL must be positive (got {$e->ttl})";
} catch (ValidationException $e) {
return "Validation error: " . $e->getMessage();
}
Info
See the Exceptions page for complete documentation on error handling strategies and best practices.
Next steps¶
- Understand Status Reports to handle delivery results
- Learn about Subscriptions management
- Set up VAPID for secure authentication
- Read about Exception Handling for validation errors