This article is part of a comprehensive series on healthcare data standards and interoperability.
- HL7 v2: The Messaging Standard That Powers Healthcare IT
- Building GDPR-Compliant FHIR APIs: A European Healthcare Guide
- EMR Modernization: Migrating from Legacy HL7 v2 to FHIR
- HL7 v3: Understanding RIM and Why v3 Failed to Replace v2
- FHIR Subscriptions: Building Real-Time Event-Driven Healthcare Apps (this article)
- ePrescribing in EU and Ireland: FHIR-Based Electronic Prescriptions
- FHIR Integration Best Practices: Lessons from Production
- FHIR API Security Part 1: Foundation & Authentication
- FHIR API Security Part 2: Implementation & Best Practices
- Real-Time Healthcare Data Pipelines: Kafka + FHIR for Clinical Decisions
- Building Interoperable Healthcare Data Systems for AI
- Case Study: Building a Modern FHIR Patient Timeline Explorer
Executive Summary
FHIR Subscriptions enable real-time event notifications, allowing applications to receive instant alerts when specific FHIR resources change—new lab results, medication orders, bed assignments, or critical vitals. Without subscriptions, applications must constantly poll the FHIR server (inefficient, slow, expensive).
This guide covers FHIR R4/R5 Subscription resources, webhook implementation patterns, Azure Service Bus integration, and production-ready .NET code for building event-driven healthcare applications.
What You’ll Learn:
- FHIR Subscription resource structure (R4 vs R5)
- Webhook vs WebSocket vs Messaging channels
- Creating subscriptions via REST API
- Implementing webhook receivers (.NET)
- Security patterns (HMAC signatures, OAuth2)
- Hospital use cases (ICU monitoring, lab alerts, OR status)
- Azure Service Bus integration
- Error handling and retry logic
Tech Stack: FHIR R4/R5 | .NET 10 | ASP.NET Web API | Azure Service Bus | Webhooks
The Polling Problem
Without Subscriptions (Polling)
Scenario: ICU monitoring dashboard needs to show critical lab results within 30 seconds.
Polling Approach:
// Poll every 30 seconds
while (true)
{
var newResults = await fhirClient.SearchAsync<Observation>(
new SearchParams()
.Where("patient=Patient/12345")
.Where("category=laboratory")
.Where($"date=gt{lastCheck}")
);
ProcessResults(newResults);
await Task.Delay(30000); // 30 seconds
}
Problems:
- ❌ 100 patients = 100 polls every 30s = 200 requests/minute
- ❌ 99% return no new data (wasted bandwidth)
- ❌ Server load scales with number of clients
- ❌ 30-second delay (not truly real-time)
- ❌ No notification if offline (missed events)
With Subscriptions (Push)
Event-Driven Approach:
// One-time setup
var subscription = new Subscription
{
Criteria = "Observation?category=laboratory",
Channel = new Subscription.ChannelComponent
{
Type = Subscription.SubscriptionChannelType.RestHook,
Endpoint = "https://myapp.ie/fhir/webhook"
}
};
await fhirClient.CreateAsync(subscription);
// Server pushes to webhook when new lab result created
// No polling needed!
Benefits:
✅ Real-time (< 1 second notification)
✅ Minimal server load (only changed resources)
✅ Scales to thousands of subscribers
✅ Event history (replay missed events)
Understanding FHIR Subscriptions
Traditional polling-based integrations are inefficient and introduce latency. FHIR Subscriptions provide a publish-subscribe mechanism where the FHIR server notifies clients when specific events occur, enabling real-time clinical workflows.FHIR R4 vs R5 Subscriptions
FHIR R5 introduces a completely redesigned subscription framework called “Topic-Based Subscriptions” that addresses limitations in R4.| Feature | FHIR R4 | FHIR R5 |
|---|---|---|
| Subscription Model | Criteria-based (FHIRPath) | Topic-based (SubscriptionTopic) |
| Channels | rest-hook, websocket, email, message | rest-hook, websocket, email, message + custom |
| Payload | Full resource or empty | empty, id-only, full-resource |
| Filters | Limited criteria string | Rich filter parameters |
| Status Tracking | Basic status field | SubscriptionStatus resource |
| Backport to R4 | N/A | Available via extensions |
FHIR Subscription Architecture
FHIR R4 Subscription Implementation
In FHIR R4, subscriptions use a criteria-based approach where you specify which resources to monitor using search parameters.Creating an R4 Subscription
{
"resourceType": "Subscription",
"id": "patient-admit-subscription",
"status": "requested",
"reason": "Monitor patient admissions for real-time alerts",
"criteria": "Encounter?status=in-progress&class=inpatient",
"channel": {
"type": "rest-hook",
"endpoint": "https://myapp.healthcare.com/fhir/webhook",
"payload": "application/fhir+json",
"header": [
"Authorization: Bearer ${AUTH_TOKEN}",
"X-Subscription-Id: patient-admit-subscription"
]
},
"end": "2025-12-31T23:59:59Z"
}
.NET FHIR Subscription Client
using Hl7.Fhir.Model;
using Hl7.Fhir.Rest;
public class FhirSubscriptionService
{
private readonly FhirClient _client;
private readonly ILogger<FhirSubscriptionService> _logger;
public FhirSubscriptionService(string fhirServerUrl, ILogger<FhirSubscriptionService> logger)
{
_client = new FhirClient(fhirServerUrl);
_logger = logger;
}
public async Task<Subscription> CreatePatientAdmitSubscriptionAsync(string webhookUrl)
{
var subscription = new Subscription
{
Status = Subscription.SubscriptionStatus.Requested,
Reason = "Monitor patient admissions",
Criteria = "Encounter?status=in-progress&class=inpatient",
Channel = new Subscription.ChannelComponent
{
Type = Subscription.SubscriptionChannelType.RestHook,
Endpoint = webhookUrl,
Payload = "application/fhir+json",
Header = new List<string>
{
$"Authorization: Bearer {GetAuthToken()}",
"Content-Type: application/fhir+json"
}
},
End = DateTimeOffset.UtcNow.AddYears(1).ToString("o")
};
var created = await _client.CreateAsync(subscription);
_logger.LogInformation("Subscription created: {Id}", created.Id);
return created;
}
public async Task<bool> DeleteSubscriptionAsync(string subscriptionId)
{
try
{
await _client.DeleteAsync($"Subscription/{subscriptionId}");
return true;
}
catch (FhirOperationException ex)
{
_logger.LogError(ex, "Failed to delete subscription {Id}", subscriptionId);
return false;
}
}
}
ASP.NET Core Webhook Handler
[ApiController]
[Route("fhir/webhook")]
public class FhirWebhookController : ControllerBase
{
private readonly ILogger<FhirWebhookController> _logger;
private readonly IEventProcessor _eventProcessor;
private readonly FhirJsonParser _parser = new();
public FhirWebhookController(
ILogger<FhirWebhookController> logger,
IEventProcessor eventProcessor)
{
_logger = logger;
_eventProcessor = eventProcessor;
}
[HttpPost]
public async Task<IActionResult> HandleNotification()
{
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
// Handle handshake (empty body)
if (string.IsNullOrWhiteSpace(body))
{
_logger.LogInformation("Subscription handshake received");
return Ok();
}
try
{
var bundle = _parser.Parse<Bundle>(body);
foreach (var entry in bundle.Entry)
{
if (entry.Resource is Encounter encounter)
{
await ProcessEncounterEventAsync(encounter);
}
else if (entry.Resource is Patient patient)
{
await ProcessPatientEventAsync(patient);
}
}
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process FHIR notification");
return StatusCode(500);
}
}
private async Task ProcessEncounterEventAsync(Encounter encounter)
{
_logger.LogInformation(
"Encounter event: {Id}, Status: {Status}, Class: {Class}",
encounter.Id,
encounter.Status,
encounter.Class?.Code);
await _eventProcessor.PublishAsync(new PatientAdmitEvent
{
EncounterId = encounter.Id,
PatientReference = encounter.Subject?.Reference,
AdmitTime = DateTimeOffset.UtcNow
});
}
}
- Validate the source – Verify requests come from your FHIR server
- Use HTTPS only – Never accept webhooks over HTTP
- Implement signature verification – Use HMAC or JWT validation
- Idempotency – Handle duplicate notifications gracefully
- Quick responses – Return 200 immediately, process async
FHIR R5 Topic-Based Subscriptions
FHIR R5 introduces SubscriptionTopic resources that define what events can trigger notifications, providing better governance and discoverability.SubscriptionTopic Definition
{
"resourceType": "SubscriptionTopic",
"id": "patient-admission",
"url": "http://example.org/fhir/SubscriptionTopic/patient-admission",
"title": "Patient Admission Events",
"status": "active",
"description": "Notification when a patient is admitted",
"resourceTrigger": [
{
"description": "Trigger on Encounter creation or update to in-progress",
"resource": "Encounter",
"supportedInteraction": ["create", "update"],
"queryCriteria": {
"previous": "status:not=in-progress",
"resultForCreate": "test-passes",
"current": "status=in-progress&class=inpatient",
"resultForDelete": "test-fails",
"requireBoth": true
}
}
],
"canFilterBy": [
{
"description": "Filter by patient",
"resource": "Encounter",
"filterParameter": "patient"
},
{
"description": "Filter by location",
"resource": "Encounter",
"filterParameter": "location"
}
],
"notificationShape": [
{
"resource": "Encounter",
"include": ["Encounter:patient", "Encounter:location"]
}
]
}
R5 Subscription with Filters
{
"resourceType": "Subscription",
"id": "icu-admissions",
"status": "requested",
"topic": "http://example.org/fhir/SubscriptionTopic/patient-admission",
"reason": "Monitor ICU admissions only",
"filterBy": [
{
"filterParameter": "location",
"value": "Location/icu-ward"
}
],
"channelType": {
"system": "http://terminology.hl7.org/CodeSystem/subscription-channel-type",
"code": "rest-hook"
},
"endpoint": "https://alerts.hospital.com/fhir/icu-webhook",
"contentType": "application/fhir+json",
"content": "full-resource",
"maxCount": 10
}
Common Subscription Patterns
| Use Case | Criteria (R4) | Description |
|---|---|---|
| Patient Admission | Encounter?status=in-progress&class=inpatient |
Alert when patient admitted |
| Lab Results Ready | DiagnosticReport?status=final |
Notify when lab results finalized |
| Critical Values | Observation?category=vital-signs&interpretation=critical |
Immediate alert for critical vitals |
| Medication Orders | MedicationRequest?status=active |
New medication prescribed |
| Appointment Changes | Appointment?status=cancelled,noshow |
Track appointment cancellations |
| Care Plan Updates | CarePlan?status=active |
Monitor care plan changes |
- Set expiration dates – Always include an end date to prevent orphaned subscriptions
- Use specific criteria – Avoid broad subscriptions that generate excessive notifications
- Implement retry logic – Handle temporary failures gracefully
- Monitor subscription health – Track delivery success/failure rates
- Consider payload size – Use id-only for high-volume subscriptions
- Handle backpressure – Queue notifications if processing is slow
Production Architecture with Azure
graph TB
subgraph "FHIR Server"
FS[Azure API for FHIR]
end
subgraph "Event Processing"
EG[Event Grid]
SB[Service Bus]
AF[Azure Functions]
end
subgraph "Applications"
WA[Clinical Dashboard]
MA[Mobile Alert App]
AN[Analytics Pipeline]
end
FS --> EG
EG --> SB
SB --> AF
AF --> WA
AF --> MA
AF --> AN
style FS fill:#E3F2FD,stroke:#2196F3
style EG fill:#E8F5E9,stroke:#4CAF50
style SB fill:#FFF3E0,stroke:#FF9800
style AF fill:#F3E5F5,stroke:#9C27B0
Subscription Channel Types
1. REST Hook (Webhook)
Most Common Pattern
{
"resourceType": "Subscription",
"status": "active",
"criteria": "Observation?code=http://loinc.org|2339-0",
"channel": {
"type": "rest-hook",
"endpoint": "https://myapp.ie/fhir/notifications",
"payload": "application/fhir+json",
"header": [
"Authorization: Bearer eyJhbGc..."
]
}
}
Pros: ✅ Simple, standard HTTP, works with firewalls
Cons: ❌ Requires public endpoint, webhook security needed
2. WebSocket
Real-Time Streaming
{
"channel": {
"type": "websocket",
"endpoint": "wss://fhir.server/ws/subscription/123"
}
}
Pros: ✅ True bidirectional, no polling, low latency
Cons: ❌ Complex, firewall issues, connection management
3. Message (Service Bus)
Enterprise Integration
{
"channel": {
"type": "message",
"endpoint": "Endpoint=sb://myfhir.servicebus.windows.net/;...",
"payload": "application/fhir+json"
}
}
Pros: ✅ Reliable, scalable, built-in retry, dead-letter queue
Cons: ❌ Azure/vendor-specific, additional infrastructure
4. Email (Alerts)
Simple Notifications
{
"channel": {
"type": "email",
"endpoint": "mailto:oncall@hospital.ie",
"payload": "text/plain"
}
}
Pros: ✅ No infrastructure, human-readable
Cons: ❌ Not programmatic, spam filters, delays
Hospital Use Cases
1. ICU Critical Lab Results
Scenario: Alert when troponin level indicates heart attack
var subscription = new Subscription
{
Status = Subscription.SubscriptionStatus.Active,
Criteria = "Observation?code=http://loinc.org|10839-9&value=gt0.04",
Channel = new Subscription.ChannelComponent
{
Type = Subscription.SubscriptionChannelType.RestHook,
Endpoint = "https://icu-dashboard.hospital.ie/alerts/cardiac"
}
};
LOINC 10839-9 = Troponin I (cardiac marker)
Value >0.04 = Possible myocardial infarction
2. Operating Room Status
Real-time OR board updates:
var subscription = new Subscription
{
Criteria = "Encounter?class=IMP&location=Location/OR-5",
Channel = new Subscription.ChannelComponent
{
Type = Subscription.SubscriptionChannelType.WebSocket,
Endpoint = "wss://or-board.hospital.ie/ws"
}
};
Updates:
- Patient enters OR
- Procedure starts
- Anesthesia administered
- Procedure complete
- Patient to recovery
3. Medication Dispensing
Pharmacy workflow:
var subscription = new Subscription
{
Criteria = "MedicationRequest?status=active&priority=urgent",
Channel = new Subscription.ChannelComponent
{
Type = Subscription.SubscriptionChannelType.Message,
Endpoint = "Endpoint=sb://hospital.servicebus.windows.net/;..."
}
};
Pharmacy receives:
- New urgent medication orders
- Modifications to existing orders
- Cancellations
No polling needed!
Why FHIR Subscriptions Matter in Healthcare
Healthcare workflows are inherently time-sensitive. A critical lab result, an unexpected vital sign change, or a patient admission requires immediate attention. Traditional integration patterns—where systems poll for updates every few minutes—introduce dangerous latency that can impact patient outcomes. FHIR Subscriptions fundamentally change this paradigm by enabling push-based, real-time notifications that keep clinicians informed the moment something happens. Consider the difference: with polling-based integration, a critical potassium level might sit unnoticed for up to 15 minutes (typical polling interval). With FHIR Subscriptions, the alert reaches the care team within seconds of the lab system finalizing the result. This difference can be life-saving in acute care settings.Advantages of FHIR Subscriptions
| Advantage | Description | Clinical Impact |
|---|---|---|
| Real-Time Notifications | Events delivered within seconds of occurrence | Faster clinical response, improved patient safety |
| Reduced Server Load | Eliminates constant polling requests | Better system performance, lower infrastructure costs |
| Standards-Based | HL7 FHIR standard, vendor-neutral | Interoperability across EHR vendors |
| Flexible Filtering | Subscribe only to relevant events | Reduces alert fatigue, focused notifications |
| Audit Trail | Subscription resources are trackable | Compliance documentation, debugging |
| Scalable Architecture | Decouple producers from consumers | Enterprise-scale deployments possible |
Challenges and Limitations
| Challenge | Description | Mitigation Strategy |
|---|---|---|
| Delivery Guarantees | Webhooks can fail; at-least-once delivery common | Implement idempotency, dead-letter queues |
| Ordering Not Guaranteed | Events may arrive out of order | Use resource version, timestamp comparisons |
| Vendor Support Varies | Not all EHRs support subscriptions equally | Verify capabilities before architecture decisions |
| Security Surface | Webhooks expose endpoints publicly | Signature verification, IP allowlists, mTLS |
| Backpressure Handling | High-volume events can overwhelm consumers | Message queues, rate limiting, auto-scaling |
| R4 Limitations | Less flexible than R5 topic-based approach | Consider R5 backport IG for advanced needs |
Adoption Roadmap
Implementing FHIR Subscriptions requires careful planning. This roadmap provides a phased approach to adoption, from pilot to enterprise-scale deployment.
graph LR
%% Global Styles
classDef phase fill:#f9f9f9,stroke:#333,stroke-width:2px;
classDef p1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1;
classDef p2 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;
classDef p3 fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#e65100;
classDef p4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c;
%% Added
breaks to the title below to push the boxes down
subgraph Roadmap [FHIR Subscriptions Implementation Path
]
direction LR
P1(Phase 1: Assessment & Planning
4-6 Weeks
• Vendor Capability Check
• Use Case Prioritization
• Security Review InfoSec
• Infra Planning Webhooks/Queues
• HIPAA Compliance Mapping):::p1
P2(Phase 2: Pilot Implementation
6-8 Weeks
• Non-Prod Dev Setup
• Single Use Case e.g., Lab Results
• End-to-End Testing
• Failure Mode Testing Retry/Dead-letter
• Establish Performance Baseline):::p2
P3(Phase 3: Production Rollout
4-6 Weeks
• Security Hardening mTLS/Signatures
• Observability Logs/Metrics
• Gradual Rollout Subset of users
• Runbook Creation
• Ops Team Training):::p3
P4(Phase 4: Scale & Optimize
Ongoing
• Expand Use Cases
• Tuning Batch sizes/Parallelism
• R5 Topic-Based Migration
• Self-Service Portal Implementation):::p4
end
%% Connections
P1 --> P2
P2 --> P3
P3 --> P4
Governance and Operational Excellence
Enterprise-scale FHIR Subscriptions require robust governance to prevent subscription sprawl, security incidents, and operational issues.Subscription Governance Framework
| Governance Area | Policy | Enforcement |
|---|---|---|
| Subscription Approval | All subscriptions require business justification and security review | Approval workflow before creation |
| Expiration Policy | Maximum subscription lifetime of 1 year; renewal required | Automated expiration, renewal reminders |
| Endpoint Requirements | HTTPS only, must be in approved domain list | Validation at subscription creation |
| Criteria Review | No overly broad criteria (must be resource-specific) | Manual review for high-volume patterns |
| Owner Assignment | Every subscription must have designated owner | Tag/extension for owner contact info |
| Audit Requirements | All subscription CRUD operations logged | FHIR AuditEvent resources |
Maintenance and Monitoring
- Health Dashboards – Monitor subscription status, delivery success rates, latency
- Alerting – Notify on: failed deliveries, subscription expirations, error rate spikes
- Capacity Planning – Track notification volume trends, plan scaling
- Periodic Reviews – Quarterly review of active subscriptions for relevance
- Cleanup Automation – Remove orphaned subscriptions (failed endpoints, inactive)
- Documentation – Maintain registry of all subscriptions with purpose and owner
- Disaster Recovery – Plan for subscription recreation in DR scenarios
- Version Management – Track FHIR version compatibility as servers upgrade
Key Metrics to Track
| Metric | Target | Alert Threshold |
|---|---|---|
| Delivery Success Rate | > 99.9% | < 99% for 5 minutes |
| End-to-End Latency (P95) | < 5 seconds | > 30 seconds |
| Active Subscriptions | Per capacity plan | > 80% of limit |
| Failed Endpoint Rate | 0% | Any persistent failure |
| Retry Queue Depth | < 100 | > 1000 for 10 minutes |
Key Takeaways
- ✅ FHIR Subscriptions enable real-time healthcare – Move from polling to push-based architecture
- ✅ R5 Topic-Based is the future – Better governance, richer filtering, backport available
- ✅ Secure your webhooks – HTTPS, authentication, signature verification
- ✅ Design for failure – Retries, idempotency, dead-letter queues
- ✅ Start with specific criteria – Avoid notification storms from overly broad subscriptions
- ✅ Monitor subscription health – Track delivery rates and latency
Conclusion
FHIR Subscriptions transform healthcare integration from batch-oriented to real-time event-driven architecture. Whether using R4’s criteria-based approach or R5’s topic-based subscriptions, the key is designing robust, secure, and scalable notification handlers. Combined with cloud services like Azure Event Grid and Service Bus, FHIR Subscriptions enable truly reactive healthcare applications that improve patient care through immediate clinical awarenessStandards and References
FHIR Subscription Specifications
Azure Integration
Security
Irish Healthcare
Related Articles in This Series
Conclusion
FHIR Subscriptions transform healthcare applications from slow, polling-based systems to real-time, event-driven architectures. Whether building ICU monitoring dashboards, care coordination platforms, or clinical decision support systems, subscriptions provide the foundation for responsive, modern healthcare IT.
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.