Skip to main content

Overview

laravel-bluesky provides two WebSocket commands for connecting to Bluesky’s real-time streams.
  • Jetstream — Bluesky’s own filtered WebSocket endpoint. Delivers JSON messages and supports collection and DID filtering.
  • Firehose — The raw AT Protocol event stream. Delivers every event on the network in DAG-CBOR binary format.
WebSocket commands are long-running processes that require a VPS, EC2, or any always-on server, or a Laravel Cloud custom worker. They do not work on serverless platforms such as Laravel Vapor or Vercel.

Installation

WebSocket support requires Workerman.
composer require workerman/workerman

Jetstream

Overview

Jetstream is Bluesky’s managed WebSocket service. You can filter by collection type or user DID, so your process only receives the events you care about.
FeatureDetails
FormatJSON
FilteringFilter by collection and DID
VolumeLow to moderate (depending on filters)
Use caseMonitor posts, likes, follows, etc.

Start the command

# Receive all messages (no filter)
php artisan bluesky:ws start

# Debug: print every received message
php artisan bluesky:ws start -v

Collection filter

Use -C to restrict which collections you receive. Pass the flag multiple times for multiple collections.
# Receive only posts and likes
php artisan bluesky:ws start -C app.bsky.feed.post -C app.bsky.feed.like

# Receive only follows
php artisan bluesky:ws start -C app.bsky.graph.follow
Common collections:
CollectionContent
app.bsky.feed.postPost create / delete
app.bsky.feed.likeLikes
app.bsky.feed.repostReposts
app.bsky.graph.followFollows
app.bsky.graph.blockBlocks

DID filter

Use -D to receive events from specific users only.
# Receive posts from specific users only
php artisan bluesky:ws start -C app.bsky.feed.post -D did:plc:xxx -D did:plc:yyy

Event handling

The Jetstream command fires Laravel events based on the message kind.
Event classWhen fired
JetstreamMessageReceivedEvery received message
JetstreamCommitMessageRecord create, update, or delete
JetstreamIdentityMessageIdentity events (handle changes, etc.)
JetstreamAccountMessageAccount activation or deactivation
Create a listener to handle events.
php artisan make:listener JetstreamPostListener
namespace App\Listeners;

use Revolution\Bluesky\Events\Jetstream\JetstreamCommitMessage;

class JetstreamPostListener
{
    public function handle(JetstreamCommitMessage $event): void
    {
        // Check the collection type
        $collection = data_get($event->message, 'commit.collection');

        if ($collection !== 'app.bsky.feed.post') {
            return;
        }

        // Operation: create / update / delete
        $operation = $event->operation;

        // Author's DID
        $did = $event->message['did'];

        // Record content
        $record = data_get($event->message, 'commit.record');
        $text = data_get($record, 'text', '');

        info("[$operation] $did: $text");
    }
}

Firehose

Overview

Firehose is the raw AT Protocol event stream. It delivers every record operation on the Bluesky network as a binary DAG-CBOR payload. The package decodes the binary data automatically, so your listeners receive standard PHP arrays.
FeatureDetails
FormatDAG-CBOR binary (decoded automatically)
FilteringNone — receives all events
VolumeVery high
Use caseFull data collection, archiving
DAG-CBOR decoding is handled by the package. Your event listeners receive decoded PHP arrays.

Start the command

php artisan bluesky:firehose start

# Debug: print received messages
php artisan bluesky:firehose start -v

Event handling

The Firehose command fires the following Laravel events.
Event classWhen fired
FirehoseMessageReceivedEvery received message (includes raw binary)
FirehoseCommitMessageRecord create, update, or delete
FirehoseIdentityMessageIdentity events
FirehoseAccountMessageAccount events
FirehoseSyncMessageRepository sync events
php artisan make:listener FirehosePostListener
namespace App\Listeners;

use Revolution\Bluesky\Events\Firehose\FirehoseCommitMessage;

class FirehosePostListener
{
    public function handle(FirehoseCommitMessage $event): void
    {
        // Check the collection type
        if ($event->collection !== 'app.bsky.feed.post') {
            return;
        }

        // Operation: create / update / delete
        $action = $event->action;

        // Author's DID
        $did = $event->did;

        // Record content (decoded array)
        $record = $event->record;
        $text = data_get($record, 'value.text', '');

        info("[$action] $did: $text");
    }
}

Configuration

Adjust the host and logging settings in config/bluesky.php.
// Jetstream
'jetstream' => [
    'host' => env('BLUESKY_JETSTREAM_HOST', 'jetstream1.us-west.bsky.network'),
    'max' => env('BLUESKY_JETSTREAM_MAX', 0), // maxMessageSizeBytes (0 = unlimited)
    'logging' => [
        'driver' => env('BLUESKY_JETSTREAM_LOG_DRIVER', 'daily'),
        'days' => 7,
        'path' => env('BLUESKY_JETSTREAM_LOG_PATH', storage_path('logs/jetstream.log')),
    ],
],

// Firehose
'firehose' => [
    'host' => env('BLUESKY_FIREHOSE_HOST', 'bsky.network'),
    'logging' => [
        'driver' => env('BLUESKY_FIREHOSE_LOG_DRIVER', 'daily'),
        'days' => 7,
        'path' => env('BLUESKY_FIREHOSE_LOG_PATH', storage_path('logs/firehose.log')),
    ],
],
Example .env overrides:
BLUESKY_JETSTREAM_HOST=jetstream2.us-east.bsky.network
BLUESKY_JETSTREAM_MAX=1000000

Combining with Labeler

You can run the Labeler server alongside Jetstream or Firehose to feed incoming events directly into your labeling logic.
# Labeler + Jetstream (monitor follow events)
php artisan bluesky:labeler:server start --jetstream -C app.bsky.graph.follow

# Labeler + Firehose (receive full stream)
php artisan bluesky:labeler:server start --firehose

Running as a long-lived process

WebSocket commands must run continuously. Use a process manager such as Supervisor to keep them alive.

Supervisor configuration

/etc/supervisor/conf.d/bluesky-jetstream.conf:
[program:bluesky-jetstream]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan bluesky:ws start -C app.bsky.feed.post
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/jetstream-worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start bluesky-jetstream:*

Laravel Forge daemon

In Laravel Forge, add a daemon from the Daemons section.
  • Command: php artisan bluesky:ws start -C app.bsky.feed.post
  • Directory: /var/www/html
  • User: forge

Laravel Cloud background process

Because these commands act as WebSocket clients that connect to Bluesky’s streams (rather than WebSocket servers), they run on Laravel Cloud. Configure them as custom workers in the Laravel Cloud background processes settings. Add a Custom Worker for each command you want to run. For Jetstream:
php artisan bluesky:ws start
For Firehose:
php artisan bluesky:firehose start
Laravel Cloud automatically handles process lifecycle during deployments. No additional configuration beyond adding the background process is required.

Tips

  • With autorestart=true, Supervisor restarts the process automatically if it crashes.
  • Consider periodic restarts to prevent memory growth over time.
  • For the high-volume Firehose stream, dispatch a Queue Job from your listener instead of processing inline.
// Dispatch a queue job from the listener

namespace App\Listeners;

use App\Jobs\ProcessFirehosePost;
use Revolution\Bluesky\Events\Firehose\FirehoseCommitMessage;

class FirehosePostListener
{
    public function handle(FirehoseCommitMessage $event): void
    {
        if ($event->collection !== 'app.bsky.feed.post') {
            return;
        }

        // Offload heavy processing to a queue
        ProcessFirehosePost::dispatch($event->did, $event->record, $event->action);
    }
}
Last modified on April 25, 2026