HIPAA

View as MarkdownOpen in Claude

The Health Insurance Portability and Accountability Act (HIPAA) establishes requirements for protecting Protected Health Information (PHI). As a telecommunications technology company, SignalWire is built to support HIPAA-compliant applications: SignalWire APIs and WebRTC communications services are all encrypted by default via HTTPS, TLS, and/or SRTP/DTLS. When building applications — especially voice AI agents defined with SWML or the Server SDK — that handle healthcare data, you must implement appropriate safeguards to protect PHI throughout the call lifecycle.

This guide provides technical guidance for implementing security controls relevant to HIPAA compliance. It is not legal advice. Healthcare organizations must consult with qualified healthcare compliance experts and legal counsel to ensure their specific implementation meets all applicable HIPAA requirements. Compliance is a shared responsibility between SignalWire, your organization, and your implementation choices.

Overview

HIPAA applies to covered entities and business associates. Individuals, organizations, and agencies that meet the definition of a covered entity must comply with the Rules’ requirements to protect the privacy and security of health information. If a covered entity engages a business associate to help carry out its health care activities, the covered entity must have a written business associate contract that establishes what the business associate has been engaged to do and requires it to comply with the Rules’ requirements to protect PHI.

SignalWire is built to support HIPAA-compliant applications. No PII (Personally Identifiable Information), private resource logs, or other records are publicly accessible — you must have specific access granted by a Space Admin to see logs in the portal, and you must have API credentials to use the API. PII and PHI that may be contained in resources can also be deleted from the logs (message bodies, fax media, message media, and so on). Recordings must be manually enabled, and they can be deleted from the Space altogether or paused during the collection of sensitive data.

What is PHI in voice AI?

In the context of voice AI agents, PHI may include:

  • Patient names, addresses, phone numbers
  • Medical record numbers and account numbers
  • Appointment details and scheduling information
  • Prescription information
  • Lab results and diagnoses
  • Insurance information
  • Any health-related information combined with patient identifiers

Shared responsibility model

LayerResponsibility
SignalWire PlatformInfrastructure security, encryption in transit, secure data centers
Your ApplicationAccess controls, audit logging, PHI handling, prompt design, SWAIG webhook security
Your OrganizationPolicies, training, BAA management, incident response

Business Associate Agreement (BAA)

SignalWire is a Business Associate and offers Business Associate Agreements for healthcare customers. You must have a signed BAA with SignalWire before processing any PHI through the platform.

If you need a BAA signed for HIPAA compliance, reach out to sales@signalwire.com to get started.

Prerequisites

Before deploying a HIPAA-compliant agent, ensure the following are in place.

Infrastructure requirements

RequirementDetails
HTTPSAll endpoints — including your SWAIG and SWML webhooks — must use TLS 1.2 or higher
AuthenticationHTTP Basic Auth required on all webhooks
HostingHIPAA-compliant hosting environment for any service that receives PHI
NetworkFirewall rules limiting access to necessary IPs

HIPAA-compliant providers

When building HIPAA-compliant agents, you must use voice and LLM providers that support BAA coverage. In SWML, voice is selected per language with the voice property of ai.languages, and the model is selected with ai_model in ai.params. The Server SDK exposes the same settings through addLanguage and setParams.

HIPAA-compliant voice providers:

ProviderVoice value
Rimerime.spore
Googlegcloud.en-US-Neural2-F
Amazonpolly.Joanna
Azureazure.en-US-JennyNeural

HIPAA-compliant LLM:

ModelConfiguration
gpt-oss-120bai_model: gpt-oss-120b

Configure the model and a HIPAA-compliant voice as follows:

1class HIPAACompliantAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="hipaa-agent")
4
5 # Use HIPAA-compliant voice provider
6 self.add_language("English", "en-US", "rime.spore")
7
8 # Use HIPAA-compliant LLM
9 self.set_params({"ai_model": "gpt-oss-120b"})

Personnel requirements

  • Designated HIPAA Privacy Officer
  • Designated HIPAA Security Officer
  • Workforce training on PHI handling
  • Documented policies and procedures

Access control

When you invite a new user to your SignalWire Space, the admin can specify the projects that they should be allowed to view, limiting accessibility to resource logs to only those who have been granted permission. The API also requires each request to be authenticated with a Space URL, Project ID, and most importantly, an API Token.

