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
Feature WebSockets Webhooks Latency Instant (milliseconds) Near-instant (seconds) Connection Persistent Per-event HTTP request Progress updates Yes, with image previews No Server requirements WebSocket client HTTP endpoint Reliability Requires active connection Automatic retries Best for Interactive UIs, real-time apps Backend integrations, serverless
For maximum reliability, use both: WebSockets for real-time UI updates and webhooks as a backup for critical notifications.
Configuration
Connection Details
Setting Value Host soketi.deapi.aiPort 443Protocol WSS (secure) App Key depin-api-prod-key
Required Credentials
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.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"
}
Field Type Description request_idstring Job’s unique identifier (UUID) statusstring Current status: processing, in_progress, or done previewstring | null Base64 preview image (image generation jobs only) result_urlstring | null Signed download URL (only when done) progressstring Progress 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 ─────────
Status Description WebSocket Event pendingJob queued, waiting for worker No processingWorker assigned, starting Yes in_progressActively generating Yes (with previews) doneCompleted successfully Yes (with result URL) errorFailed Via 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
Connection fails immediately
Authorization fails (403)
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
Connection drops frequently
The client library reconnects automatically
Monitor state_change events to track connection health
Consider using webhooks as a backup