EnvoyX Docs

WebSockets

WebSocket Connection

WebSockets provide real-time, bidirectional communication between your client and EnvoyX. Unlike webhooks (server-to-server), WebSockets are ideal for web and mobile applications that need instant updates.

When to Use WebSockets

Use WebSockets when:

  • Building real-time dashboards
  • Creating web/mobile apps with live updates
  • Need instant feedback on invoice processing
  • Want to avoid polling

Use Webhooks when:

  • Building server-to-server integrations
  • Need guaranteed delivery with retries
  • Processing events asynchronously
  • Don't need bidirectional communication

Connection

Endpoint

wss://staging-api.tryenvoyx.com/api/v1/ws

Authentication

Authenticate with your API key via query parameter:

wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=YOUR_API_KEY

Client Examples

// Browser or Node.js (with ws package)

const apiKey = 'YOUR_API_KEY'
const ws = new WebSocket(`wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=${apiKey}`)

ws.onopen = () => {
  console.log('Connected to EnvoyX WebSocket')

  // Send ping every 30 seconds to keep connection alive
  setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send('ping')
    }
  }, 30000)
}

ws.onmessage = (event) => {
  const data = JSON.parse(event.data)

  console.log('Event received:', data)

  // Handle different event types
  switch (data.event) {
    case 'invoice.processed':
      handleInvoiceProcessed(data.data)
      break
    case 'invoice.flagged':
      handleInvoiceFlagged(data.data)
      break
    case 'pong':
      console.log('Pong received')
      break
    default:
      console.log('Unknown event:', data.event)
  }
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}

ws.onclose = (event) => {
  console.log('WebSocket closed:', event.code, event.reason)

  // Implement reconnection logic
  setTimeout(() => {
    console.log('Reconnecting...')
    connectWebSocket()
  }, 5000)
}

// Graceful shutdown
window.addEventListener('beforeunload', () => {
  ws.close(1000, 'Client disconnecting')
})
import asyncio
import websockets
import json

async def connect_websocket(api_key):
    uri = f"wss://staging-api.tryenvoyx.com/api/v1/ws?api_key={api_key}"

    async with websockets.connect(uri) as websocket:
        print("Connected to EnvoyX WebSocket")

        # Send ping every 30 seconds
        async def send_ping():
            while True:
                await asyncio.sleep(30)
                await websocket.send("ping")

        # Start ping task
        ping_task = asyncio.create_task(send_ping())

        try:
            # Receive messages
            async for message in websocket:
                data = json.loads(message)
                print(f"Event received: {data['event']}")

                # Handle different event types
                if data['event'] == 'invoice.processed':
                    handle_invoice_processed(data['data'])
                elif data['event'] == 'invoice.flagged':
                    handle_invoice_flagged(data['data'])

        except websockets.exceptions.ConnectionClosed:
            print("WebSocket closed")
            ping_task.cancel()

# Run
asyncio.run(connect_websocket("YOUR_API_KEY"))
package main

import (
    "encoding/json"
    "log"
    "time"

    "github.com/gorilla/websocket"
)

type Event struct {
    Event string                 `json:"event"`
    Data  map[string]interface{} `json:"data"`
}

func main() {
    apiKey := "YOUR_API_KEY"
    url := "wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=" + apiKey

    conn, _, err := websocket.DefaultDialer.Dial(url, nil)
    if err != nil {
        log.Fatal("WebSocket connection error:", err)
    }
    defer conn.Close()

    log.Println("Connected to EnvoyX WebSocket")

    // Send ping every 30 seconds
    go func() {
        ticker := time.NewTicker(30 * time.Second)
        defer ticker.Stop()

        for range ticker.C {
            if err := conn.WriteMessage(websocket.TextMessage, []byte("ping")); err != nil {
                log.Println("Ping error:", err)
                return
            }
        }
    }()

    // Receive messages
    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }

        var event Event
        if err := json.Unmarshal(message, &event); err != nil {
            log.Println("JSON parse error:", err)
            continue
        }

        log.Printf("Event received: %s\n", event.Event)

        switch event.Event {
        case "invoice.processed":
            handleInvoiceProcessed(event.Data)
        case "invoice.flagged":
            handleInvoiceFlagged(event.Data)
        }
    }
}

Event Format

All WebSocket messages follow the same format as webhook payloads:

{
  "event": "invoice.processed",
  "timestamp": "2024-02-10T12:00:00Z",
  "data": {
    "id": "inv_clx1234567890",
    "status": "PROCESSED",
    "extracted_data": {
      "claim_number": "CLM-2024-001",
      "total_amount": 250.00
    }
  }
}

