AutomationFlowsData & Sheets › Hipaa-compliant Healthcare Data Synchronization

Hipaa-compliant Healthcare Data Synchronization

HIPAA-Compliant Healthcare Data Synchronization. Uses httpRequest, postgres, mongoDb, elasticsearch. Scheduled trigger; 19 nodes.

Cron / scheduled trigger★★★★☆ complexity19 nodesHTTP RequestPostgresMongoDBElasticsearchSyslogEmail Send
Data & Sheets Trigger: Cron / scheduled Nodes: 19 Complexity: ★★★★☆ Added:

This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "HIPAA-Compliant Healthcare Data Synchronization",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "name": "Schedule Sync",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// Initialize audit log\nconst auditLog = {\n  syncId: crypto.randomUUID(),\n  timestamp: new Date().toISOString(),\n  environment: process.env.NODE_ENV || 'production',\n  syncType: 'scheduled',\n  complianceChecks: {\n    encryption: true,\n    authentication: true,\n    authorization: true,\n    dataMinimization: true\n  },\n  piiAccessed: [],\n  startTime: Date.now()\n};\n\n// Store audit log in execution data\nglobal.currentAudit = auditLog;\n\nreturn [{ json: { auditLog } }];"
      },
      "name": "Initialize Audit",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        450,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "fhirApi",
        "requestMethod": "GET",
        "url": "https://ehr-system.hospital.com/fhir/Patient",
        "options": {
          "headers": {
            "entries": [
              {
                "name": "X-Audit-User",
                "value": "sync-service"
              },
              {
                "name": "X-Purpose-Of-Use",
                "value": "TREATMENT"
              }
            ]
          },
          "timeout": 30000
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "_lastUpdated",
              "value": "gt{{ $now.minus(15, 'minutes').toISO() }}"
            },
            {
              "name": "_count",
              "value": "100"
            }
          ]
        }
      },
      "name": "Fetch EHR Updates",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        650,
        200
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT patient_id, last_sync_timestamp FROM patient_sync_status WHERE needs_update = true LIMIT 100"
      },
      "name": "Get Pending Syncs",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        650,
        400
      ]
    },
    {
      "parameters": {
        "functionCode": "// Parse FHIR bundle and extract patient data\nconst ehrData = $node['Fetch EHR Updates'].json;\nconst pendingSyncs = $node['Get Pending Syncs'].json;\n\nconst patients = [];\nconst errors = [];\n\n// Process FHIR bundle\nif (ehrData.resourceType === 'Bundle' && ehrData.entry) {\n  for (const entry of ehrData.entry) {\n    try {\n      const patient = entry.resource;\n      if (patient.resourceType !== 'Patient') continue;\n      \n      // De-identify data for analytics\n      const deIdentified = {\n        patientId: patient.id,\n        mrn: patient.identifier?.find(id => id.system === 'MRN')?.value,\n        demographics: {\n          birthYear: patient.birthDate ? new Date(patient.birthDate).getFullYear() : null,\n          gender: patient.gender,\n          zipCode: patient.address?.[0]?.postalCode?.substring(0, 3) + 'XX' // First 3 digits only\n        },\n        lastUpdated: patient.meta?.lastUpdated\n      };\n      \n      // Encrypt sensitive fields\n      const encrypted = {\n        ...deIdentified,\n        name: encrypt(JSON.stringify(patient.name)),\n        fullAddress: encrypt(JSON.stringify(patient.address)),\n        telecom: encrypt(JSON.stringify(patient.telecom)),\n        ssn: patient.identifier?.find(id => id.system === 'SSN') \n          ? encrypt(patient.identifier.find(id => id.system === 'SSN').value)\n          : null\n      };\n      \n      patients.push(encrypted);\n      \n      // Log PII access for audit\n      global.currentAudit.piiAccessed.push({\n        patientId: patient.id,\n        fieldsAccessed: ['name', 'address', 'telecom', 'ssn'],\n        timestamp: new Date().toISOString(),\n        purpose: 'sync'\n      });\n      \n    } catch (error) {\n      errors.push({\n        resourceId: entry.resource?.id,\n        error: error.message,\n        timestamp: new Date().toISOString()\n      });\n    }\n  }\n}\n\n// Mock encryption function\nfunction encrypt(data) {\n  return Buffer.from(data).toString('base64');\n}\n\nreturn [\n  patients.map(p => ({json: p})),\n  errors.map(e => ({json: e}))\n];"
      },
      "name": "Process & Encrypt",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        850,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $items().length }}",
              "operation": "larger",
              "value2": 0
            }
          ]
        }
      },
      "name": "Has Patients?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1050,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $items().length }}",
              "operation": "larger",
              "value2": 0
            }
          ]
        }
      },
      "name": "Has Errors?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1050,
        400
      ]
    },
    {
      "parameters": {
        "functionCode": "// Validate data integrity before syncing\nconst validPatients = [];\nconst validationErrors = [];\n\nfor (const item of items) {\n  const patient = item.json;\n  const validationResult = {\n    patientId: patient.patientId,\n    checks: {\n      hasRequiredFields: !!(patient.patientId && patient.mrn),\n      encryptionValid: !!(patient.name && patient.fullAddress && patient.telecom),\n      dataIntegrity: validateChecksum(patient),\n      consentOnFile: await checkConsent(patient.patientId)\n    }\n  };\n  \n  const allChecksPassed = Object.values(validationResult.checks).every(v => v);\n  \n  if (allChecksPassed) {\n    validPatients.push(patient);\n  } else {\n    validationErrors.push({\n      ...patient,\n      validationResult,\n      action: 'quarantine'\n    });\n  }\n}\n\n// Mock functions\nfunction validateChecksum(data) {\n  // In production, would calculate and verify checksum\n  return true;\n}\n\nasync function checkConsent(patientId) {\n  // In production, would check consent database\n  return true;\n}\n\nreturn [\n  validPatients.map(p => ({json: p})),\n  validationErrors.map(e => ({json: e}))\n];"
      },
      "name": "Validate Integrity",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1250,
        200
      ]
    },
    {
      "parameters": {
        "operation": "upsert",
        "table": "patient_analytics",
        "columns": "patient_id,mrn,demographics,last_updated,encrypted_pii",
        "updateKey": "patient_id",
        "options": {
          "queryBatching": "transaction"
        }
      },
      "name": "Sync to Analytics DB",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1450,
        100
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": "={{ $json.patientId }}",
        "collection": "patients",
        "updateFields": "={{ JSON.stringify($json) }}",
        "options": {
          "upsert": true
        }
      },
      "name": "Sync to NoSQL",
      "type": "n8n-nodes-base.mongoDb",
      "typeVersion": 1,
      "position": [
        1450,
        200
      ]
    },
    {
      "parameters": {
        "operation": "create",
        "index": "patient-search",
        "documentId": "={{ $json.patientId }}",
        "body": "={{ JSON.stringify({...($json), indexed_at: new Date().toISOString()}) }}"
      },
      "name": "Update Search Index",
      "type": "n8n-nodes-base.elasticsearch",
      "typeVersion": 1,
      "position": [
        1450,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO data_quarantine (patient_id, data, reason, timestamp) VALUES ($1, $2, $3, $4)",
        "options": {
          "queryParams": "={{ $json.patientId }},={{ JSON.stringify($json) }},={{ JSON.stringify($json.validationResult) }},={{ new Date().toISOString() }}"
        }
      },
      "name": "Quarantine Invalid",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1450,
        400
      ]
    },
    {
      "parameters": {
        "level": "error",
        "message": "Healthcare sync error",
        "fields": {
          "item": [
            {
              "name": "syncId",
              "value": "={{ global.currentAudit.syncId }}"
            },
            {
              "name": "errorCount",
              "value": "={{ $items().length }}"
            },
            {
              "name": "errors",
              "value": "={{ JSON.stringify($items().map(i => i.json)) }}"
            }
          ]
        }
      },
      "name": "Log Sync Errors",
      "type": "n8n-nodes-base.syslog",
      "typeVersion": 1,
      "position": [
        1250,
        500
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE patient_sync_status SET last_sync_timestamp = NOW(), sync_status = $1 WHERE patient_id = ANY($2)",
        "options": {
          "queryParams": "success,={{ $items().map(i => i.json.patientId) }}"
        }
      },
      "name": "Update Sync Status",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1650,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "// Generate compliance report\nconst audit = global.currentAudit;\naudit.endTime = Date.now();\naudit.duration = audit.endTime - audit.startTime;\n\nconst successCount = $node['Has Patients?'].json?.length || 0;\nconst errorCount = $node['Has Errors?'].json?.length || 0;\nconst quarantineCount = $node['Quarantine Invalid'].json?.length || 0;\n\nconst report = {\n  syncId: audit.syncId,\n  timestamp: audit.timestamp,\n  summary: {\n    totalProcessed: successCount + errorCount + quarantineCount,\n    successful: successCount,\n    errors: errorCount,\n    quarantined: quarantineCount,\n    duration: audit.duration\n  },\n  compliance: {\n    ...audit.complianceChecks,\n    piiAccessLogged: audit.piiAccessed.length > 0,\n    encryptionUsed: true,\n    auditTrailComplete: true\n  },\n  dataAccess: {\n    patientsAccessed: audit.piiAccessed.length,\n    fieldsAccessed: ['name', 'address', 'telecom', 'ssn'],\n    purpose: 'TREATMENT',\n    retention: '7 years'\n  },\n  nextSync: new Date(Date.now() + 900000).toISOString() // 15 minutes\n};\n\nreturn [{ json: report }];"
      },
      "name": "Generate Compliance Report",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1850,
        300
      ]
    },
    {
      "parameters": {
        "operation": "create",
        "table": "hipaa_audit_log",
        "columns": "sync_id,timestamp,summary,compliance,data_access,user_id,purpose"
      },
      "name": "Store Audit Log",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        2050,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.summary.errors }}",
              "operation": "larger",
              "value2": 5
            }
          ],
          "boolean": [
            {
              "value1": "={{ $json.compliance.auditTrailComplete }}",
              "value2": false
            }
          ]
        },
        "combineOperation": "any"
      },
      "name": "Alert Needed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2250,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "pagerDutyApi",
        "requestMethod": "POST",
        "url": "https://events.pagerduty.com/v2/enqueue",
        "jsonParameters": true,
        "options": {},
        "bodyParametersJson": "{\n  \"routing_key\": \"healthcare-sync-service\",\n  \"event_action\": \"trigger\",\n  \"payload\": {\n    \"summary\": \"Healthcare sync issues detected\",\n    \"severity\": \"error\",\n    \"source\": \"n8n-sync\",\n    \"custom_details\": {{ JSON.stringify($json) }}\n  }\n}"
      },
      "name": "Page On-Call",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        2450,
        200
      ]
    },
    {
      "parameters": {
        "fromEmail": "compliance@hospital.com",
        "toEmail": "compliance-team@hospital.com",
        "subject": "Healthcare Data Sync Report - {{ $json.syncId }}",
        "html": "<h2>HIPAA Compliance Report</h2>\n<p>Sync ID: {{ $json.syncId }}</p>\n<p>Timestamp: {{ $json.timestamp }}</p>\n<h3>Summary</h3>\n<ul>\n  <li>Total Processed: {{ $json.summary.totalProcessed }}</li>\n  <li>Successful: {{ $json.summary.successful }}</li>\n  <li>Errors: {{ $json.summary.errors }}</li>\n  <li>Quarantined: {{ $json.summary.quarantined }}</li>\n  <li>Duration: {{ $json.summary.duration }}ms</li>\n</ul>\n<h3>Compliance Status</h3>\n<p>All compliance checks: {{ $json.compliance }}</p>\n<h3>Next Sync</h3>\n<p>{{ $json.nextSync }}</p>",
        "options": {
          "allowUnauthorizedCerts": false
        }
      },
      "name": "Email Compliance Report",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2,
      "position": [
        2250,
        400
      ]
    }
  ],
  "connections": {
    "Schedule Sync": {
      "main": [
        [
          {
            "node": "Initialize Audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize Audit": {
      "main": [
        [
          {
            "node": "Fetch EHR Updates",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Pending Syncs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch EHR Updates": {
      "main": [
        [
          {
            "node": "Process & Encrypt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Pending Syncs": {
      "main": [
        [
          {
            "node": "Process & Encrypt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process & Encrypt": {
      "main": [
        [
          {
            "node": "Has Patients?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has Errors?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Patients?": {
      "main": [
        [
          {
            "node": "Validate Integrity",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Has Errors?": {
      "main": [
        [
          {
            "node": "Log Sync Errors",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Validate Integrity": {
      "main": [
        [
          {
            "node": "Sync to Analytics DB",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sync to NoSQL",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update Search Index",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Quarantine Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sync to Analytics DB": {
      "main": [
        [
          {
            "node": "Update Sync Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sync to NoSQL": {
      "main": [
        [
          {
            "node": "Update Sync Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Search Index": {
      "main": [
        [
          {
            "node": "Update Sync Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Sync Status": {
      "main": [
        [
          {
            "node": "Generate Compliance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Sync Errors": {
      "main": [
        [
          {
            "node": "Generate Compliance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quarantine Invalid": {
      "main": [
        [
          {
            "node": "Generate Compliance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Compliance Report": {
      "main": [
        [
          {
            "node": "Store Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Audit Log": {
      "main": [
        [
          {
            "node": "Alert Needed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert Needed?": {
      "main": [
        [
          {
            "node": "Page On-Call",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Compliance Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Compliance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

HIPAA-Compliant Healthcare Data Synchronization. Uses httpRequest, postgres, mongoDb, elasticsearch. Scheduled trigger; 19 nodes.

Source: https://github.com/pvdyck/n8n-test-framework/blob/dabf65c294eba226a14b439ef672584f48315473/examples/workflows/healthcare-data-sync.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Disparador 1.8. Uses itemLists, postgres, emailSend, httpRequest. Scheduled trigger; 85 nodes.

Item Lists, Postgres, Email Send +1
Data & Sheets

This n8n workflow runs daily to analyze active customer behavior, engineers relevant features from usage and transaction data, applies a machine learning or AI-based model to predict churn probability

HTTP Request, Postgres, Email Send
Data & Sheets

This n8n workflow automates continuous compliance monitoring across IT, OT, and cloud environments by aggregating security controls, validating policies (ISO 27001, NIST, GDPR, SOC2), detecting anomal

HTTP Request, Postgres, Email Send
Data & Sheets

Automates real-time market monitoring, technical analysis, AI-powered signal generation for cryptocurrencies (and stocks), filters high-confidence trades, and delivers actionable alerts via multiple c

HTTP Request, Postgres, Email Send
Data & Sheets

Daily Churn Retention Actions - V9. Uses postgres, httpRequest, emailSend. Scheduled trigger; 13 nodes.

Postgres, HTTP Request, Email Send