inverge / websocket
PHP & Laravel backend SDK for the Inverge WebSocket service — rooms, relations, payload schemas, and server-to-room emit over the API-key partner API.
Requires
- php: ^8.2
- ext-json: *
- guzzlehttp/guzzle: ^7.5
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0
This package is auto-updated.
Last update: 2026-08-24 18:02:53 UTC
README
A backend SDK for the Inverge WebSocket service. It talks to the API-key partner HTTP surface — your server emits events into rooms and manages rooms / relations / payload schemas. It does not open a socket itself (that's the browser/device side), and it does not expose usage or billing — those live in the dashboard at https://wss.inverge.net.
- Framework-agnostic core (plain PHP, Guzzle transport).
- First-class Laravel integration: auto-discovered provider,
WebSocketfacade, publishable config, and a notification channel. - Typed exceptions, an inert "disabled" mode for local/CI, and helpers that make correct, cheap room modeling the easy path.
Requires PHP 8.2+.
Install
composer require inverge/websocket
Set your credentials:
WEBSOCKET_BASE_URL=https://websocket.inverge.net WEBSOCKET_API_KEY=your_partner_api_key WEBSOCKET_ENABLED=true
Laravel
Nothing to wire up — the provider and WebSocket facade are auto-discovered.
Publish the config if you want to tweak it:
php artisan vendor:publish --tag=websocket-config
Plain PHP
use Inverge\WebSocket\Client; $ws = Client::make('https://websocket.inverge.net', getenv('WEBSOCKET_API_KEY'));
Quick start
use Inverge\WebSocket\Support\Room; // Emit an event into a room (and any rooms related to it) $ws->emit(Room::scoped('order', 8821), 'order.created', ['orderId' => 8821]); // Laravel facade use Inverge\WebSocket\Laravel\Facades\WebSocket; WebSocket::emit('order:8821', 'order.created', ['orderId' => 8821]);
Core concept: scope rooms by recipient, not by topic
A room is a delivery list. Put a socket in a room only if it should receive every message sent there. Event names (
order.created) are labels on the payload — a room broadcast reaches every member regardless of event name.
Putting everyone in one big orders room and separating by event name delivers
everything to everyone — a data leak and a billing blow-up (you're billed per
recipient delivery). Instead, scope rooms to the parties who need the message:
use Inverge\WebSocket\Support\Room; Room::scoped('order', 8821); // "order:8821" -> the client + assigned pilot + ops Room::of('pilot', 17); // "pilot:17" -> one driver Room::of('client', 42); // "client:42" -> one customer
Deliveries per emit ≈ members of the target room(s). Keep rooms as small as the set of parties that genuinely need the message.
Emitting
// one event $ws->emit('order:8821', 'order.created', $payload); // multiple events, same payload, same room $ws->emit('order:8821', ['order.created', 'audit.log'], $payload); // several rooms in ONE request (e.g. the order parties AND the ops dashboard) $ws->broadcast([ ['room' => 'order:8821', 'event' => 'order.created', 'payload' => $payload], ['room' => 'ops', 'event' => 'order.created', 'payload' => $payload], ]); // route from a room to its RELATED rooms (relational fan-out), or to one target $ws->route(from: 'order:8821', to: null, event: 'order.created', payload: $payload); $ws->route(from: 'order:8821', to: 'ops', event: 'order.created', payload: $payload);
Every emit returns the service ack, e.g.
['ok' => true, 'room' => 'c:1:order:8821', 'related' => ['ops'], 'events' => ['order.created']].
Rooms, relations & payload schemas
use Inverge\WebSocket\Enums\RoomType; $rooms = $ws->rooms(); $rooms->all(); // list rooms $order = $rooms->create('order:8821', RoomType::Relational); $rooms->ensure('ops'); // idempotent get-or-create $rooms->delete($order['id']); // relations: an emit to $orderId also fans out to $opsId $rooms->link($orderId, $opsId); $rooms->unlink($orderId, $opsId); $rooms->related('order:8821'); // -> ['ops'] // payload schema (JSON Schema draft-07 / 2020-12) — reject malformed emits $rooms->setSchema($orderId, [ 'type' => 'object', 'required' => ['orderId'], 'properties' => ['orderId' => ['type' => 'integer']], ]); $rooms->disableSchema($orderId); // keep it attached but off $rooms->enableSchema($orderId); $rooms->getSchema($orderId); $rooms->deleteSchema($orderId);
When a schema is enforced, an emit() with a non-conforming payload throws
ValidationException (see below).
Usage & billing
Usage, analytics, and billing statements are not part of this SDK — view them in the dashboard at https://wss.inverge.net.
Laravel notifications
Send realtime events straight from a Notification:
use Illuminate\Notifications\Notification; use Inverge\WebSocket\Laravel\Notifications\WebSocketChannel; use Inverge\WebSocket\Laravel\Notifications\WebSocketMessage; use Inverge\WebSocket\Support\Room; class OrderCreated extends Notification { public function __construct(private readonly Order $order) {} public function via($notifiable): array { return [WebSocketChannel::class]; } public function toWebSocket($notifiable): WebSocketMessage { return WebSocketMessage::make() ->to(Room::scoped('order', $this->order->id)) ->event('order.created') ->payload(['orderId' => $this->order->id, 'status' => 'pending']); } }
toWebSocket() may instead return a plain array of broadcast messages
(['room'=>, 'event'=>, 'payload'=>], ...) to hit several rooms at once.
Resolve the client anywhere via DI or the facade:
public function __construct(private readonly \Inverge\WebSocket\Client $ws) {} // or \Inverge\WebSocket\Laravel\Facades\WebSocket::emit(...);
Error handling
All failures extend Inverge\WebSocket\Exceptions\WebSocketException, which
carries ->status, ->errorCode, ->details, and ->errors():
| Exception | When |
|---|---|
AuthenticationException |
401/403 — bad or missing API key |
NotFoundException |
404 — unknown resource, or a disabled feature |
RoomExistsException |
409 — duplicate room name |
ValidationException |
payload failed the room's schema (->errors()) |
RequestException / ServerException |
other 4xx / 5xx |
ConnectionException |
transport failure (timeout, DNS, refused) |
use Inverge\WebSocket\Exceptions\ValidationException; try { $ws->emit('order:8821', 'order.created', $payload); } catch (ValidationException $e) { report($e); // $e->errors() has the schema violations }
Disabled / no-op mode
When enabled is false or no API key is configured, the SDK makes no network
calls: emit-like methods return ['skipped' => true] and reads return empty. This
lets your app run untouched in local/dev/CI. Toggle with WEBSOCKET_ENABLED=false.
Custom transport
The default transport is Guzzle. Swap it (shared client, tracing, tests) by
implementing Inverge\WebSocket\Contracts\Transport:
$ws = new Client($config, new MyTransport()); // in Laravel: bind your own in a service provider, then rebuild the singleton.
Testing
composer install
composer test
Tests use an in-memory FakeTransport — no network required. Use the same double
in your app to assert emits without hitting the service.
License
MIT.