See Webhook Events for full event documentation.

Connection Lifecycle

1. Connect

const ws = new WebSocket('wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=YOUR_API_KEY')

2. Keep Alive

Send ping every 30 seconds to prevent timeout:

setInterval(() => {
  ws.send('ping')
}, 30000)

Server responds with pong:

{
  "event": "pong",
  "timestamp": "2024-02-10T12:00:00Z"
}

3. Receive Events

All invoice events are automatically sent to your WebSocket connection.

4. Disconnect

Close gracefully:

ws.close(1000, 'Client disconnecting')

Reconnection Strategy

Implement exponential backoff for reconnections:

let reconnectAttempts = 0
const maxReconnectAttempts = 10

function connectWebSocket(apiKey) {
  const ws = new WebSocket(`wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=${apiKey}`)

  ws.onopen = () => {
    console.log('Connected')
    reconnectAttempts = 0  // Reset on successful connection
  }

  ws.onclose = (event) => {
    if (reconnectAttempts < maxReconnectAttempts) {
      const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000)
      reconnectAttempts++

      console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})`)

      setTimeout(() => {
        connectWebSocket(apiKey)
      }, delay)
    } else {
      console.error('Max reconnection attempts reached')
    }
  }
}

Error Handling

Connection Errors

ws.onerror = (error) => {
  console.error('WebSocket error:', error)

  // Check common issues:
  // - Invalid or expired API key
  // - Network connectivity
  // - Server unavailable
}

Authentication Errors

If authentication fails, the server closes the connection with code 4401:

ws.onclose = (event) => {
  if (event.code === 4401) {
    console.error('Authentication failed - API key invalid or expired')
    // Check your API key and try again
  }
}

Close Codes

CodeReasonAction
1000Normal closureNo action needed
1001Going awayReconnect if needed
1006Abnormal closureReconnect with backoff
4401UnauthorizedCheck API key
4429Rate limitedWait before reconnecting

Rate Limiting

WebSocket connections have the following limits:

  • Max Connections: 5 simultaneous connections per API key
  • Message Rate: 100 messages per minute
  • Connection Rate: 10 new connections per minute

Exceeding rate limits will result in connection closure with code 4429.

Best Practices

1. Implement Heartbeat

Always send ping messages to keep the connection alive:

const pingInterval = setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send('ping')
  }
}, 30000)

ws.onclose = () => {
  clearInterval(pingInterval)
}

2. Handle Reconnection

Network issues are common. Always implement reconnection logic with exponential backoff.

3. Graceful Shutdown

Always close connections properly:

window.addEventListener('beforeunload', () => {
  ws.close(1000, 'Client disconnecting')
})

4. Message Queuing

Handle offline scenarios by queuing actions:

const messageQueue = []

function sendMessage(message) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify(message))
  } else {
    messageQueue.push(message)
  }
}

ws.onopen = () => {
  // Send queued messages
  while (messageQueue.length > 0) {
    ws.send(JSON.stringify(messageQueue.shift()))
  }
}

React Hook Example

import { useEffect, useState } from 'react'

function useEnvoyXWebSocket(apiKey) {
  const [events, setEvents] = useState([])
  const [connected, setConnected] = useState(false)

  useEffect(() => {
    if (!apiKey) return

    const ws = new WebSocket(
      `wss://staging-api.tryenvoyx.com/api/v1/ws?api_key=${apiKey}`
    )

    ws.onopen = () => {
      setConnected(true)

      // Heartbeat
      const pingInterval = setInterval(() => {
        ws.send('ping')
      }, 30000)

      ws.pingInterval = pingInterval
    }

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      setEvents((prev) => [...prev, data])
    }

    ws.onclose = () => {
      setConnected(false)
      clearInterval(ws.pingInterval)
    }

    return () => {
      ws.close(1000)
    }
  }, [apiKey])

  return { events, connected }
}

// Usage
function Dashboard({ apiKey }) {
  const { events, connected } = useEnvoyXWebSocket(apiKey)

  return (
    <div>
      <p>Status: {connected ? 'Connected' : 'Disconnected'}</p>
      <ul>
        {events.map((event, i) => (
          <li key={i}>{event.event}: {event.data.id}</li>
        ))}
      </ul>
    </div>
  )
}

Comparison: WebSocket vs Webhooks

FeatureWebSocketWebhooks
ConnectionPersistent, bidirectionalRequest per event
LatencyInstant (<100ms)1-2 seconds
Client TypeWeb/mobile appsServers
ReliabilityRequires reconnection logicBuilt-in retries
Scalability5 connections/keyUnlimited
Best ForReal-time dashboardsServer integrations

Next Steps

On this page