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 Php Server Laravel Package

pusher/pusher-php-server

Official PHP library for the Pusher Channels HTTP API. Install via Composer, create a Pusher instance with your app credentials, and publish events or authenticate channels from PHP 7.3–8.4, including Laravel broadcasting.

View on GitHub
Deep Wiki
Context7

Getting Started

Installation. In almost every Laravel app you never install this explicitly — laravel/framework already depends on pusher/pusher-php-server (^7.x) because Illuminate\Broadcasting\Broadcasters\PusherBroadcaster wraps it. Add it directly only when you need to pin the version or use the low-level client (Pusher\Pusher) for things broadcasting does not expose — channel queries, user authentication, webhooks, batch triggers:

composer require pusher/pusher-php-server:^7.3

Configuration. Look at config/broadcasting.php first (the pusher connection block) and populate .env:

BROADCAST_CONNECTION=pusher          # Laravel 11+; BROADCAST_DRIVER on older versions
PUSHER_APP_ID=123456
PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_CLUSTER=eu
PUSHER_SCHEME=https
PUSHER_USE_TLS=true
# Only for self-hosted / Pusher-compatible servers (soketi, laravel-websockets):
# PUSHER_HOST=127.0.0.1
# PUSHER_PORT=6001

The array passed as the fourth Pusher constructor argument maps 1:1 to these keys (cluster, host, port, scheme, useTLS, timeout, encryption_master_key_base64), which is why the config file looks the way it does.

First use case. Create a broadcast event — this is the 90% path:

class OrderShipped implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('orders.'.$this->order->user_id)];
    }

    public function broadcastAs(): string { return 'OrderShipped'; }
    public function broadcastWith(): array { return ['id' => $this->order->id, 'status' => 'shipped']; }
}

Authorize it in routes/channels.php (Broadcast::channel('orders.{userId}', fn ($user, $userId) => (int) $user->id === (int) $userId);), then listen in Echo. To verify credentials before touching the front end, run a one-liner in tinker:

app(Pusher\Pusher::class)->trigger('test-channel', 'hello', ['ok' => true]);

If that returns without an exception, the API side is wired correctly and any remaining problem is on the client/authorization side.

To get the underlying client from Laravel's broadcaster (useful when you don't want a second instance with duplicated config):

$pusher = app(Illuminate\Broadcasting\BroadcastManager::class)->connection('pusher')->getPusher();

Implementation Patterns

Broadcast through events, reach for the raw client only when needed. ShouldBroadcast puts the broadcast on the queue (safe, retryable); ShouldBroadcastNow sends inline from the request/worker. Use the latter only for low-latency, non-critical payloads — a slow Pusher API call inside a web request is a request-latency tax.

Fan-out with a single API call. trigger() accepts an array of channels and performs one HTTP request. triggerBatch() sends up to 10 distinct events in one call — ideal for notifying 10 channels of the same payload without 10 round trips.

Exclude the acting user. Pass ['socket_id' => $request->header('X-Socket-Id')] (or your own socket-id plumbing) so the originator doesn't receive an echo of their own action — the classic "my own message appears twice in chat" fix.

Async triggering in queue workers and Octane. triggerAsync() / triggerBatchAsync() return Guzzle promises; useful when a job must publish to several channel groups and you want them in flight concurrently. Always ->wait() before the job returns so failures surface.

Custom Guzzle client for observability and retries. Bind a configured GuzzleHttp\Client (with middleware for tracing, RetryMiddleware, or a MockHandler in tests) as a singleton and pass it as the fifth constructor argument. This is the cleanest seam for testing code that calls $pusher->trigger() directly.

Webhooks. Laravel's broadcasting routes don't cover Pusher webhooks — write a small controller in the api middleware group (CSRF-exempt), verify with $pusher->webhook($headers, $rawBody) in a try/catch for Pusher\PusherException, then queue the actual processing and return 200 immediately. Handle channel_occupied, channel_vacated, member_added, member_removed and client_event.

