Skip to main content
Version: Testnet

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 testnet endpoint with your API key as a query parameter:

wss://api-testnet.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-testnet.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-testnet.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.