Everything TORTUS produces flows back to your application through events . You subscribe with client.on(event, callback), which returns an unsubscribe function.
const unsubscribe = client . on ( 'consultation:started' , ({ consultation }) => {
console . log ( 'Started:' , consultation . reference );
});
unsubscribe (); // stop listening
Events
Event When it fires consultation:startedA consultation has started consultation:resumedAn in-progress consultation was re-opened (e.g. after client restart) consultation:completedA consultation completed successfully consultation:errorA consultation failed with an error consultation:cancelledA consultation was cancelled consultation:closedA consultation was closed
Event When it fires meeting:startedA meeting has started meeting:resumedAn in-progress meeting was re-opened meeting:updatedMeeting metadata (notes/audio) was updated meeting:completedA meeting completed successfully (suppressed when disableFinishMeetingButton is true) meeting:cancelledA meeting was cancelled meeting:closedA meeting was closed
For completion events, the payload separates metadata from output :
consultation (or meeting): all the metadata, including reference, mode, patient, customFields, and integration.
result: the generated artifacts only.
Completing a consultation
When a consultation completes you must call finish() to acknowledge receipt. Optionally pass a status to report whether your processing succeeded.
client . on ( 'consultation:completed' , async ({ result , finish }) => {
try {
await processConsultation ( result );
await finish ({ status: 'success' }); // default if no parameter is provided
} catch ( error ) {
await finish ({ status: 'failed' }); // TORTUS shows retry options to the user
}
});
Status Meaning { status: 'success' }Processing succeeded. Default when no parameter is given. { status: 'failed' }Processing failed; the user can retry from the TORTUS UI.
Calling finish() with no arguments is equivalent to finish({ status: 'success' }):
await finish (); // these are
await finish ({ status: 'success' }); // equivalent
Reading artifacts
result.artifacts holds the structured medical output. Each artifact type is an array , so always filter by contentType rather than relying on array order.
const { medicalCodes , medicalNotes , letters , transcriptions } = result . artifacts ;
const htmlNote = medicalNotes . find (( n ) => n . contentType === 'text/html' );
const markdownNote = medicalNotes . find (( n ) => n . contentType === 'text/markdown' );
const letter = letters . find (( l ) => l . contentType === 'text/html' );
const transcription = transcriptions . find (( t ) => t . contentType === 'text/plain' );
Medical codes
medicalCodes includes diagnoses and observations. Observational codes carry a measurement, and every code carries an evidence array (which may be empty).
medicalCodes . forEach (( code ) => {
console . log ( code . code ); // e.g. 'I10' or '72313002'
console . log ( code . display ); // e.g. 'Essential hypertension'
if ( code . value && code . unit ) {
console . log ( code . value , code . unit ); // e.g. '120 mmHg'
}
if ( code . evidence . length > 0 ) {
console . log ( code . evidence ); // e.g. ['Patient reported elevated BP']
}
});
Other lifecycle events
client . on ( 'consultation:error' , ({ consultation , error }) => {
console . error ( `Consultation ${ consultation . reference } failed:` , error );
});
client . on ( 'consultation:resumed' , ({ consultation }) => {
console . log ( 'Resumed:' , consultation . reference , consultation . patient );
});
Putting it together
A complete integration that loads the client, subscribes, then starts a consultation on demand:
import { loadTortus } from '@tortus-ai/embed-client' ;
async function setupTortusClient () {
const client = await loadTortus ({
publishableKey: 'pk_live_...' ,
container: '#tortus-container' ,
environment: 'production' ,
fetchClientSecrets : async () => {
const response = await fetch ( '/api/tortus/client-secrets' );
const data = await response . json ();
return { launchToken: data . launchToken };
},
});
client . on ( 'consultation:completed' , async ({ consultation , result , finish }) => {
try {
await processConsultation ( consultation , result );
await finish ({ status: 'success' });
} catch ( error ) {
await finish ({ status: 'failed' });
}
});
client . on ( 'consultation:error' , ({ consultation , error }) => {
// show a notification, log, etc.
});
return client ;
}
const client = await setupTortusClient ();
document . getElementById ( 'start-consultation' ). addEventListener ( 'click' , async () => {
await client . consultations . start (
{ mode: 'FACE_TO_FACE' , patient: await getPatientData (), integration: { system: 'EMIS' } },
{ returnTo: 'home' }
);
});
async function processConsultation ( consultation , result ) {
const { medicalNotes , letters , medicalCodes , transcriptions } = result . artifacts ;
const note =
medicalNotes . find (( n ) => n . contentType === 'text/html' ) ??
medicalNotes . find (( n ) => n . contentType === 'text/markdown' );
const letter =
letters . find (( l ) => l . contentType === 'text/html' ) ??
letters . find (( l ) => l . contentType === 'text/markdown' );
const transcription = transcriptions . find (( t ) => t . contentType === 'text/plain' );
await saveToEhr ({
reference: consultation . reference ,
note: note ?. content ,
letter: letter ?. content ,
codes: medicalCodes ,
transcription: transcription ?. content ,
});
}
Cleanup
Unsubscribe from individual listeners, close a specific consultation, or destroy the whole client.
// Remove a single listener
const unsubscribe = client . on ( 'consultation:completed' , handleConsultationComplete );
unsubscribe ();
// Close a specific consultation
await consultation . close ();
// Destroy the entire client (cleans up all resources)
await client . destroy ();
destroy() cleans up all resources but won’t remove historical data saved in the browser.