Skip to main content

Quickstart — Your First Form in 15 Minutes

Prerequisites: a BPMN process with at least one userTask already registered in Stateway, and a valid API Key.

Step 1 — Create a Session in Your Backend​

When a user logs into your application, add this call to your authentication handler:

// Node.js — your backend
const response = await fetch('https://api.stateway.io/v1/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.STATEWAY_API_KEY,
},
body: JSON.stringify({
user_id: user.id, // id of the user in YOUR system
user_display: user.name,
ttl: 3600,
scope: {
definitions: [
{
key: 'my-process',
roles: ['operator'],
candidate_as_user: false,
can_start: true,
max_concurrent: 1
}
]
}
})
});

const { session_token } = await response.json();

// Include session_token in the login response to the frontend
// Do NOT persist the token in the backend — it is single-use by the frontend

The session_token is shown only once in the response. Forward it immediately to the frontend.

Step 2 — Install the SDK​

npm install @stateway/frontend-sdk
# or
yarn add @stateway/frontend-sdk

Step 3 — Connect and Display Pending Work​

import { StatewayClient } from '@stateway/frontend-sdk';

// sessionToken comes from the login response of your backend
const sw = new StatewayClient({
endpoint: 'wss://ws.stateway.io',
sessionToken: auth.statewayToken,
});

sw.on('work.snapshot', (items) => {
if (items.length === 0) return;
const task = items[0];

document.querySelector('#task-name').textContent = task.name;
document.querySelector('#task-form').innerHTML =
renderForm(task.formSchema, task.variables);
});

sw.on('work.empty', ({ definitions }) => {
const [def] = definitions.filter(d => d.canStart);
if (def) {
document.querySelector('#start-btn').style.display = 'block';
document.querySelector('#start-btn').onclick = () =>
sw.startProcess(def.key);
}
});

work.snapshot fires immediately after connect with all tasks the user currently has pending. work.empty fires when there is no pending work but the user may start new instances.

Step 4 — Complete the Task​

document.querySelector('#submit-btn').onclick = async () => {
const formData = getFormData();
await sw.completeTask(currentTaskId, formData);
// The next state arrives automatically via work.snapshot or task.assigned
};

The SDK handles reconnection automatically. If the connection drops, it reconnects with exponential backoff (1s → 2s → 4s → … capped at 30s).

Events are not replayed. There is no event buffer: anything the server emitted while you were offline is lost. Recovery on reconnect relies solely on the fresh snapshot the server sends (work.loading → work.snapshot | work.empty) — treat every work.snapshot/work.empty as the full replacement of your local work state, not as an increment. This covers work items and open instances only: notification and instance.completed events missed while offline are not recoverable through the snapshot — use search() to reconcile instance status if you need it.

If an action (completeTask, claimTask, startProcess, …) is in flight when the socket drops, its promise stays pending until the SDK reconnects and then rejects with CONNECTION_LOST — or with TIMEOUT, if the 30s request timeout expires first. Either way the outcome is unknown: the request may or may not have been applied. Do not blindly retry — wait for the next snapshot to see the resulting state. Also note that close codes 4000-4999 (authentication failures) are terminal: the SDK emits error (with the close reason as error.code), does not emit disconnected, and does not reconnect — your app must renew the session and reconnect.

Next Steps​