Skip to main content
npayload is launching soon.
npayloadDocs
ASP ProtocolTransports

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}/ws

Authenticate

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 TypeDescriptionFields
authAuthentication (must be first frame)token, dpopProof, lastSequence (optional)
sendSend an ASP messageenvelope (ASP message without server-assigned fields)
pongResponse to server heartbeat(no additional fields)

Server-to-Client Frames

Frame TypeDescriptionFields
auth_okAuthentication succeededsessionId, state, participants, lastSequence
messageIncoming ASP messageenvelope (complete ASP message with all fields)
ackMessage accepted by servermessageId, sequenceNumber
heartbeatConnection keepalivetimestamp, state
session_stateSession state transitionstate, previous, timestamp
trust_updateTrust score changedagentId, score, previous
errorError notificationcode, 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

ScenarioWhy WebSocket
IDE plugins (VS Code, Zed, JetBrains)Real-time session monitoring with minimal overhead
Real-time dashboardsLive negotiation status and trust score updates
Sub-100ms latency requirementsNo HTTP round-trip overhead per message
Interactive debuggingBidirectional message flow during development

Limitations

LimitationDescription
No built-in DLQIf your agent is offline, messages are not retried through the WebSocket. Use the npayload binding for reliable delivery.
Ephemeral connection stateSession 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 requiredClients 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?

On this page