llms.txtllms-full.txt
DashboardStatusGet API Key
IntroductionQuickstartModelsPricingArchitecture & SecurityLimits & Quotas
Execution Modes & HTTP QueueWebhooksWebSocketsMCP Servern8n Integrationn8n dryAPI node
API OverviewErrorsText-to-ImagePOSTText-to-Image Price CalculationPOSTText-to-VideoPOSTText-to-Video Price CalculationPOSTImage-to-VideoPOSTImage-to-Video Price CalculationPOSTAudio-to-VideoPOSTAudio-to-Video Price CalculationPOSTText-to-Speech (TTS)POSTText-to-Speech Price CalculationPOSTText-to-MusicPOSTText-to-Music Price CalculationPOSTText-to-EmbeddingPOSTText-to-Embedding Price CalculationPOSTImage-to-ImagePOSTImage-to-Image Price CalculationPOSTImage Background RemovalPOSTImage Background Removal Price CalculationPOSTImage UpscalePOSTImage Upscale Price CalculationPOST
OpenAPI
SDKs & IntegrationsPayment MethodsFAQ — Frequently Asked QuestionsSupport & Contact
dAdryAPI
DashboardStatusGet API Key
Execution Modes & Integrations
Technical Reference

WebSockets

Technical documentation for dryAPI APIs, integration guides, and operational references.

Receive real-time job status updates via WebSocket connections

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.

INFO

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.dryapi.dev',
    wsPort: 443,
    forceTLS: true,
    cluster: 'mt1',
    enabledTransports: ['ws', 'wss'],
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            const res = await fetch('https://api.dryapi.dev/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

TIP

For maximum reliability, use both: WebSockets for real-time UI updates and webhooks as a backup for critical notifications.

Configuration

Connection Details

SettingValue
Hostsoketi.dryapi.dev
Port443
ProtocolWSS (secure)
App Keydepin-api-prod-key

Required Credentials

  • Client ID: Your unique identifier (from dashboard)
  • API Token: Your authentication token (see Authentication)

Connecting

We use a Pusher-compatible protocol. You can use any Pusher client library.

Using Laravel Echo (Recommended for Frontend)

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.dryapi.dev',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: (socketId, callback) => {
            fetch('https://api.dryapi.dev/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.dryapi.dev',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            try {
                const response = await fetch('https://api.dryapi.dev/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);
});
import pysher
import time

YOUR_API_TOKEN = 'your-api-token'
YOUR_CLIENT_ID = 'your-client-id'

pusher = pysher.Pusher(
    key='depin-api-prod-key',
    custom_host='soketi.dryapi.dev',
    secure=True,
    port=443,
    auth_endpoint='https://api.dryapi.dev/broadcasting/auth',
    auth_endpoint_headers={
        'Authorization': f'Bearer {YOUR_API_TOKEN}'
    }
)

def on_connect(data):
    channel = pusher.subscribe(f'private-client.{YOUR_CLIENT_ID}')
    channel.bind('request.status.updated', lambda data: print(f'Update: {data}'))

pusher.connection.bind('pusher:connection_established', on_connect)
pusher.connect()

while True:
    time.sleep(1)

Channels

Each client has a private channel scoped to their account:

private-client.{client_id}

WARNING

Private channels require authentication. You can only subscribe to your own channel.

INFO

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")

WARNING

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.dryapi.dev',
      wsPort: 443,
      wssPort: 443,
      forceTLS: true,
      enabledTransports: ['ws', 'wss'],
      cluster: 'mt1',
      authorizer: (channel) => ({
        authorize: (socketId, callback) => {
          fetch('https://api.dryapi.dev/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.DRYAPI_CLIENT_ID;
const API_TOKEN = process.env.DRYAPI_API_TOKEN;

const pusher = new Pusher('depin-api-prod-key', {
    wsHost: 'soketi.dryapi.dev',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: 'mt1',
    authorizer: (channel) => ({
        authorize: async (socketId, callback) => {
            try {
                const response = await fetch('https://api.dryapi.dev/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.dryapi.dev/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..."
}

INFO

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.dryapi.dev 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

Last updated on 21 March 2026

Webhooks

Previous Page

MCP Server

Next Page

On this page

OverviewQuick StartWhen to Use WebSockets vs WebhooksConfigurationConnection DetailsRequired CredentialsConnectingUsing Laravel Echo (Recommended for Frontend)Using Pusher Client DirectlyChannelsEventsrequest.status.updatedStatus FlowComplete ExamplesReact HookNode.js BackendAuthenticationConnection ManagementKeep-AliveReconnectionTroubleshooting