Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pusher Push Notifications Laravel Package

pusher/pusher-push-notifications

Official PHP server SDK for Pusher Beams. Send push notifications to iOS, Android, and web clients through a simple, fluent API with support for device interests, authentication, and batch publishing. Works standalone or alongside Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the Pusher Beams server SDK (pusher/pusher-push-notifications, namespace Pusher\PushNotifications\PushNotifications). It is not a Laravel package: no service provider, no auto-discovery, no facade, no config file. You wire it up yourself in ~10 lines.

1. Install

composer require pusher/pusher-push-notifications

2. Credentials (Beams dashboard → your instance → Keys). Never commit the secret key.

BEAMS_INSTANCE_ID=your-instance-id
BEAMS_SECRET_KEY=your-secret-key
// config/services.php
'beams' => [
    'instance_id' => env('BEAMS_INSTANCE_ID'),
    'secret_key'  => env('BEAMS_SECRET_KEY'),
],

3. Register a singleton — the client is stateless and safe to reuse:

// app/Providers/AppServiceProvider.php
use Pusher\PushNotifications\PushNotifications;

$this->app->singleton(PushNotifications::class, fn () => new PushNotifications([
    'instanceId' => config('services.beams.instance_id'),
    'secretKey'  => config('services.beams.secret_key'),
]));

4. Authenticate users — mobile/web clients call your endpoint to exchange a logged-in session for a Beams token:

Route::get('/beams-auth', function (Request $request, PushNotifications $beams) {
    return response()->json([
        'token' => $beams->generateToken((string) $request->user()->id),
    ]);
})->middleware('auth');

5. First use case — notify one user about something that just happened:

$beams = app(PushNotifications::class);

$beams->publishToUsers([(string) $user->id], [
    'apns' => ['aps' => ['alert' => ['title' => 'Order shipped', 'body' => '...'], 'sound' => 'default']],
    'fcm'  => ['notification' => ['title' => 'Order shipped', 'body' => '...']],
    'web'  => ['notification' => ['title' => 'Order shipped', 'body' => '...']],
]);