Server-side presence queries. For "who's online" dashboards or support widgets, getChannels(['filter_by_prefix' => 'presence-']) and getChannelInfo($name, ['info' => 'user_count,subscription_count']) are cheaper and simpler than maintaining your own presence table — but note subscription_count must be enabled in the Pusher dashboard first.

End-to-end encryption. Set encryption_master_key_base64 in the config and use private-encrypted- channel names. Only apply it to channels that genuinely need it; the ciphertext is unreadable in the Pusher debug console, which complicates debugging.

Testing. For broadcast events, Event::fake() / Broadcast::fake() is enough — you're asserting the intent, not the HTTP call. For direct $pusher->trigger() code, bind a Pusher\Pusher built on a Guzzle client with a MockHandler, or wrap your publishing in a thin RealtimePublisher service and fake that instead. Don't hit the real API in CI.

Gotchas and Tips

  • PUSHER_APP_ID / PUSHER_APP_SECRET empty or mismatched is the single most common failure: you'll get 401 / ApiErrorException: Invalid signature. If PUSHER_HOST is set but PUSHER_PORT/PUSHER_SCHEME are stale in .env, you'll get connection timeouts or 404s instead.
  • Self-hosted servers (soketi, laravel-websockets) need host, port, and almost always useTLS=false plus scheme=http. A leftover PUSHER_USE_TLS=true produces cryptic cURL/SSL errors rather than a clear message.
  • Laravel 11 renamed the env var to BROADCAST_CONNECTION. BROADCAST_DRIVER=pusher silently does nothing on 11+, leaving broadcasting on null and no events sent.
  • Don't double-encode payloads. With broadcasting, return a plain array from broadcastWith(). With the raw client, only pass true as the sixth trigger() argument when your $data is already a JSON string — otherwise clients receive a JSON-encoded string instead of an object.
  • Broadcasting inside a DB transaction can publish before the row is committed, so clients fetch a record that doesn't exist yet. Use public bool $afterCommit = true; on the event (or DB::afterCommit(...)) — this is the most common "works locally, races in production" bug with Pusher.
  • Encrypted and unencrypted channels can't be mixed in a single trigger() call, because encryption is per-request. This throws; split into two calls.
  • Webhook header casing. $request->headers->all() returns lowercased keys (x-pusher-signature), while the library looks up X-Pusher-Signature. Build the array explicitly — ['X-Pusher-Key' => $request->header('X-Pusher-Key'), 'X-Pusher-Signature' => $request->header('X-Pusher-Signature')] — and always pass $request->getContent() (raw body), never validated/parsed data, or signature verification fails.
  • Errors are exceptions, not return codes. Transport failures throw Guzzle exceptions; API rejections throw Pusher\ApiErrorException (or Pusher\PusherException for client-side argument problems). PusherBroadcaster re-wraps these as BroadcastException. Catch at the job level and let the queue retry — but make handlers idempotent, since a retried broadcast can duplicate a message.
  • socket_id exclusion only works if the client actually sends its socket id to your backend; it's not inferred. With Echo, read it from Echo.socketId() and ship it as a header.
  • Set an explicit timeout (seconds) in the options array. The default can block a queue worker far longer than you'd like during a Pusher outage.
  • Quota and rate limits are plan-dependent (connections, daily messages, batch size of 10). Channel-info queries count toward rate limits, so cache getChannelInfo() results for a few seconds if you render them in a live dashboard.
  • Extension point: Pusher implements LoggerAwareInterface — call setLogger(app('log')), or set 'log' => true on the broadcasting connection so Laravel attaches its logger automatically. This is the fastest way to see the exact request URL and response body when auth fails.
  • Keep event names stable. Use broadcastAs() and listen for that name in JS; renaming the PHP class otherwise silently breaks client listeners, and there's no server-side error to warn you.
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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table