Skip to main content
Use $vzaps->events() to receive instance events in realtime without exposing a public URL. For HTTP callbacks, see Webhooks.
In PHP, realtime should run in CLI processes, workers, daemons, Laravel Commands, Symfony Console commands, or queue consumers. Do not keep a WebSocket open inside a normal HTTP request lifecycle.

Common events

EventDescription
MessageNew incoming message or message event.
ReadReceiptRead/delivery update.
PresenceUser presence.
ChatPresenceChat presence.
HistorySyncHistory sync.
ConnectedInstance connected to WhatsApp.
DisconnectedInstance disconnected.
GroupParticipantsAddParticipants added to a group.
GroupParticipantsRemoveParticipants removed from a group.
AllEvery subscribed event.

Install WebSocket transport

composer require textalk/websocket
You can also pass webSocketFactory to VZapsClient for tests or a custom transport.

Subscribe to realtime

use VZaps\Sdk\Models\Realtime\EventSubscribeRequest;
use VZaps\Sdk\Models\Realtime\VZapsEventType;

$subscription = $vzaps->events()->subscribe(new EventSubscribeRequest(
    instanceId: 'VZ...',
    instanceToken: 'instance-token',
    events: [
        VZapsEventType::Message,
        VZapsEventType::ReadReceipt,
        VZapsEventType::Connected,
        VZapsEventType::Disconnected,
    ],
    reconnect: true,
    maxRetries: 10,
    retryDelayMs: 1000,
));
Return: EventSubscription — object with on(), close(), and automatic reconnect when configured. Options:
FieldTypeRequiredDescription
instanceIdstringYesInstance to monitor.
instanceTokenstringYesInstance token.
eventsVZapsEventType[] or string[]NoEvent list. If omitted, uses events subscribed on the instance.
reconnectbooleanNoReconnect automatically. Default: true.
maxRetriesintNoMaximum retry attempts.
retryDelayMsintNoDelay between attempts.
lastEventIdstringNoResume cursor.

Register handlers

$subscription->on(VZapsEventType::Message, function ($event): void {
    echo $event->id . PHP_EOL;
    echo $event->instanceId . PHP_EOL;
    print_r($event->data);
});

$subscription->on(VZapsEventType::All, function ($event): void {
    echo 'Received event: ' . $event->type . PHP_EOL;
});

$subscription->onError(function ($error): void {
    error_log('Realtime error: ' . $error->getMessage());
});

Close subscription

$subscription->close();
Return: Promise<void> after the WebSocket closes. In CLI processes:
if (function_exists('pcntl_signal')) {
    pcntl_signal(SIGINT, function () use ($subscription): void {
        $subscription->close();
        exit(0);
    });
}

$subscription->awaitClose();

Event envelope

Each event received by the SDK has this shape:
{
  "id": "evt_01J...",
  "type": "Message",
  "instance_id": "VZ...",
  "created_at": "2026-06-23T22:57:17.000Z",
  "data": {
    "type": "Message",
    "media_url": "https://cdn.example.com/media/image.jpg"
  }
}
Fields:
FieldDescription
idEvent identifier. Use for deduplication.
typeEvent type.
instance_idSource instance in the original payload.
created_atEvent creation time in the original payload.
dataEvent payload.
data.media_urlMedia URL when the incoming event contains media and the platform provides the file.
On the PHP VZapsEvent object, the main shortcuts are $event->id, $event->type, $event->instanceId, $event->createdAt, $event->data, and $event->raw.

Delivery and ack

Delivery is at-least-once. Your app should process events idempotently. After the handler finishes, the SDK sends ack automatically. Recommendations:
  • save event.id if your automation performs external side effects;
  • ignore events that were already processed;
  • use lastEventId when reconnecting if you want to reduce gaps;
  • keep handlers fast and move long-running work to your own queue.

Realtime or webhook?

ScenarioRecommendation
Bot, dashboard, or app with immediate consumptionRealtime
Backend with public URL and HTTP pipelineWebhook
You do not want to expose a public URLRealtime
You want to reprocess deliveries via logsWebhook