WebSocket notifications


Version Notice: SOFT Integration is available starting from API version 2026-08-01.
The API version you have selected does not include it. Switch to 2026-08-01 to access this functionality.

The notification WebSocket lets your application know that a consultation has finished without exposing any public HTTP endpoint. It delivers a trigger, not the clinical result.

Connection

wss://live-notifications-soft-integrations.invoxmedical.com?third-party-signature=<signature>

The signature is the credential: use the same signature you used to open Invox Genesis, and open the connection while it is still valid.

Handshake result

ResultMeaning
Connection openThe signature is valid and the connection is bound to the consultation.
401 at handshakeThe signature is missing, unknown or expired. The connection is not established.

Open the connection right after opening Invox Genesis, so that no notification is missed.

Messages you receive

REPORT_READY

Sent once the consultation has finished and the result is available.

{
  "type": "REPORT_READY",
  "requestId": "2222-2222-2222-2222"
}

React to it by calling Get consultation result with that requestId.

It is also replayed. If the result was produced while you were disconnected, the next ping you send after reconnecting returns REPORT_READY again, right after the pong. You do not need to poll to find out whether you missed it: keep pinging and the notification reaches you.

pong

Response to your keepalive message.

{
  "type": "pong",
  "ts": 1776123550031
}

error

Sent when a message you sent could not be processed. The connection stays open.

{
  "type": "error",
  "error": "UNKNOWN_ACTION"
}
errorCause
INVALID_JSONThe message body was not valid JSON.
UNKNOWN_ACTIONThe action field is not a supported message.

Messages you send

ping

The connection is closed after a period of inactivity. Send a ping every few minutes to keep it alive.

{
  "action": "ping"
}
Idle connections are dropped after approximately 10 minutes without traffic. A keepalive every 5 minutes is a safe interval for consultations that run longer than that.

Reference implementation

TypeScript
const socket = new WebSocket(
  `wss://live-notifications-soft-integrations.invoxmedical.com?third-party-signature=${signature}`,
);

const keepAlive = setInterval(() => {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ action: "ping" }));
  }
}, 5 * 60 * 1000);

socket.addEventListener("message", async (event) => {
  const message = JSON.parse(event.data);

  if (message.type === "REPORT_READY") {
    await fetchConsultationResult(message.requestId);
  }
});

socket.addEventListener("close", () => {
  clearInterval(keepAlive);
});
C#
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

var socket = new ClientWebSocket();
await socket.ConnectAsync(
    new Uri($"wss://live-notifications-soft-integrations.invoxmedical.com?third-party-signature={signature}"),
    CancellationToken.None);

// Keepalive every 5 minutes
_ = Task.Run(async () =>
{
    while (socket.State == WebSocketState.Open)
    {
        await Task.Delay(TimeSpan.FromMinutes(5));
        var ping = Encoding.UTF8.GetBytes("{\"action\":\"ping\"}");
        await socket.SendAsync(ping, WebSocketMessageType.Text, true, CancellationToken.None);
    }
});

var buffer = new byte[4096];
while (socket.State == WebSocketState.Open)
{
    var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
    var json = Encoding.UTF8.GetString(buffer, 0, result.Count);

    using var doc = JsonDocument.Parse(json);
    if (doc.RootElement.GetProperty("type").GetString() == "REPORT_READY")
    {
        var requestId = doc.RootElement.GetProperty("requestId").GetString();
        await FetchConsultationResult(requestId);
    }
}
Java
import java.net.URI;
import java.net.http.*;
import java.util.concurrent.*;
import com.fasterxml.jackson.databind.*;

ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient();

WebSocket socket = client.newWebSocketBuilder()
    .buildAsync(
        URI.create("wss://live-notifications-soft-integrations.invoxmedical.com?third-party-signature=" + signature),
        new WebSocket.Listener() {
            @Override
            public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
                try {
                    JsonNode message = mapper.readTree(data.toString());
                    if ("REPORT_READY".equals(message.get("type").asText())) {
                        fetchConsultationResult(message.get("requestId").asText());
                    }
                } catch (Exception e) {
                    // handle parse error
                }
                ws.request(1);
                return null;
            }
        })
    .join();

// Keepalive every 5 minutes
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
    () -> socket.sendText("{\"action\":\"ping\"}", true),
    5, 5, TimeUnit.MINUTES);
Python
import json
import threading
import websocket  # pip install websocket-client

url = f"wss://live-notifications-soft-integrations.invoxmedical.com?third-party-signature={signature}"


def on_message(ws, raw):
    message = json.loads(raw)
    if message.get("type") == "REPORT_READY":
        fetch_consultation_result(message["requestId"])


def on_open(ws):
    def keep_alive():
        while ws.keep_running:
            ws.send(json.dumps({"action": "ping"}))
            threading.Event().wait(300)

    threading.Thread(target=keep_alive, daemon=True).start()


ws = websocket.WebSocketApp(url, on_open=on_open, on_message=on_message)
ws.run_forever()

Reconnection

If the connection drops while the consultation is still running, reconnect with the same signature, as long as it has not expired. If you were disconnected at the moment the consultation finished, send a ping after reconnecting: the pending REPORT_READY is delivered along with the pong. The result is also retrievable with Get consultation result until it expires.

Do not use the WebSocket as your only guarantee of delivery for long-running workflows. Delivery depends on your client being connected or reconnecting, and it is never retried on its own. For unattended processing, configure the webhook as well.

Lifecycle

  1. Invox Genesis is opened with the signature.
  2. Your application connects the WebSocket with the same signature.
  3. The connection is bound to the consultation.
  4. The physician finishes the consultation.
  5. REPORT_READY is delivered, or replayed on your next ping if you were disconnected.
  6. Your application fetches the result and closes the connection.