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"
}
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
| Message | Description |
|---|---|
{ "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 Type | Description |
|---|---|
connected | Initial connection confirmation with connectionId |
pong | Response to ping, includes timestamp |
subscribed | Confirms subscription to a specific jobId |
unsubscribed | Confirms unsubscription from a specific jobId |
subscribed-all | Confirms subscribe-all |
unsubscribed-all | Confirms unsubscribe-all |
job-status-update | Job status change event with full job details |
error | Error 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.
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
| Limit | Default |
|---|---|
| Max connections per API key | 10 |
| Max connection duration | 24 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:
| Code | Meaning |
|---|---|
CONNECTION_LIMIT_EXCEEDED | Too many open connections for this API key |
RATE_LIMIT_EXCEEDED | Too many subscribe/unsubscribe operations (includes retryAfterSeconds) |
UNKNOWN_MESSAGE_TYPE | Unrecognized message type |
INVALID_MESSAGE | Malformed JSON or missing required fields |
WebSocket vs. Polling
| WebSocket | REST Polling | |
|---|---|---|
| Latency | Instant on status change | Depends on poll interval |
| Efficiency | Single persistent connection | Repeated HTTP requests |
| Best for | Real-time UIs, automated pipelines | Simple scripts, one-off checks |
| Endpoint | wss://api-testnet.kurier.xyz/api/v1/ws | GET /api/v1/job-status/<apiKey>/<jobId> |
Both approaches return the same job status data. Use whichever fits your use case.