Skip to main content

Overview

WebSockets provide real-time job status updates through a persistent connection. Unlike polling, updates are pushed instantly as they happen, including live preview images during generation.
WebSockets are ideal for interactive applications where you need instant feedback on job progress.

Quick Start

Install the required packages:
npm install pusher-js laravel-echo
Connect and listen for updates:
import Pusher from 'pusher-js';

const CLIENT_ID = 'your-client-id';      // From your dashboard
const API_TOKEN = 'your-api-token';      // From authentication

const pusher = new Pusher('depin-api-prod-key', {
    wsHost: 'soketi.deapi.ai',
    wsPort: 443,
    forceTLS: true,
    cluster: 'mt1',
    enabledTransports: ['ws', 'wss'],
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            const res = await fetch('https://api.deapi.ai/broadcasting/auth', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${API_TOKEN}`
                },
                body: JSON.stringify({ socket_id: socketId, channel_name: channel.name })
            });
            callback(null, await res.json());
        }
    })
});

pusher.subscribe(`private-client.${CLIENT_ID}`)
    .bind('request.status.updated', (data) => {
        console.log(`Job ${data.request_id}: ${data.status} (${data.progress}%)`);
        if (data.result_url) console.log('Result:', data.result_url);
    });

When to Use WebSockets vs Webhooks

FeatureWebSocketsWebhooks
LatencyInstant (milliseconds)Near-instant (seconds)
ConnectionPersistentPer-event HTTP request
Progress updatesYes, with image previewsNo
Server requirementsWebSocket clientHTTP endpoint
ReliabilityRequires active connectionAutomatic retries
Best forInteractive UIs, real-time appsBackend integrations, serverless
For maximum reliability, use both: WebSockets for real-time UI updates and webhooks as a backup for critical notifications.

Configuration

Connection Details

SettingValue
Hostsoketi.deapi.ai
Port443
ProtocolWSS (secure)
App Keydepin-api-prod-key

Required Credentials

Connecting

We use a Pusher-compatible protocol. You can use any Pusher client library.
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

const echo = new Echo({
    broadcaster: 'pusher',
    key: 'depin-api-prod-key',
    wsHost: 'soketi.deapi.ai',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: (socketId, callback) => {
            fetch('https://api.deapi.ai/broadcasting/auth', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${YOUR_API_TOKEN}`
                },
                body: JSON.stringify({
                    socket_id: socketId,
                    channel_name: channel.name
                })
            })
            .then(response => response.json())
            .then(data => callback(null, data))
            .catch(error => callback(error));
        }
    })
});

// Subscribe to your channel
echo.private(`client.${YOUR_CLIENT_ID}`)
    .listen('.request.status.updated', (data) => {
        console.log('Job update:', data);
    });

Using Pusher Client Directly

import Pusher from 'pusher-js';

const pusher = new Pusher('depin-api-prod-key', {
    wsHost: 'soketi.deapi.ai',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            try {
                const response = await fetch('https://api.deapi.ai/broadcasting/auth', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${YOUR_API_TOKEN}`
                    },
                    body: JSON.stringify({
                        socket_id: socketId,
                        channel_name: channel.name
                    })
                });
                callback(null, await response.json());
            } catch (error) {
                callback(error);
            }
        }
    })
});

const channel = pusher.subscribe(`private-client.${YOUR_CLIENT_ID}`);
channel.bind('request.status.updated', (data) => {
    console.log('Job update:', data);
});

Channels

Each client has a private channel scoped to their account:
private-client.{client_id}
Private channels require authentication. You can only subscribe to your own channel.
When using Laravel Echo, use .listen('.request.status.updated', ...) with a dot prefix. With raw Pusher, use .bind('request.status.updated', ...) without the dot.

Events

request.status.updated

Sent whenever a job’s status changes or progress is updated. Event Payload:
{
  "request_id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "in_progress",
  "preview": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
  "result_url": null,
  "progress": "45.50"
}
FieldTypeDescription
request_idstringJob’s unique identifier (UUID)
statusstringCurrent status: processing, in_progress, or done
previewstring | nullBase64 preview image (image generation jobs only)
result_urlstring | nullSigned download URL (only when done)
progressstringProgress percentage as decimal string (e.g., "45.50")
The result_url is a signed URL that expires. Download promptly or use the job status endpoint to get a fresh URL.

Status Flow

pending → processing → in_progress (multiple) → done
                    ↘                         ↗
                      ─────── error ─────────
StatusDescriptionWebSocket Event
pendingJob queued, waiting for workerNo
processingWorker assigned, startingYes
in_progressActively generatingYes (with previews)
doneCompleted successfullyYes (with result URL)
errorFailedVia webhooks only

Complete Examples

React Hook

import { useEffect, useState, useCallback } from 'react';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

interface JobUpdate {
  request_id: string;
  status: 'processing' | 'in_progress' | 'done';
  preview: string | null;
  result_url: string | null;
  progress: string;
}

export function useJobUpdates(clientId: string, apiToken: string) {
  const [updates, setUpdates] = useState<Map<string, JobUpdate>>(new Map());
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    window.Pusher = Pusher;

    const echo = new Echo({
      broadcaster: 'pusher',
      key: 'depin-api-prod-key',
      wsHost: 'soketi.deapi.ai',
      wsPort: 443,
      wssPort: 443,
      forceTLS: true,
      enabledTransports: ['ws', 'wss'],
      cluster: 'mt1',
      authorizer: (channel) => ({
        authorize: (socketId, callback) => {
          fetch('https://api.deapi.ai/broadcasting/auth', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${apiToken}`
            },
            body: JSON.stringify({
              socket_id: socketId,
              channel_name: channel.name
            })
          })
          .then(res => res.json())
          .then(data => callback(null, data))
          .catch(err => callback(err));
        }
      })
    });

    echo.connector.pusher.connection.bind('connected', () => setConnected(true));
    echo.connector.pusher.connection.bind('disconnected', () => setConnected(false));

    echo.private(`client.${clientId}`)
      .listen('.request.status.updated', (data: JobUpdate) => {
        setUpdates(prev => new Map(prev).set(data.request_id, data));
      });

    return () => {
      echo.leave(`client.${clientId}`);
      echo.disconnect();
    };
  }, [clientId, apiToken]);

  const getJobUpdate = useCallback((requestId: string) => updates.get(requestId), [updates]);

  return { updates, connected, getJobUpdate };
}