We highly recommend that each application that programmatically accesses SignalWire uses its own API tokens, so that you can easily see when these tokens have been used and remove them when necessary. Additionally, take care to make sure only those who need access to a project have it.

The principle of least privilege. The principle of least privilege (POLP) limits users’ access rights to only what is strictly required to do their jobs. Implementing this is a huge help in HIPAA compliance, as well as a solid business practice.

Authentication configuration

Configure strong authentication on the agent’s own endpoints. The Server SDK reads HTTP Basic Auth credentials from environment variables:

$# .env file - use strong, unique credentials
$SWML_BASIC_AUTH_USER=healthcareagent
$SWML_BASIC_AUTH_PASSWORD=your-secure-password-minimum-32-characters
$
$# Never use default or weak passwords in production

If SWML_BASIC_AUTH_PASSWORD is not set, the SDK auto-generates a password on each startup; in production, always set it explicitly.

For sensitive functions, enable token security: mark any PHI-accessing tool secure so its function callback also requires a validated token. Any SWAIG function (SWML reference, Server SDK guide) that accesses PHI calls out to a web_hook_url you host — in SWML, protect it with HTTP Basic Auth (SWAIG.defaults.web_hook_url as username:password@url) and serve it over HTTPS.

1from signalwire import AgentBase
2from signalwire.core.function_result import FunctionResult
3
4class HealthcareAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="healthcare-agent")
7
8 # Mark PHI-accessing functions as secure
9 # This adds cryptographic token validation
10 self.define_tool(
11 name="get_patient_info",
12 description="Retrieve patient information by ID",
13 parameters={
14 "type": "object",
15 "properties": {
16 "patient_id": {
17 "type": "string",
18 "description": "Patient identifier"
19 }
20 },
21 "required": ["patient_id"]
22 },
23 handler=self.get_patient_info,
24 secure=True # Enables token validation
25 )
26
27 def get_patient_info(self, args, raw_data):
28 # Validate and sanitize input
29 patient_id = args.get("patient_id", "").strip()
30 if not self.validate_patient_id(patient_id):
31 return FunctionResult("Invalid patient identifier.")
32
33 # Fetch from secure backend
34 # ... implementation
35 return FunctionResult("Retrieved patient information.")

TLS/HTTPS configuration

Always use HTTPS in production. Configure TLS via environment variables:

$# Direct TLS (if not using a reverse proxy)
$SWML_SSL_ENABLED=true
$SWML_SSL_CERT_PATH=/etc/ssl/certs/healthcare-agent.crt
$SWML_SSL_KEY_PATH=/etc/ssl/private/healthcare-agent.key
$SWML_SSL_VERIFY_MODE=CERT_REQUIRED
$
$# Enable HSTS
$SWML_USE_HSTS=true
$SWML_HSTS_MAX_AGE=31536000

For production deployments behind a reverse proxy (nginx, Caddy) that terminates SSL, set the public base URL instead:

$SWML_PROXY_URL_BASE=https://healthcare.example.com

Audit logging

HIPAA requires audit trails for PHI access. Write the audit entry in whichever handler backs your function — the tool handler (Server SDK) or your SWAIG web_hook_url handler (SWML) — and redact sensitive fields so that PHI never appears in plaintext logs:

1import logging
2from datetime import datetime
3from signalwire import AgentBase
4from signalwire.core.function_result import FunctionResult
5
6# Configure structured logging
7logging.basicConfig(
8 level=logging.INFO,
9 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10)
11audit_logger = logging.getLogger('hipaa_audit')
12
13class HIPAACompliantAgent(AgentBase):
14 def __init__(self):
15 super().__init__(name="hipaa-agent")
16 self.add_language("English", "en-US", "rime.spore")
17
18 def log_phi_access(self, action, user_id, resource_type, success, details=None):
19 """Log PHI access for HIPAA audit trail."""
20 audit_logger.info(
21 "PHI_ACCESS",
22 extra={
23 "timestamp": datetime.utcnow().isoformat(),
24 "action": action,
25 "user_id": user_id,
26 "resource_type": resource_type,
27 "success": success,
28 "details": self.sanitize_for_log(details)
29 }
30 )
31
32 def sanitize_for_log(self, data):
33 """Remove PHI from log entries."""
34 if data is None:
35 return None
36
37 # Fields to redact
38 sensitive_fields = [
39 'ssn', 'social_security',
40 'dob', 'date_of_birth',
41 'address', 'phone',
42 'medical_record', 'mrn',
43 'diagnosis', 'prescription'
44 ]
45
46 sanitized = dict(data) if isinstance(data, dict) else {"value": "[REDACTED]"}
47 for field in sensitive_fields:
48 if field in sanitized:
49 sanitized[field] = "[REDACTED]"
50
51 return sanitized

Required audit log events:

EventRequired fields
Call StartTimestamp, call_id, caller_id (hashed), agent_name
PHI AccessTimestamp, action, resource_type, user_id, success/failure
Function CallTimestamp, function_name, sanitized_args, result_status
AuthenticationTimestamp, auth_type, success/failure, source_ip
Call EndTimestamp, call_id, duration, disposition

Call recording compliance

HIPAA and state laws require consent for call recording. Put the disclosure instruction directly in the AI prompt so it is spoken before any PHI discussion:

1class HealthcareAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="healthcare-agent")
4 self.add_language("English", "en-US", "rime.spore")
5
6 # Add recording disclosure to prompt
7 self.prompt_add_section(
8 "Recording Disclosure",
9 body="""At the start of every call, you MUST inform the caller:
10
11 "This call may be recorded for quality assurance and compliance purposes.
12 Your continued participation indicates your consent to recording.
13 If you do not wish to be recorded, please let me know now."
14
15 Wait for acknowledgment before proceeding with any PHI discussion."""
16 )

Alternatively, you can deliver the disclosure as a static_greeting with static_greeting_no_barge set to true, which forces the entire message to play before the caller can interrupt.

Pausing recording for sensitive data

Pause recording when collecting highly sensitive information. A SWAIG function pauses and resumes the recording — in SWML by returning a SWML action that runs stop_record_call (and later record_call), or in the Server SDK by chaining .stop_record_call() / .record_call() on the FunctionResult:

1def collect_sensitive_info(self, args, raw_data):
2 """Collect sensitive info with recording paused."""
3 return (
4 FunctionResult(
5 "I'll need to collect some sensitive information. "
6 "Recording will be paused during this process."
7 )
8 .stop_record_call(control_id="main_recording")
9 # Recording is now paused
10 # Collect sensitive data via subsequent function calls
11 )
12
13def resume_after_sensitive(self, args, raw_data):
14 """Resume recording after sensitive collection."""
15 return (
16 FunctionResult("Thank you. Resuming our conversation.")
17 .record_call(control_id="main_recording", stereo=True)
18 )

Recording retention

Call recordings containing PHI are considered part of the medical record. HIPAA does not specify a retention period for PHI itself — retention requirements come from state medical records laws, other federal regulations (for example, Medicare conditions of participation), and organizational policies.

The HIPAA six-year retention requirement (45 CFR 164.530(j)) applies to administrative documentation — policies, procedures, training records, risk assessments, and audit logs — not to PHI or call recordings.

ConsiderationRecommendation
Retention PeriodFollow state medical records laws and organizational policy (commonly 7-10 years for adults; longer for minors)
StorageEncrypted at rest, access-controlled
DestructionSecure deletion with audit trail when retention period expires
Access LoggingLog all recording access

Manage recordings through the REST API — list them to audit access, and delete them once their retention period expires.

PHI handling best practices

Keeping PHI out of the LLM with global_data and meta_data

One of the most effective ways to protect PHI is to keep it away from the LLM entirely. Use global_data or meta_data to store sensitive information while keeping it out of the model’s context.

  • ai.global_data — a key-value object that persists for the whole AI session. It is available to your prompts, AI params, and the SWML returned by SWAIG functions, and is updated at runtime with the set_global_data action (or update_global_data on a FunctionResult). As long as you never template a value into the prompt or a spoken response, it stays out of the model’s context.
  • meta_data — an environmental variable scoped locally to a function (and its web_hook_url), updated with the set_meta_data action (see the set_meta_data guide). Use it for per-function context that no other function — and never the model’s conversation — should see.
Data locationLLM sees itYour function handler can use it
Prompt / conversation / sayYesYes
Function arguments and responsesYesYes
global_data (not templated into the prompt)NoYes
meta_dataNoYes (locally to the function)

A verification function stores the resolved patient record in global_data and returns only a generic confirmation, so the identifiers your other functions rely on are never exposed to the model:

1class CompartmentalizedHealthcareAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="compartmentalized-healthcare")
4 self.add_language("English", "en-US", "rime.spore")
5
6 def verify_patient(self, args, raw_data):
7 """Verify patient and store PHI in global_data, not in AI context."""
8 dob = args.get("date_of_birth")
9 ssn_last4 = args.get("ssn_last4")
10
11 # Fetch full patient record from database
12 patient = self.lookup_patient(dob, ssn_last4)
13
14 if patient:
15 # Store sensitive data in global_data - LLM cannot see this
16 # Only your SWAIG functions can access it
17 return (
18 FunctionResult("I've verified your identity. How can I help?")
19 .update_global_data({
20 "patient_id": patient["id"],
21 "mrn": patient["medical_record_number"],
22 "full_name": patient["full_name"],
23 "ssn": patient["ssn"],
24 "insurance_id": patient["insurance_id"],
25 "verified": True
26 })
27 )
28
29 return FunctionResult("I couldn't verify your identity.")
30
31 def schedule_appointment(self, args, raw_data):
32 """Use stored PHI without exposing it to the LLM."""
33 # Retrieve PHI from global_data - never sent to LLM
34 global_data = raw_data.get("global_data", {})
35
36 if not global_data.get("verified"):
37 return FunctionResult("Please verify your identity first.")
38
39 # Use the stored patient_id and MRN for scheduling
40 patient_id = global_data.get("patient_id")
41 mrn = global_data.get("mrn")
42
43 # Schedule using real identifiers, but LLM only knows
44 # "the verified patient" - not the actual PHI
45 appointment = self.create_appointment(patient_id, args.get("date"))
46
47 # Return confirmation without exposing PHI to AI
48 return FunctionResult(
49 f"Your appointment is confirmed for {args.get('date')}. "
50 "You'll receive a confirmation at the contact information on file."
51 )

A later function — for example, scheduling an appointment — reads ${global_data.patient_id} and ${global_data.mrn} on the server side and confirms the booking without ever speaking the identifiers back.

Collecting PHI via IVR (bypassing the LLM)

For maximum PHI protection, collect sensitive values with the prompt verb (DTMF or speech) instead of through conversation. Input gathered this way is placed in the prompt_value variable for your SWML and webhooks — it never enters the model’s context or the transcript, and with recording paused it never reaches the recording either:

1from signalwire import AgentBase, FunctionResult
2
3agent = AgentBase(name="healthcare-agent", route="/agent")
4
5@agent.tool(name="collect_dob", description="Collect date of birth via DTMF, bypassing the LLM")
6def collect_dob(args, raw_data=None):
7 # Run the SWML `prompt` verb to collect digits. The input lands in ${prompt_value}
8 # and never enters the model's context, the transcript, or (with recording paused)
9 # the recording.
10 return (
11 FunctionResult()
12 .execute_swml({
13 "version": "1.0.0",
14 "sections": {
15 "main": [
16 {"stop_record_call": {"control_id": "main_recording"}},
17 {"prompt": {
18 "play": "say:Please enter your 8-digit date of birth. For example, 0 3 1 5 1 9 8 5 for March 15, 1985.",
19 "max_digits": 8,
20 "digit_timeout": 15,
21 }},
22 {"record_call": {"control_id": "main_recording", "stereo": True}},
23 ]
24 },
25 })
26 .set_response(
27 "Thank the caller for providing their information and continue verifying "
28 "their identity. Never repeat sensitive values back to the caller."
29 )
30 )
31
32agent.run()

This approach provides several layers of protection:

Collection methodLLM sees dataTranscript containsRecording contains
Normal conversationYesYesYes
DTMF via promptNoNoTones only
Speech via promptNoNoYes (pause recording to exclude)
global_data / meta_dataNoNoNo

Combined pattern: maximum PHI protection

For the strongest protection, combine paused recording with DTMF collection of every sensitive field in a single flow, then resume recording and hand control back to the AI with only a generic confirmation:

1version: 1.0.0
2sections:
3 main:
4 # Pause recording for the entire sensitive collection
5 - stop_record_call:
6 control_id: main_recording
7 # Collect date of birth via DTMF - bypasses the LLM
8 - prompt:
9 play: "say:Please enter your 8-digit date of birth. For example, 0 3 1 5 1 9 8 5 for March 15, 1985."
10 max_digits: 8
11 digit_timeout: 15
12 - set:
13 dob: "${prompt_value}"
14 # Collect last 4 of SSN via DTMF - bypasses the LLM
15 - prompt:
16 play: "say:Please enter the last 4 digits of your Social Security Number."
17 max_digits: 4
18 digit_timeout: 10
19 - set:
20 ssn_last4: "${prompt_value}"
21 # Resume recording, then return to the AI with a generic confirmation
22 - record_call:
23 control_id: main_recording
24 stereo: true
25 - ai:
26 prompt:
27 text: |
28 Thank the caller for providing their information and confirm you are now
29 verifying their identity. Never repeat the collected values back.

Prompt design for PHI protection

Design prompts to minimize PHI exposure. Whether you write a single text prompt or a structured POM, encode the protection and verification rules explicitly. In the Server SDK, promptAddSection builds the POM for you:

1class SecureHealthcareAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="secure-healthcare")
4 self.add_language("English", "en-US", "rime.spore")
5
6 self.prompt_add_section(
7 "PHI Protection Rules",
8 body="""CRITICAL: Follow these rules for Protected Health Information:
9
10 1. NEVER repeat back sensitive information like:
11 - Social Security Numbers
12 - Full date of birth
13 - Medical record numbers
14 - Detailed diagnosis information
15
16 2. When confirming patient identity, use partial information:
17 - "I see your date of birth ends in [last 2 digits of year]"
18 - "Your record shows an address on [street name only]"
19
20 3. NEVER include PHI in error messages or clarifications
21
22 4. If asked to repeat sensitive info, say:
23 "For security purposes, I cannot repeat that information."
24
25 5. Verify caller identity before ANY PHI access using
26 established verification procedures."""
27 )
28
29 self.prompt_add_section(
30 "Identity Verification",
31 body="""Before accessing ANY patient information:
32
33 1. Collect patient identifier (MRN or account number)
34 2. Verify with TWO of the following:
35 - Date of birth
36 - Last 4 of SSN
37 - Address on file
38 - Phone number on file
39
40 Only proceed after successful verification."""
41 )

Secure function design

Design SWAIG functions to return the minimum necessary PHI. The handler should verify the caller is authorized, log the access, and respond with only what the caller needs to hear — never provider names or detailed locations. On an authorization failure, return a non-revealing message:

1def check_appointment(self, args, raw_data):
2 """Check appointment - returns minimal PHI."""
3 patient_id = args.get("patient_id")
4
5 # Verify caller is authorized
6 if not self.verify_caller_authorization(raw_data, patient_id):
7 self.log_phi_access("appointment_check", patient_id, "appointment", False)
8 return FunctionResult(
9 "I was unable to verify your identity. "
10 "Please contact us directly for assistance."
11 )
12
13 # Log the access
14 self.log_phi_access("appointment_check", patient_id, "appointment", True)
15
16 # Return minimal information
17 appointment = self.get_next_appointment(patient_id)
18 if appointment:
19 # Don't include provider names or detailed location
20 return FunctionResult(
21 f"Your next appointment is on {appointment['date']} "
22 f"at {appointment['time']}. "
23 f"Please arrive 15 minutes early."
24 )
25
26 return FunctionResult("You have no upcoming appointments scheduled.")

Input validation

Validate all inputs to prevent injection and ensure data integrity. Declare expected inputs and constraints in the function parameters (SWML reference, Server SDK guide) so the model can only supply well-formed values, then re-validate in the handler:

1import re
2
3def validate_patient_id(self, patient_id):
4 """Validate patient ID format."""
5 # Only allow alphanumeric, specific length
6 if not patient_id:
7 return False
8 if not re.match(r'^[A-Z0-9]{8,12}$', patient_id):
9 return False
10 return True
11
12def validate_date_of_birth(self, dob):
13 """Validate DOB format."""
14 try:
15 # Expected format: MM/DD/YYYY
16 if not re.match(r'^\d{2}/\d{2}/\d{4}$', dob):
17 return False
18 # Additional validation...
19 return True
20 except Exception:
21 return False

Redacting stored message media

To remove PHI from message logs after the fact, redact the body of a previously sent message through the REST API. Redaction clears the message body for compliance or privacy while keeping a record that the message was sent; the original body is overwritten and cannot be recovered. Only messages in a terminal state (delivered, undelivered, failed) are eligible.

Deployment

The PHI-handling parts of an agent live in the services you host: the endpoint that serves the SWML (or the Server SDK app) and the web_hook_url endpoints that back your SWAIG functions. Secure those services regardless of how you deploy them.

Serverless deployment

For AWS Lambda, Google Cloud Functions, or Azure Functions hosting your agent or SWAIG webhooks:

ConsiderationImplementation
Secrets ManagementUse AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault for SWAIG credentials
VPC ConfigurationDeploy in a VPC with no public internet access where possible
IAM PoliciesLeast-privilege access to resources
LoggingCloudWatch/Cloud Logging with encryption
Cold StartsMay affect audit log timing; account for this

The Server SDK serves serverless platforms through handle_serverless_request, with a mode for each provider:

1# handler.py - Serverless with HIPAA considerations
2import os
3from signalwire import AgentBase
4
5class HIPAAServerlessAgent(AgentBase):
6 def __init__(self):
7 super().__init__(name="hipaa-serverless")
8 self.add_language("English", "en-US", "rime.spore")
9
10 # Ensure auth is configured
11 if not os.environ.get("SWML_BASIC_AUTH_PASSWORD"):
12 raise ValueError("SWML_BASIC_AUTH_PASSWORD must be set")
13
14agent = HIPAAServerlessAgent()
15
16def lambda_handler(event, context):
17 """AWS Lambda entry point."""
18 return agent.handle_serverless_request(event, context, mode="lambda")

Self-hosted deployment

Self-hosted security checklist:

  • Deploy in HIPAA-compliant data center or cloud region
  • Enable full-disk encryption on all servers
  • Configure host-based firewall
  • Implement intrusion detection
  • Enable comprehensive system logging
  • Regular security patching schedule
  • Network segmentation from other workloads

A container deployment that keeps the agent off the public internet and terminates TLS at a reverse proxy:

1# docker-compose.yml for a HIPAA-compliant deployment
2version: '3.8'
3
4services:
5 healthcare-agent:
6 build: .
7 environment:
8 - SWML_BASIC_AUTH_USER=${SWML_BASIC_AUTH_USER}
9 - SWML_BASIC_AUTH_PASSWORD=${SWML_BASIC_AUTH_PASSWORD}
10 - SWML_PROXY_URL_BASE=${SWML_PROXY_URL_BASE}
11 - SIGNALWIRE_LOG_LEVEL=info
12 networks:
13 - internal
14 deploy:
15 resources:
16 limits:
17 memory: 512M
18
19 nginx:
20 image: nginx:alpine
21 ports:
22 - "443:443"
23 volumes:
24 - ./nginx.conf:/etc/nginx/nginx.conf:ro
25 - ./certs:/etc/ssl/certs:ro
26 depends_on:
27 - healthcare-agent
28 networks:
29 - internal
30 - external
31
32networks:
33 internal:
34 internal: true # No external access
35 external:

Administrative safeguards

Access management

1# Example: Role-based function access
2class RoleBasedHealthcareAgent(AgentBase):
3
4 # Define role permissions
5 ROLE_PERMISSIONS = {
6 "patient": ["check_appointment", "request_prescription_refill"],
7 "staff": ["check_appointment", "schedule_appointment", "view_patient_summary"],
8 "provider": ["check_appointment", "schedule_appointment", "view_patient_summary",
9 "view_medical_record", "add_clinical_note"]
10 }
11
12 def __init__(self):
13 super().__init__(name="role-based-healthcare")
14 self.caller_role = None
15
16 def verify_permission(self, function_name):
17 """Check if current caller role has permission."""
18 if not self.caller_role:
19 return False
20 allowed = self.ROLE_PERMISSIONS.get(self.caller_role, [])
21 return function_name in allowed

Incident response

For a security incident, have your handlers refuse PHI access and return a safe fallback when an emergency flag is set. Check an AGENT_EMERGENCY_MODE flag and short-circuit before any PHI access:

1import os
2
3class HealthcareAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="healthcare-agent")
6 self.emergency_mode = os.environ.get("AGENT_EMERGENCY_MODE", "").lower() == "true"
7
8 def access_phi(self, args, raw_data):
9 """PHI access with emergency mode check."""
10 if self.emergency_mode:
11 audit_logger.warning("PHI_ACCESS_BLOCKED_EMERGENCY_MODE")
12 return FunctionResult(
13 "This service is temporarily unavailable. "
14 "Please call our main line for assistance."
15 )
16
17 # Normal PHI access logic...

Incident response requirements:

RequirementDetails
Breach NotificationNotify HHS under the HHS Breach Notification Rule — within 60 days for breaches affecting 500+ individuals; smaller breaches in the annual report
Patient NotificationNotify affected individuals without unreasonable delay, no later than 60 days after discovery
DocumentationDocument all incidents and response actions
Root Cause AnalysisInvestigate and remediate vulnerabilities

Compliance checklist

Pre-deployment

  • Signed BAA with SignalWire
  • HIPAA-compliant hosting environment selected
  • Strong authentication credentials configured
  • TLS/HTTPS properly configured
  • Audit logging implemented and tested
  • PHI handling procedures documented
  • Workforce training completed
  • Incident response plan documented
  • Recording consent disclosures implemented
  • Data retention policies defined

Ongoing compliance

  • Regular access reviews (quarterly recommended)
  • Audit log reviews (monthly recommended)
  • Security patch management
  • Annual risk assessment
  • Annual HIPAA training refresh
  • BAA review and updates as needed
  • Penetration testing (annual recommended)
  • Business continuity testing

Documentation requirements

Maintain documentation for:

DocumentRetention
Policies and Procedures6 years from creation or last effective date
Risk Assessments6 years
Training Records6 years from training date
BAAs6 years from termination
Audit Logs6 years
Incident Reports6 years

Complete example

Complete examples of a HIPAA-compliant healthcare appointment agent in the Server SDKs and SWML are presented in the tabs below.

1#!/usr/bin/env python3
2"""HIPAA-compliant healthcare appointment agent."""
3
4import os
5import logging
6from datetime import datetime
7from signalwire import AgentBase
8from signalwire.core.function_result import FunctionResult
9
10# Audit logging setup
11audit_logger = logging.getLogger('hipaa_audit')
12audit_logger.setLevel(logging.INFO)
13
14class HIPAAHealthcareAgent(AgentBase):
15 """Healthcare agent with HIPAA compliance controls."""
16
17 def __init__(self):
18 super().__init__(name="hipaa-healthcare-agent")
19
20 # Use HIPAA-compliant voice and LLM providers
21 self.add_language("English", "en-US", "rime.spore")
22 self.set_params({"ai_model": "gpt-oss-120b"})
23
24 # Check required security configuration
25 if not os.environ.get("SWML_BASIC_AUTH_PASSWORD"):
26 raise ValueError("SWML_BASIC_AUTH_PASSWORD required for HIPAA compliance")
27
28 self.emergency_mode = os.environ.get("AGENT_EMERGENCY_MODE", "").lower() == "true"
29 self.verified_caller = False
30
31 self._configure_prompts()
32 self._setup_tools()
33
34 def _configure_prompts(self):
35 """Configure HIPAA-compliant prompts."""
36 self.prompt_add_section(
37 "Role",
38 body="You are a healthcare appointment assistant for Example Medical Center."
39 )
40
41 self.prompt_add_section(
42 "Recording Disclosure",
43 body="""At the START of every call, say:
44
45 "Thank you for calling Example Medical Center. This call may be recorded
46 for quality and compliance purposes. How may I help you today?"
47 """
48 )
49
50 self.prompt_add_section(
51 "PHI Protection",
52 body="""CRITICAL RULES:
53 1. Verify caller identity before accessing ANY patient information
54 2. Never repeat back full SSN, DOB, or medical record numbers
55 3. Use partial confirmation only ("ending in...", "on file as...")
56 4. Never include PHI in error messages
57 5. If unsure about authorization, do not provide information
58 """
59 )
60
61 self.prompt_add_section(
62 "Verification Process",
63 body="""Before accessing patient information:
64 1. Ask for patient's date of birth
65 2. Ask for last 4 digits of SSN OR phone number on file
66 3. Use verify_patient function to confirm
67 4. Only proceed if verification succeeds
68 """
69 )
70
71 def _setup_tools(self):
72 """Configure secure tools."""
73 self.define_tool(
74 name="verify_patient",
75 description="Verify patient identity before accessing records",
76 parameters={
77 "type": "object",
78 "properties": {
79 "date_of_birth": {
80 "type": "string",
81 "description": "Patient date of birth (MM/DD/YYYY)"
82 },
83 "verification_value": {
84 "type": "string",
85 "description": "Last 4 of SSN or phone number"
86 }
87 },
88 "required": ["date_of_birth", "verification_value"]
89 },
90 handler=self.verify_patient,
91 secure=True
92 )
93
94 self.define_tool(
95 name="check_appointments",
96 description="Check patient appointments (requires prior verification)",
97 parameters={
98 "type": "object",
99 "properties": {}
100 },
101 handler=self.check_appointments,
102 secure=True
103 )
104
105 def _log_audit(self, action, success, details=None):
106 """Log HIPAA audit event."""
107 audit_logger.info(
108 f"HIPAA_AUDIT|{datetime.utcnow().isoformat()}|{action}|"
109 f"{'SUCCESS' if success else 'FAILURE'}|{details or ''}"
110 )
111
112 def verify_patient(self, args, raw_data):
113 """Verify patient identity."""
114 if self.emergency_mode:
115 self._log_audit("verify_patient", False, "emergency_mode")
116 return FunctionResult(
117 "Our system is temporarily unavailable. Please call back later."
118 )
119
120 dob = args.get("date_of_birth", "")
121 verification = args.get("verification_value", "")
122
123 # In production, verify against your patient database
124 # This is a placeholder for the actual verification logic
125 verified = self._verify_against_database(dob, verification)
126
127 if verified:
128 self.verified_caller = True
129 self._log_audit("verify_patient", True, "identity_confirmed")
130 return FunctionResult(
131 "Thank you, I've verified your identity. How can I help you today?"
132 )
133 else:
134 self._log_audit("verify_patient", False, "verification_failed")
135 return FunctionResult(
136 "I wasn't able to verify your identity with the information provided. "
137 "Please contact our office directly for assistance."
138 )
139
140 def check_appointments(self, args, raw_data):
141 """Check appointments - requires prior verification."""
142 if not self.verified_caller:
143 self._log_audit("check_appointments", False, "not_verified")
144 return FunctionResult(
145 "I need to verify your identity first. "
146 "Can you provide your date of birth?"
147 )
148
149 if self.emergency_mode:
150 self._log_audit("check_appointments", False, "emergency_mode")
151 return FunctionResult(
152 "Our scheduling system is temporarily unavailable."
153 )
154
155 # Fetch appointments from your system
156 # Return minimal necessary information
157 self._log_audit("check_appointments", True, "appointments_retrieved")
158 return FunctionResult(
159 "Your next appointment is scheduled for Monday at 2:30 PM "
160 "with the medical team. Please arrive 15 minutes early."
161 )
162
163 def _verify_against_database(self, dob, verification):
164 """Verify patient against database. Implement your logic here."""
165 # Placeholder - implement actual database verification
166 return True
167
168if __name__ == "__main__":
169 agent = HIPAAHealthcareAgent()
170 agent.run()

Resources

SignalWire resources:

HIPAA resources:

Summary

Building HIPAA-compliant communications and voice AI applications requires attention to:

  1. Technical controls — authenticated, HTTPS SWAIG webhooks; encryption; audit logging in your handlers.
  2. Administrative controls — policies, training, role-based access management.
  3. PHI handling — minimization, identity verification, and keeping PHI out of the model with global_data, meta_data, and DTMF collection.
  4. Documentation — policies, audit trails, incident records.

SignalWire is built to support HIPAA-compliant applications, and provides the building blocks — AI prompts and post-prompts, AI-inaccessible data stores, recording control, and secure SWAIG functions — needed for compliance. Your responsibility is to configure these features appropriately, secure the webhooks that handle PHI, and maintain the administrative safeguards required by HIPAA. Always consult with qualified healthcare compliance experts and legal counsel to ensure your specific implementation meets all applicable requirements.