Skip to main content
Version: Mainnet

WebSocket API

Kurier provides a WebSocket endpoint for real-time job status updates. Instead of polling the REST API, you can open a persistent connection and receive instant notifications as your proofs move through the verification pipeline.

Connecting​

Open a WebSocket connection to the mainnet endpoint with your API key as a query parameter:

wss://api.kurier.xyz/api/v1/ws?apiKey=YOUR_API_KEY

On success, the server sends a confirmation message:

{
"type": "connected",
"connectionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "Connected successfully"
}
info

Your API key is authenticated on connect. Invalid or missing keys will result in the connection being closed with code 1008 (Unauthorized).

Message Protocol​

All messages are JSON. The client sends commands and the server sends responses and events.

Client Commands​

MessageDescription
{ "type": "ping" }Keep-alive ping
{ "type": "subscribe", "jobId": "<uuid>" }Subscribe to a specific job's status updates
{ "type": "unsubscribe", "jobId": "<uuid>" }Unsubscribe from a specific job
{ "type": "subscribe-all" }Subscribe to updates for all your jobs
{ "type": "unsubscribe-all" }Unsubscribe from all jobs

Server Responses​

Message TypeDescription
connectedInitial connection confirmation with connectionId
pongResponse to ping, includes timestamp
subscribedConfirms subscription to a specific jobId
unsubscribedConfirms unsubscription from a specific jobId
subscribed-allConfirms subscribe-all
unsubscribed-allConfirms unsubscribe-all
job-status-updateJob status change event with full job details
errorError with message, optional code and retryAfterSeconds

Job Status Updates​

When a job you are subscribed to changes status, you receive a job-status-update message:

{
"type": "job-status-update",
"data": {
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "Finalized",
"statusId": 6,
"event": "finalized",
"proofType": "groth16",
"chainId": 1,
"txHash": "0x...",
"blockHash": "0x...",
"createdAt": "2026-03-27T12:00:00.000Z",
"updatedAt": "2026-03-27T12:01:30.000Z"
}
}

The data object includes all fields from the REST job status endpoint. See Job Statuses for the full list of status values.

note

When a job reaches a final state (Finalized, Aggregated, or Failed), you are automatically unsubscribed from that job. There is no need to send an explicit unsubscribe.

Example: JavaScript Client​

const ws = new WebSocket(
'wss://api.kurier.xyz/api/v1/ws?apiKey=YOUR_API_KEY'
);

ws.onopen = () => {
console.log('Connected');
};

ws.onmessage = (event) => {
const msg = JSON.parse(event.data);

switch (msg.type) {
case 'connected':
console.log('Connection ID:', msg.connectionId);
// Subscribe to a specific job after submitting a proof
ws.send(JSON.stringify({ type: 'subscribe', jobId: 'YOUR_JOB_ID' }));
break;

case 'job-status-update':
console.log(`Job ${msg.data.jobId}: ${msg.data.status}`);
if (['Finalized', 'Aggregated', 'Failed'].includes(msg.data.status)) {
console.log('Job complete, auto-unsubscribed');
}
break;

case 'pong':
console.log('Server alive at', msg.timestamp);
break;

case 'error':
console.error('Error:', msg.message);
if (msg.retryAfterSeconds) {
console.log(`Retry after ${msg.retryAfterSeconds}s`);
}
break;
}
};

// Keep-alive ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30_000);

Example: Subscribe to All Jobs​

If you want to receive updates for every job submitted under your API key, use subscribe-all instead of subscribing to individual job IDs:

ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe-all' }));
};

ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'job-status-update') {
console.log(`Job ${msg.data.jobId} is now ${msg.data.status}`);
}
};

Connection Limits​

LimitDefault
Max connections per API key10
Max connection duration24 hours

Connections that exceed the maximum duration are closed automatically. Reconnect and re-subscribe as needed.

Error Handling​

Error messages include a code field to help with programmatic handling:

CodeMeaning
CONNECTION_LIMIT_EXCEEDEDToo many open connections for this API key
RATE_LIMIT_EXCEEDEDToo many subscribe/unsubscribe operations (includes retryAfterSeconds)
UNKNOWN_MESSAGE_TYPEUnrecognized message type
INVALID_MESSAGEMalformed JSON or missing required fields

WebSocket vs. Polling​

WebSocketREST Polling
LatencyInstant on status changeDepends on poll interval
EfficiencySingle persistent connectionRepeated HTTP requests
Best forReal-time UIs, automated pipelinesSimple scripts, one-off checks
Endpointwss://api.kurier.xyz/api/v1/wsGET /api/v1/job-status/<apiKey>/<jobId>

Both approaches return the same job status data. Use whichever fits your use case.