Where to look first: the repo README (it's basically the whole API surface — ~6 methods), then the Beams docs' "Publish" page for payload shapes, and the dashboard's Debug tab, which lets you fire a test push at a specific device before you write a line of server code. Configure APNs key / FCM service account / VAPID keys in the dashboard before debugging client-side.

Implementation Patterns

Wrap it in a Notification channel. This is the highest-leverage integration: you get Laravel's Notification::send(), via(), queueing, and on-demand targeting for free.

// app/Channels/BeamsChannel.php
class BeamsChannel
{
    public function __construct(private PushNotifications $beams) {}

    public function send($notifiable, Notification $notification): void
    {
        if (! $id = $notifiable->routeNotificationFor('beams', $notification)) {
            return;
        }

        $message = $notification->toBeams($notifiable);

        $this->beams->publishToUsers([(string) $id], $message->toArray());
    }
}
class OrderShipped extends Notification implements ShouldQueue
{
    public function via($notifiable) { return [BeamsChannel::class]; }

    public function toBeams($notifiable): object
    {
        return new BeamsMessage(
            title: 'Order shipped',
            body: "Order #{$this->order->id} is on its way",
            data: ['order_id' => $this->order->id],
        );
    }
}

Model concern for the routing key. Keep the Beams user ID decoupled from the primary key in case you ever need to rotate it (and never use emails — they change and are PII):

trait HasBeamsId
{
    public function routeNotificationForBeams(): ?string
    {
        return $this->beams_user_id; // or (string) $this->id
    }
}

Always send from a queued job, never inline. Publish is a blocking HTTP call to Pusher; putting it in a request cycle adds latency and creates a failure mode you can't retry.

final class SendBeamsPush implements ShouldQueue
{
    use Queueable, InteractsWithQueue;
    public int $tries = 3;
    public array $backoff = [5, 30, 120];
    public function __construct(public array $userIds, public array $payload) {}
    public function handle(PushNotifications $beams): void
    {
        foreach (array_chunk($this->userIds, 500) as $chunk) {
            $beams->publishToUsers($chunk, $this->payload);
        }
    }
}

Interests for fan-out, users for 1:1. Interests are Beams' topic primitive — the client subscribes (beamsClient.subscribe('premier-league')), the server publishes to the interest name. Use them for anything audience-shaped (a live match, a tenant-wide announcement) so you never enumerate users server-side:

$beams->publishToInterests(["tenant-{$tenant->id}"], $payload);

Namespace your interests. Since interest names are a flat global string space inside the instance, prefix them (tenant-42, team-7-news) or you will collide across features.

Handle logout deliberately. deleteUser($id) removes the user and the user↔device associations. On a shared/kiosk device that's correct; on a multi-device account it silently kills push for the user's other devices until each re-authenticates. For normal mobile logout, do nothing on the server — the client SDK's clearAllState() / stop() is enough. Reserve deleteUser for account deletion and "stop sending me anything" preferences.

Long-lived sessions need token refresh. Tokens expire (24h by default). Either let the client SDK re-fetch /beams-auth on a 401, or pass an explicit expiry with generateTokenWithExpiry((string) $user->id, 3600) and refresh on app foreground.

Make it testable by owning the boundary. Because the SDK does its own cURL calls, Http::fake() will not intercept anything. Bind against your own tiny interface and fake that:

interface PushSender {
    public function toUsers(array $ids, array $payload): string; // returns publishId
}

// tests
$this->mock(PushSender::class, fn ($m) => $m->shouldReceive('toUsers')->once());

Data-only (silent) pushes for background sync: omit fcm.notification and send only fcm.data; for APNs set 'content-available' => 1 and no alert. They're best-effort and won't render — don't use them for anything user-visible.

Gotchas and Tips

  • Everything is a "success". publishToUsers(['nobody-registered'], ...) returns a publishId happily. Pushing to a user who never called setUserId, or to an interest nobody subscribed to, is a silent no-op — not an exception. Never treat a 2xx as proof of delivery; the only artifact you get back is publishId, which is useful solely for grepping against the Beams dashboard.
  • Registration is not instant. A device that just started the SDK (or just subscribed to an interest) may not be targetable for a few seconds. Don't fire a "welcome" push synchronously in the same request that registered the device — queue it with a short delay.
  • User IDs must be strings. publishToUsers([1], ...) vs [(string) 1] vs "u-1" are three different users. Cast explicitly, everywhere, and never change the format after launch — existing device registrations are keyed on it.
  • Interest name charset is narrow: a-zA-Z0-9_=-@,.;: and capped in length (~164 chars), max 10 interests per device. A stray space or # in "premier league" gives you a 400, and a silent failure to subscribe. Slugify before subscribing.
  • Payload size cap is ~4KB per platform block. Notification bodies plus data overflow fast if you stuff serialized models in there. Send IDs, not objects.
  • Don't put secrets in the payload. It's rendered on lock screens and fully readable by the client.
  • Android 8+ requires a notification channel on the client. Set fcm.notification.android_channel_id (or channel_id) to a channel your app actually created; otherwise the push is dropped with no server-side error.
  • No scheduling, no idempotency, no delivery receipts, no webhooks. Schedule with ->delay() on the job. Guard against double-sends from retries (retry after a timeout re-publishes) with ShouldBeUnique or a "sent" flag — Beams has no dedupe key.
  • 401/403 = key mismatch, almost always. The instanceId/secretKey pair must be from the same instance; the most common cause is a .env mix-up between staging and production instances, or a key regenerated in the dashboard without redeploying.
  • Check the composer constraints before adding it to a legacy app — it's a thin cURL-based client, so no Guzzle version conflicts, but that also means no Http::fake(), no Guzzle middleware, and no retry magic. You build retry/backoff yourself (see $tries/$backoff above).
  • Octane-safe, but only just. The singleton holds no request state, which is fine — but don't capture auth()->user() inside a closure bound at container build time.
  • Config isn't cached for you. Because there's no package config, config:cache will happily freeze a null instance ID if the env var is missing at build time. Add a boot-time guard that throws on empty credentials in non-local environments — you want that to fail at deploy, not at 3am on the first push.
  • Extension point: the constructor options. It accepts more than the two keys (e.g. a custom host), which is the escape hatch for proxies or local stubs — worth checking the constructor signature before you reach for a fork.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment