Generate signature


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.

Issues the signature that authorizes opening one Invox Genesis consultation for one physician. This call is made server to server: never call it from the browser, since it requires your API Key credentials.

Request description

Endpoint: /api/v1/generate-soft-integration-signature
Method: POST

{
  "Content-Type": "application/json",
  "Authorization": "Bearer <accessToken>"
}

Note: the <accessToken> is obtained as described in Prerequisites. The API Key behind it must hold the GENESIS-SOFT-INTEGRATION permission.

To set the API version on a specific request, you can add a specific header named X-Invox-Medical-Api-Version with the value of the version you are targeting:

X-Invox-Medical-Api-Version: 2026-08-01

SOFT Integration was introduced in 2026-08-01, so that is the only version this endpoint accepts. Omitting the header resolves to the latest version. Sending an earlier version such as 2026-03-01 is rejected with 400 and invalidApiVersionForEndpoint, because SOFT Integration did not exist back then.

Payload

{
  "organizationId": "1111-1111-1111-1111",
  "email": "doctor@cliente.com",
  "requestId": "2222-2222-2222-2222",
  "consultationMetadata": {
    "patientId": "PAT-123-56",
    "hcId": "1234567"
  }
}

Payload structure

type GenerateSoftIntegrationSignaturePayload = {
  organizationId: string;
  email: string;
  requestId: string;
  consultationMetadata?: Record<string, unknown>;
};
consultationMetadata is free-form. The fields shown above are only an example. You can send as many fields as you need to describe the consultation — patient name, appointment identifier, department, episode number, or anything else meaningful in your EHR. Invox Medical stores them as sent and returns them untouched in the notification and in the result, so you can use them to reconcile the consultation on your side.

Field rules

FieldRequiredRules
organizationIdYesYour organization in the Invox Medical platform. The physician must belong to it.
emailYesEmail of the physician. Normalized to lower case before resolving the user, so it is case-insensitive.
requestIdYesIdentifier generated by you. Single-use: it can never be reused, not even after cancelling.
consultationMetadataNoConsultation context displayed to the physician inside Invox Genesis. Free-form; never used for authorization.
requestId is single-use. Once a requestId has been accepted it stays reserved, so a later request carrying the same value is rejected with 400 and duplicatedRequestId. This holds even if the consultation was finished or cancelled: cancelling releases the physician, never the requestId. Generate a fresh value on every retry, and keep your own mapping from requestId to the encounter in your EHR.
requestId is the key you will use later to retrieve the result, and the value echoed back in the notification. Use a value that is unique in your system. If you derive it from an encounter identifier, add a suffix such as an attempt counter or a timestamp so that reopening the same encounter still produces a new value.

Validation performed

Before issuing the signature, the platform verifies fail-closed that:

  1. The access token is valid and the API Key holds the GENESIS-SOFT-INTEGRATION permission.
  2. The payload is well formed and the requestId has never been used before.
  3. The physician identified by email exists in the identity provider.
  4. The physician is enabled.
  5. The physician belongs to the declared organizationId.

If any of these checks fails, no signature is issued.

Signature lifetime

The issued signature is valid for a maximum of 3 hours. After that it can no longer be used to open Invox Genesis or to open the notification WebSocket, and every attempt is rejected.

The latest session wins

A physician can only have one active SOFT Integration session at a time. Requesting a new signature for a physician who already has one does not fail: the previous session is cancelled automatically and the new one takes over.

Only the physician opens these sessions, so a second request means they moved on and the earlier consultation was abandoned.

A session stops being active when:

  • the physician finishes the consultation,
  • you release it with Cancel consultation,
  • a newer signature is issued for the same physician, or
  • the signature reaches its 3-hour lifetime.
Cancellation is silent for the displaced session. If the physician still had the previous consultation open, it stops working without warning and produces no result. Request a new signature only when you intend to replace the previous consultation.
This also covers the abandoned-browser case: if the physician closes the window without finishing, the next signature request releases that session instead of being blocked until it expires.
Replacing a session does not recycle its identifier: the new signature must carry a newrequestId. Reusing the one you just displaced is rejected with 400 and duplicatedRequestId.

Responses

Correct response

Successful request

Describe the characteristics of a satisfactory response

200

Response structure:

{
  "signature": "<signature>"
}

Wrong responses

Bad request

Describe the characteristics of a bad request

400

Response body

{
  "message": "Field \"requestId\" is required.",
  "errorType": "missingOrInvalidParameter"
}
Possible values of errorType
type GenerateSignatureBadRequestErrorType =
  | "missingOrInvalidParameter" // organizationId, email or requestId missing or malformed
  | "duplicatedRequestId"; // the requestId was already used and cannot be reused


Unauthorized request

Describe the characteristics of an unauthorized request

401

Response body

{
  // empty body
}

Description: this error occurs when the endpoint authorizer fails to validate the token sent in the request header.


Forbidden request

Describe the characteristics of a forbidden request

403

Response body

{
  "message": "User does not belong to the specified organization.",
  "errorType": "userNotEligible"
}
Possible values of errorType
type GenerateSignatureForbiddenErrorType =
  | "invalidPermission" // the API Key does not hold GENESIS-SOFT-INTEGRATION
  | "userNotEligible"; // user unknown, disabled, or in another organization

userNotEligible is deliberately returned for the three cases (unknown user, disabled user, wrong organization) without distinguishing between them, to avoid disclosing which emails exist in the platform.

Conflict

Describe the characteristics of two signature requests racing for the same physician

409

Response body

{
  "message": "The physician already has an active SOFT Integration session.",
  "errorType": "activeSessionExists"
}

Description: rare. An existing session no longer causes this, because it is cancelled automatically. It only happens when two requests for the same physician arrive at the same instant and one loses the race. Retry.

Example

TypeScript
const response = await fetch(
  "https://api-suite.invoxmedical.com/api/v1/generate-soft-integration-signature",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      organizationId,
      email: physicianEmail,
      requestId,
      consultationMetadata: { patientId, hcId },
    }),
  },
);

if (!response.ok) {
  // handle 400 / 401 / 403 / 409 — do not open Invox Genesis
}

const { signature } = await response.json();
C#
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

var payload = new
{
    organizationId,
    email = physicianEmail,
    requestId,
    consultationMetadata = new { patientId, hcId }
};

var content = new StringContent(
    JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

var response = await client.PostAsync(
    "https://api-suite.invoxmedical.com/api/v1/generate-soft-integration-signature",
    content);

if (!response.IsSuccessStatusCode)
{
    // handle 400 / 401 / 403 / 409 — do not open Invox Genesis
}

using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var signature = doc.RootElement.GetProperty("signature").GetString();
Java
import java.net.URI;
import java.net.http.*;
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

String body = mapper.writeValueAsString(Map.of(
    "organizationId", organizationId,
    "email", physicianEmail,
    "requestId", requestId,
    "consultationMetadata", Map.of("patientId", patientId, "hcId", hcId)
));

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api-suite.invoxmedical.com/api/v1/generate-soft-integration-signature"))
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer " + accessToken)
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
    // handle 400 / 401 / 403 / 409 — do not open Invox Genesis
}

String signature = mapper.readTree(response.body()).get("signature").asText();
Python
import requests

response = requests.post(
    "https://api-suite.invoxmedical.com/api/v1/generate-soft-integration-signature",
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}",
    },
    json={
        "organizationId": organization_id,
        "email": physician_email,
        "requestId": request_id,
        "consultationMetadata": {"patientId": patient_id, "hcId": hc_id},
    },
)

if not response.ok:
    # handle 400 / 401 / 403 / 409 — do not open Invox Genesis
    pass

signature = response.json()["signature"]

Next step

Use the signature to open the Invox Genesis consultation.