FHIR Subscriptions: Building Real-Time Event-Driven Healthcare Apps

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 Subscription Architecture - Sequence Diagram showing workflow from Healthcare App through FHIR Server, Webhook Endpoint, to Azure Service Bus
FHIR Subscription Architecture: Real-time event notification workflow

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
        });
    }
}
⚠️
WEBHOOK SECURITY CONSIDERATIONS
  • 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
💡
SUBSCRIPTION BEST PRACTICES
  • 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

💡
OPERATIONAL BEST PRACTICES
  • 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 awareness

Standards and References

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.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.