AI-powered clinical documentation workflows built on n8n
Generate structured SOAP notes from text or voice β automatically.
This project provides three production-ready n8n workflows for automated clinical note generation. A clinician or intake system sends raw clinical input β as typed text or a voice recording β and receives a fully structured SOAP (Subjective, Objective, Assessment, Plan) note in return, conforming to the master clinical progress note template.
| Workflow | File | Input | Output |
|---|---|---|---|
| TTT | clinicalNote(TTT).json |
Raw clinical text | SOAP note JSON |
| STT | clinicalNote(STT).json |
Audio recording | SOAP note JSON |
| TTS | clinicalNote(TTS).json |
Raw clinical text | SOAP note + spoken audio |
All workflows produce output conforming to the following clinical progress note structure:
Clinical Progress Note
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Patient Information
βββ Patient Name β {{patient_name}}
βββ Date of Birth β {{patient_dob}}
βββ Patient ID β {{patient_id}}
βββ Date of Visit β {{visit_date}}
βββ Practitioner β {{practitioner}}, {{practitioner_credentials}}
βββ Chief Complaint β {{chief_complaint}}
S β Subjective (Patient's own words & reported history)
βββ HPI Narrative β {{hpi_narrative}}
βββ Reported Symptoms β {{symptoms_reported}}
βββ Progress Since Last β {{progress_since_last}}
βββ Compliance with Plan β {{compliance_notes}}
O β Objective (Measurable, observable clinical data)
βββ Vital Signs
β βββ BP β {{bp}}
β βββ Heart Rate β {{heart_rate}}
β βββ Temperature β {{temperature}}
β βββ O2 Saturation β {{o2_saturation}}
βββ Physical Exam β {{physical_exam_findings}}
βββ Labs Reviewed β {{lab_results}}
βββ Imaging β {{imaging_results}}
A β Assessment (Clinician's diagnosis & interpretation)
βββ Primary Diagnosis (ICD-10) β {{primary_diagnosis}} ({{icd_10_code}})
βββ Problem List β {{problem_list}}
βββ Clinical Rationale β {{assessment_summary}}
P β Plan (Course of action)
βββ Medication Changes β {{medication_plan}}
βββ Interventions β {{intervention_details}}
βββ Follow-up Instructions β {{follow_up_plan}}
βββ Referrals β {{referrals}}
HTTP Request (Text or Audio)
β
βΌ
βββββββββββββββ
β Webhook β β n8n entry point
ββββββββ¬βββββββ
β
βββββββ΄βββββββ
βΌ βΌ
βββββββββββ ββββββββββββ [STT only]
β Verify β β ElevenLabsβ βββ Audio transcription
βPractit. β β Scribe β
ββββββ¬βββββ βββββββ¬ββββββ
βΌ β
βββββββββββ β
β Verify β β
β Patient β β
ββββββ¬βββββ β
ββββββββ¬βββββββ
βΌ
βββββββββββββββ
β codeParse β β Normalize patient + practitioner + clinical input
ββββββββ¬βββββββ
βΌ
βββββββββββββββ
β Gemma 3 4B β β Local LLM via Ollama
β (LLM Chain) β
ββββββββ¬βββββββ
βΌ
βββββββββββββββ
β Clean JSON β β Strip markdown, parse output
ββββββββ¬βββββββ
βΌ
ββββββββββββββββββββββββββββ
β Response β
β JSON (TTT / STT) β
β audio/mpeg (TTS) β
ββββββββββββββββββββββββββββ
Accepts raw clinical text β returns SOAP note as JSON
Endpoint
POST /webhook/clinical-note/start
Content-Type: application/json
Request Body
{
"provider_email": "provider@clinic.com",
"patient_email": "patient@example.com",
"appointment_id": "APPT-XXXX",
"mode": "text",
"text_input": "<raw clinical notes from the encounter>",
"metadata": {
"source": "patient",
"language": "en-US"
}
}Example Clinical Input
34-year-old male with 4 days of fever, cough, sore throat, congestion,
headache, chills, and fatigue. Cough initially dry, now mildly productive.
Denies chest pain, shortness of breath, nausea, vomiting, or diarrhea.
Partial relief with paracetamol. Vitals: Temp 37.8 C, BP 118/74, HR 88,
RR 16, SpO2 98% on room air. Mild pharyngeal erythema. Lungs clear.
COVID and rapid strep negative.
Response
{
"status": "success",
"code": 200,
"message": "Clinical SOAP note generated successfully.",
"data": {
"soap_note": {
"patient_name": "{{patient_name}}",
"patient_dob": "{{patient_dob}}",
"patient_id": "{{patient_id}}",
"visit_date": "{{visit_date}}",
"practitioner": "{{practitioner_name}}",
"practitioner_credentials": "MD",
"chief_compliant": "Patient presents with cough, congestion, and subjective fever.",
"subjective": {
"hpi_narrative": "Patient reports 4 days of fever, cough, sore throat, congestion, headache, chills, and fatigue...",
"symptoms_reported": "Fever, cough, sore throat, congestion, headache, chills, fatigue. Denies chest pain, shortness of breath.",
"progress_since_last": "NA",
"compliance_notes": "NA"
},
"objective": {
"bp": "118/74",
"heart_rate": "88",
"temperature": "37.8 C",
"o2_saturation": "98 percent on room air",
"physical_exam_findings": "Mild pharyngeal erythema. Lungs clear bilaterally. Heart rhythm normal. Abdomen soft, non-tender.",
"lab_results": "COVID negative. Rapid strep negative.",
"imaging_results": "None"
},
"assessment": {
"primary_diagnosis": "Upper Respiratory Infection",
"icd_10_code": "J06.9",
"problem_list": "Upper Respiratory Infection",
"assessment_summary": "Presentation consistent with viral upper respiratory infection based on symptom duration, negative rapid tests, and clinical findings."
},
"plan": {
"medication_plan": "Acetaminophen 325mg every 6 hours as needed. Saline nasal spray for congestion.",
"intervention_details": "Patient educated on hydration and rest.",
"follow_up_plan": "Return if symptoms worsen or do not improve within 72 hours.",
"referrals": "None"
}
}
},
"timestamp": "2025-12-16T00:00:00.000Z"
}Accepts a clinical audio recording β transcribes it β returns SOAP note as JSON
Endpoint
POST /webhook/clinical-note-audio
Content-Type: multipart/form-data
Request Fields
| Field | Type | Description |
|---|---|---|
data |
File | Audio recording of the clinical encounter (.wav, .mp3, etc.) |
practitioner_email |
String | Practitioner's registered email |
patient_email |
String | Patient's registered email |
cURL Example
curl -X POST http://localhost:5678/webhook/clinical-note-audio \
-F "data=@recording.wav" \
-F "practitioner_email=provider@clinic.com" \
-F "patient_email=patient@example.com"Response β Same SOAP note JSON structure as TTT.
Audio is transcribed using ElevenLabs Scribe v1 before being processed by the LLM.
Accepts raw clinical text β generates SOAP note β returns spoken summary as audio
Endpoint
POST /webhook/clinical-note-tts
Content-Type: application/json
Request Body β Same structure as TTT.
Response
HTTP 200
Content-Type: audio/mpeg
Content-Disposition: inline; filename="clinical_note.mp3"
[binary audio stream]
The spoken summary includes:
- Chief Complaint
- History of Present Illness
- Assessment Summary
- Follow-up Plan
| Node | Type | Purpose |
|---|---|---|
clinicalNote |
Webhook | Entry point β receives the HTTP request |
verifyPractitioner |
PostgreSQL | Looks up practitioner FHIR resource by email |
verifyPatient |
PostgreSQL | Looks up patient FHIR resource by email |
codeParse |
Code | Normalizes patient, practitioner & clinical text into one payload |
promptChain |
LLM Chain | Sends structured prompt to Gemma 3 via Ollama |
cleanOutputJSON |
Code | Strips LLM markdown, returns parsed JSON |
respondSuccess |
Respond to Webhook | Returns final JSON response |
| Node | Type | Purpose |
|---|---|---|
speech2Text |
HTTP Request | Sends audio to ElevenLabs Scribe v1 for transcription |
extractTranscription |
Code | Extracts text field from transcription response |
merge |
Merge | Joins verified patient/practitioner data with transcription |
| Node | Type | Purpose |
|---|---|---|
cleanOutputJSON |
Code | Extracts HPI, assessment & plan for the spoken script |
text2Speech |
HTTP Request | Sends script to ElevenLabs TTS API, receives audio |
respondSucessAudio |
Respond to Webhook | Returns binary audio response |
Patient and practitioner data is stored as FHIR R4 JSON resources in PostgreSQL:
CREATE TABLE practitioner (
resource JSONB
);
CREATE TABLE patient (
resource JSONB
);Lookup queries used internally:
-- Find practitioner by email
SELECT resource FROM practitioner
WHERE resource @> jsonb_build_object(
'telecom', jsonb_build_array(
jsonb_build_object('system', 'email', 'value', $1)
)
) LIMIT 1;
-- Find patient by email
SELECT resource FROM patient
WHERE resource->>'email' = $1 LIMIT 1;- n8n (self-hosted)
- Ollama with
gemma3:4bpulled - PostgreSQL with FHIR-structured patient/practitioner data
- ElevenLabs account (for STT and TTS workflows)
1. Import workflows into n8n
Go to Settings β Import Workflow and import each .json file.
2. Configure credentials in n8n
| Credential | Where used | Notes |
|---|---|---|
| PostgreSQL | verifyPractitioner, verifyPatient |
Your Cloud SQL or local Postgres |
| Ollama API | gemma3 node |
Default: http://localhost:11434 |
| ElevenLabs API Key | speech2Text, text2Speech |
Set as xi-api-key header |
3. (Optional) Google Cloud SQL β use the Auth Proxy
If your PostgreSQL is hosted on Google Cloud SQL, run the proxy locally before starting n8n:
.\cloud-sql-proxy.exe YOUR_PROJECT:YOUR_REGION:YOUR_INSTANCE --port=5432Then set Postgres host to 127.0.0.1 in your n8n credential.
4. Activate workflows and test
Use the example requests above or import into Postman/Insomnia.
The workflow JSON files contain the following placeholders. Replace them with your actual values inside n8n:
| Placeholder | Replace with |
|---|---|
YOUR_ELEVENLABS_API_KEY |
Your ElevenLabs API key |
YOUR_POSTGRES_CREDENTIAL_ID |
Your n8n Postgres credential ID |
YOUR_OLLAMA_CREDENTIAL_ID |
Your n8n Ollama credential ID |
YOUR_N8N_INSTANCE_ID |
Your n8n instance ID |
β οΈ Never commit real API keys or credentials to version control.
This project is for educational and portfolio purposes.