Node.js Backend

import Pusher from 'pusher-js';

const CLIENT_ID = process.env.DEAPI_CLIENT_ID;
const API_TOKEN = process.env.DEAPI_API_TOKEN;

const pusher = new Pusher('depin-api-prod-key', {
    wsHost: 'soketi.deapi.ai',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            try {
                const response = await fetch('https://api.deapi.ai/broadcasting/auth', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${API_TOKEN}`
                    },
                    body: JSON.stringify({
                        socket_id: socketId,
                        channel_name: channel.name
                    })
                });
                callback(null, await response.json());
            } catch (error) {
                callback(error);
            }
        }
    })
});

pusher.connection.bind('connected', () => console.log('Connected'));
pusher.connection.bind('error', (err) => console.error('Error:', err));

const channel = pusher.subscribe(`private-client.${CLIENT_ID}`);

channel.bind('pusher:subscription_succeeded', () => console.log('Subscribed'));

channel.bind('request.status.updated', (data) => {
    console.log(`Job ${data.request_id}: ${data.status} (${data.progress}%)`);
    if (data.preview) console.log('Preview available');
    if (data.result_url) console.log('Download:', data.result_url);
});

process.on('SIGINT', () => { pusher.disconnect(); process.exit(); });

Authentication

Private channels require authorization via the broadcasting auth endpoint. Endpoint: POST https://api.deapi.ai/broadcasting/auth Headers:
Authorization: Bearer <your-api-token>
Content-Type: application/json
Request:
{
  "socket_id": "123456.789012",
  "channel_name": "private-client.42"
}
Response:
{
  "auth": "depin-api-prod-key:signature..."
}
The Pusher client libraries handle this automatically via the authorizer callback.

Connection Management

Keep-Alive

The WebSocket server expects a ping every 30 seconds. Most Pusher client libraries handle this automatically. If implementing a custom client, send a pusher:ping event within the timeout window to maintain the connection.

Reconnection

The Pusher client handles reconnection automatically with exponential backoff.
// Monitor connection state
pusher.connection.bind('state_change', (states) => {
    console.log('State:', states.current);
    // States: initialized, connecting, connected, unavailable, failed, disconnected
});

// Clean up when done
pusher.unsubscribe(`private-client.${clientId}`);
pusher.disconnect();

Troubleshooting

  • Verify your API token is valid
  • Check that you’re using soketi.deapi.ai as the host
  • Ensure port 443 is not blocked by your firewall
  • Confirm your client ID matches your API token
  • Verify channel name format: private-client.{your_client_id}
  • Check that your API token has not been revoked
  • Use .request.status.updated with Echo (dot prefix) or request.status.updated with Pusher (no dot)
  • Verify subscription succeeded via pusher:subscription_succeeded event
  • Ensure jobs are submitted with the same client credentials
  • The client library reconnects automatically
  • Monitor state_change events to track connection health
  • Consider using webhooks as a backup