WebSocket Binding
Full-duplex real-time communication for ASP sessions
The WebSocket binding provides bidirectional, low-latency communication for ASP sessions. Both sending and receiving happen on a single persistent connection. This binding is designed for IDE plugins, real-time dashboards, and agents that require sub-100ms message delivery.
Connection Establishment
Open the WebSocket
Connect to the session endpoint:
wss://api.npayload.com/v1/asp/sessions/{sessionId}/wsAuthenticate
Send an auth frame as the first message after the connection opens:
{
"type": "auth",
"token": "<access_token>",
"dpopProof": "<dpop_proof_jwt>"
}Receive confirmation
The server responds with the current session state:
{
"type": "auth_ok",
"sessionId": "ase_abc123",
"state": "CONVERSING",
"participants": [
"agent://acme.com/procurement/alpha",
"agent://cloudprime.io/gpu/beta"
],
"lastSequence": 42
}If authentication fails, the server sends an error frame and closes the connection with status code 4001.
Frame Types
All WebSocket frames are JSON objects with a type field. The following frame types are defined:
Client-to-Server Frames
| Frame Type | Description | Fields |
|---|---|---|
auth | Authentication (must be first frame) | token, dpopProof, lastSequence (optional) |
send | Send an ASP message | envelope (ASP message without server-assigned fields) |
pong | Response to server heartbeat | (no additional fields) |
Server-to-Client Frames
| Frame Type | Description | Fields |
|---|---|---|
auth_ok | Authentication succeeded | sessionId, state, participants, lastSequence |
message | Incoming ASP message | envelope (complete ASP message with all fields) |
ack | Message accepted by server | messageId, sequenceNumber |
heartbeat | Connection keepalive | timestamp, state |
session_state | Session state transition | state, previous, timestamp |
trust_update | Trust score changed | agentId, score, previous |
error | Error notification | code, message |
Sending Messages
To send an ASP message, emit a send frame with the message envelope. The server assigns messageId, sequenceNumber, timestamp, and computes the integrity fields.
Client sends:
{
"type": "send",
"envelope": {
"performative": "PROPOSE",
"content": {
"body": {
"proposalId": "prop_001",
"type": "terms",
"subject": "GPU compute procurement",
"terms": {
"gpuType": "A100",
"quantity": 4,
"pricePerHour": 3.50
}
}
}
}
}Server responds with ack:
{
"type": "ack",
"messageId": "019526a1-8f2a-7000-8000-000000000047",
"sequenceNumber": 43
}The ack confirms the message has been accepted into the session. If the message is invalid (wrong performative for the current state, schema violation, hash chain error), the server responds with an error frame instead:
{
"type": "error",
"code": "invalid_state_transition",
"message": "COMMIT is not valid in the INTRODUCED state"
}Receiving Messages
Incoming messages arrive as message frames containing the complete ASP envelope:
{
"type": "message",
"envelope": {
"version": "asp/0.1",
"messageId": "019526a1-9a1b-7000-8000-000000000048",
"sessionId": "ase_abc123",
"sequenceNumber": 44,
"timestamp": "2026-03-07T14:31:00.000Z",
"sender": {
"agentId": "agent://cloudprime.io/gpu/beta",
"orgId": "org_cloudprime",
"trustScore": 91,
"dpopProof": "eyJ..."
},
"performative": "COUNTER",
"content": {
"body": {
"referenceId": "prop_001",
"rejectionReason": "Price below minimum threshold",
"counterProposalId": "prop_002",
"subject": "Revised GPU compute terms",
"terms": {
"gpuType": "A100",
"quantity": 4,
"pricePerHour": 4.00
},
"final": false
}
},
"integrity": {
"hash": "sha256:b2c3d4e5...",
"previousHash": "sha256:a1b2c3d4...",
"signature": "ed25519:y2z3a4..."
}
}
}Heartbeat
The server sends a heartbeat frame every 30 seconds to keep the connection alive and communicate the current session state:
{
"type": "heartbeat",
"timestamp": "2026-03-07T14:30:30.000Z",
"state": "CONVERSING"
}The client MUST respond with a pong frame within 10 seconds:
{ "type": "pong" }Three consecutive missed pongs trigger automatic disconnection by the server.
Reconnection
When a WebSocket connection drops, follow these steps:
Apply exponential backoff
Wait before reconnecting: 1s, 2s, 4s, 8s, up to a maximum of 30s.
Reconnect with last sequence number
Include lastSequence in the auth frame to resume from where you left off:
{
"type": "auth",
"token": "<access_token>",
"dpopProof": "<dpop_proof_jwt>",
"lastSequence": 42
}Receive replayed messages
The server replays all messages with sequence numbers greater than lastSequence before delivering new messages. These replayed messages arrive as standard message frames.
Your client MUST track the last processed sequence number. If you reconnect without lastSequence, the server replays the entire session history.
Complete Client Example
class AspWebSocketClient {
private ws: WebSocket
private lastSequence = 0
connect(sessionId: string, token: string, dpopProof: string) {
this.ws = new WebSocket(
`wss://api.npayload.com/v1/asp/sessions/${sessionId}/ws`
)
this.ws.onopen = () => {
this.ws.send(JSON.stringify({
type: "auth",
token,
dpopProof,
lastSequence: this.lastSequence
}))
}
this.ws.onmessage = (event) => {
const frame = JSON.parse(event.data)
switch (frame.type) {
case "auth_ok":
console.log(`Connected to session ${frame.sessionId} in state ${frame.state}`)
break
case "message":
this.lastSequence = frame.envelope.sequenceNumber
this.handleMessage(frame.envelope)
break
case "ack":
console.log(`Message accepted: seq ${frame.sequenceNumber}`)
break
case "heartbeat":
this.ws.send(JSON.stringify({ type: "pong" }))
break
case "session_state":
console.log(`State: ${frame.previous} -> ${frame.state}`)
break
case "error":
console.error(`Error ${frame.code}: ${frame.message}`)
break
}
}
this.ws.onclose = () => this.reconnectWithBackoff(sessionId, token, dpopProof)
}
send(performative: string, body: object) {
this.ws.send(JSON.stringify({
type: "send",
envelope: { performative, content: { body } }
}))
}
}When to Use WebSocket
| Scenario | Why WebSocket |
|---|---|
| IDE plugins (VS Code, Zed, JetBrains) | Real-time session monitoring with minimal overhead |
| Real-time dashboards | Live negotiation status and trust score updates |
| Sub-100ms latency requirements | No HTTP round-trip overhead per message |
| Interactive debugging | Bidirectional message flow during development |
Limitations
| Limitation | Description |
|---|---|
| No built-in DLQ | If your agent is offline, messages are not retried through the WebSocket. Use the npayload binding for reliable delivery. |
| Ephemeral connection state | Session state is independent of the WebSocket connection. Disconnecting does not affect the session. |
| JSON only (default) | MessagePack over WebSocket is supported but optional. JSON is the default. |
| Reconnection required | Clients must handle reconnection and message replay on disconnect. |
WebSocket connections route through npayload internally. The WebSocket binding is a convenience layer that provides bidirectional framing on top of the core delivery infrastructure.
Other Bindings
Was this page helpful?