Delivery Options

 

This page outlines various options that can be used to control how notification delivery works.

Payload Search Result Mode

 
This feature applies to REST HOOK and MESSAGE subscriptions only.
Every Subscription is a data exfiltration route and Payload Search Result Mode widens it as each delivery can include the results of any search. For this reason, creating or updating a Subscription requires the additional FHIR_MANAGE_SUBSCRIPTIONS permission. See Subscription Security.

By default, the REST HOOK Subscription delivery involves performing an HTTP PUT (FHIR update) operation where the payload is the individual resource that triggered the subscription (i.e. the resource will be of the type that was identified by the Subscription criteria). Similarly, MESSAGE Subscription delivery involves writing an individual resource to the target queue.

Some scenarios require the delivery of a collection of resources as a Bundle to the target server. When Payload Search Result Mode is used, the REST HOOK delivery module will perform a search using user-defined criteria and then delivers the results of the search as a FHIR Transaction operation. Similarly, the search results will be delivered to the user-specified endpoint channel as bundle resources when Payload Search Result Mode is applied in MESSAGE delivery module.

To enable Payload Search Result Mode, an extension is added to the Subscription resource with the following URL:

NameURLTypeDescription
Payload Search Criteria string The value to this extension is a string containing a FHIR search expression (i.e. a URL fragment without the base URL portion, as shown in the example below). It may contain the following substitutions:
  • ${matched_resource_id} – Will be replaced with the relative ID of the resource that matched the subscription.
  • Rest Hook Subscription

The example below shows a sample Rest Hook Subscription configured to use Payload Search Result Mode. This subscription has a criteria of Observation?, meaning it will match all Observations.

{
  "resourceType": "Subscription",
  "extension": [ {
    "url": "http://hapifhir.io/fhir/StructureDefinition/subscription-payload-search-criteria",
    "valueString": "Observation?_id=${matched_resource_id}&_include=*"
  } ],
  "status": "requested",
  "criteria": "Observation?",
  "channel": {
    "type": "rest-hook",
    "endpoint": "http://example.com/baseR4",
    "payload": "application/fhir+json"
  }
}
  • Message Subscription

The example below shows a sample Message Subscription configured to use Payload Search Result Mode. This subscription has a criteria of Observation? and an extension of "Observation?_id=${matched_resource_id}&_include=Observation:subject", meaning it will match all Observations and return a bundle that contains both the matched Observation and the subject.

{
  "resourceType": "Subscription",
  "extension": [ {
    "url": "http://hapifhir.io/fhir/StructureDefinition/subscription-payload-search-criteria",
    "valueString": "Observation?_id=${matched_resource_id}&_include=Observation:subject"
  } ],
  "status": "requested",
  "criteria": "Observation?",
  "channel": {
    "type": "message",
    "endpoint": "channel:example-queue-name",
    "payload": "application/json"
  }
}

When using this mode, a transaction bundle resembling the following example will be POSTed to the base URL of the FHIR server (ie. the endpoint configured in the Subscription resource) or the endpoint channel in case of message subscription per the FHIR transaction operation semantics.

{
  "resourceType": "Bundle",
  "id": "6a5be3a8-04d0-4c9a-b179-161fced75ab7",
  "type": "transaction",
  "entry": [ {
    "fullUrl": "Observation/3/_history/1",
    "resource": {
      "resourceType": "Observation",
      "id": "3",
      "meta": {
        "versionId": "1",
        "lastUpdated": "2020-06-29T18:08:18.729-04:00"
      },
      "subject": {
        "reference": "Patient/2"
      }
    },
    "request": {
      "method": "PUT",
      "url": "Observation/3"
    }
  }, {
    "fullUrl": "Patient/2/_history/1",
    "resource": {
      "resourceType": "Patient",
      "id": "2",
      "meta": {
        "versionId": "1",
        "lastUpdated": "2020-06-29T18:08:18.701-04:00"
      },
      "active": true
    },
    "request": {
      "method": "PUT",
      "url": "Patient/2"
    }
  } ]
}

Replication Mode

 
This feature applies to REST HOOK subscriptions only.

In replication mode, Smile CDR assumes that it is replicating all matching resources from one Smile CDR instance to another Smile CDR instance. This allows the server to make assumptions about how operations may be batched and sequenced. The following Subscription example shows a replication subscription:

{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "Monitor new neonatal function (note, age will be determined by the monitor)",
  "criteria": "Observation?code=SNOMED-CT|1000000050",
  "channel": {
    "extension": [
      {
        "url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-mode",
        "valueBoolean": true
      },
      {
        "url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-id-prefix",
        "valueString": "CDR1-"
      }
    ],
    "type": "rest-hook",
    "endpoint": "http://localhost:40243/fhir/context",
    "payload": "application/json"
  }
}

When running in replication mode, the following extensions may be used.

NameURLTypeDescription
Enable Replication Mode https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-mode boolean If set to true, this subscription will be delivered in replication mode.
Add ID Prefixes https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-id-prefix string If specified, all resource IDs (or a specific set of resource IDs if the selector property below is also set) will be prefixed with the given prefix (both in IDs, and in references to those IDs from other resources). This is useful in situations where you want to be able to replicate data from several source CDR instances to one target CDR instance, or in cases where the source CDR instance is using numeric IDs (since Smile CDR will not allow client-provided numeric IDs).
Add ID Prefixes - Selector https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-id-prefix-selector string If specified, only resource IDs matching the given pattern will have a prefix appended. The format of the pattern is a regular expresion and the pattern must match the entire string including the resource type and the ID part. For example, the following pattern will match all purely numeric IDs but will not prefix any IDs with non-numeric characters in them: `[a-zA-Z]+/[0-9]+`
Strip ID Prefixes https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-strip-id-prefix string If specified, all resource IDs will have the specified prefix removed from any resource IDs and references that are found within resources being delivered. This is helpful when subscriptions need to be delivered from a server that uses prefixes to a server that does not. For example, this could be used when delivering resources from a server that was populated by a subscription that uses the Add ID Prefixes on its own Subscription.
Strip ID Base URLs https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-replicate-strip-reference-base-url string If specified, all references containing this base URL will have the base URL removed prior to delivery.

If the replication is being sent to a second Smile CDR instance, you may wish to enable the dao_config.auto_create_placeholder_reference_targets.enabled configuration property on the replication target persistence module. This means that if payloads are received out-of-order by the receiving system, no payload will be rejected simply because it contains a reference to a resource that has not yet been received.

Strip Version IDs

 
This feature applies to REST HOOK subscriptions only.

By default, the resource version ID is included when delivering a resource to the REST HOOK receiving endpoint. This can be disabled using an extension on the REST HOOK channel.

{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "Monitor new neonatal function (note, age will be determined by the monitor)",
  "criteria": "Observation?code=SNOMED-CT|1000000050",
  "channel": {
    "extension": [
      {
        "url": "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids",
        "valueBoolean": true
      }
    ],
    "type": "rest-hook",
    "endpoint": "http://localhost:40243/fhir/context",
    "payload": "application/json"
  }
}

Note the following extension:

URLTypeDescription
http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids boolean If set to true, the deliverer will strip the resource version ID before sending the resource

Delivering Latest Version

 
This feature applies to REST HOOK subscriptions only.

By default if a resource is created and then updated in rapid succession, the deliverer will deliver versions 1 and 2 to the REST HOOK target.

This can be adjusted so that if the resource gets updated before the deliverer manages to transmit the resource, only the latest version will be transmitted (and it may be delivered more than once).

{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "Monitor new neonatal function (note, age will be determined by the monitor)",
  "criteria": "Observation?code=SNOMED-CT|1000000050",
  "channel": {
    "extension": [
      {
        "url": "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version",
        "valueBoolean": true
      }
    ],
    "type": "rest-hook",
    "endpoint": "http://localhost:40243/fhir/context",
    "payload": "application/json"
  }
}

Note the following extension:

URLTypeDescription
http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version boolean If set to true, the deliverer will attempt to send only the latest version of a resource.

Delivering Delete Events

 
This feature applies to REST HOOK subscriptions only.

By default, subscription doesn't deliver resource delete events.

This can be adjusted so resource delete events also get delivered.

{
    "resourceType": "Subscription",
    "id": "1",
    "meta": {
        "versionId": "1",
        "lastUpdated": "2017-08-19T16:52:11.041-04:00"
    },
    "status": "active",
    "reason": "Monitor resource persistence events",
    "criteria": "Patient",
    "channel": {
        "extension": [
            {
                "url": "http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages",
                "valueBoolean": "true"
            }
        ],
        "type": "rest-hook",
        "payload": "application/json",
        "header": [
            "Authorization: Bearer 123"
        ]
    }
}

Due to how deleted resources are stored, the _includeDeleted parameter can only be combined with the _id and _lastUpdated parameters in the URL when resource type is provided.

{
	
    "criteria": "Patient?_id=patient-test&_includeDeleted"
	
}
{

	"criteria": "Patient?_lastUpdated=gt2025-12-16&_includeDeleted"

}

Note the following extension:

URLTypeDescription
http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages boolean If set to true, the deliverer will also send messages for resource delete events.

Custom Delivery Class

 
This feature applies to REST HOOK subscriptions only.

Users may also supply a custom Java class that will be used to deliver the resource instead of the built-in FHIR client. This is useful in cases where you want to customize the delivery (e.g. by adding special attributes or filters, or by using another protocol altogether instead of FHIR/REST).

The following example shows a subscription that uses a custom delivery class:

{
  "resourceType": "Subscription",
  "id": "1",
  "meta": {
    "versionId": "1",
    "lastUpdated": "2017-08-19T16:52:11.041-04:00"
  },
  "status": "active",
  "reason": "Monitor new neonatal function (note, age will be determined by the monitor)",
  "criteria": "Observation?code=SNOMED-CT|1000000050&_format=xml",
  "channel": {
    "extension": [
      {
        "url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-delivery-class",
        "valueString": "org.example.ExampleDeliverer"
      }
    ],
    "type": "rest-hook",
    "payload": "application/json",
    "header": [
      "Authorization: Bearer 123"
    ]
  }
}

Note the following extension:

URLTypeDescription
https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-delivery-class string This is the class name for the Java class that implements the ISubscriptionDeliverer interface.

The following example shows a simple delivery class:

package org.example;

public class ExampleDeliverer implements ISubscriptionDeliverer {

   private Logger ourLog = LoggerFactory.getLogger(ExampleDeliverer.class);

   /**
    * Deliver the resource
    *
    * @param theOperation    The operation triggering this delivery
    * @param theSubscription The subscription
    * @param theResource     The resource
    */
   @Override
   public void deliver(RestOperationTypeEnum theOperation, CanonicalSubscription theSubscription, IBaseResource theResource) {

      String endpointUrl = theSubscription.getEndpointUrl();

      String payloadString = theSubscription.getPayloadString();

      EncodingEnum payloadType = EncodingEnum.forContentType(payloadString);

      IGenericClient client = FhirContext.forR4().newRestfulGenericClient(endpointUrl);

      List<String> headers = getSubscriptionHeaders(theSubscription);
      for (String next : headers) {
         if (isNotBlank(next)) {
            client.registerInterceptor(new SimpleRequestHeaderInterceptor(next));
         }
      }

      IClientExecutable operation = client.create().resource(theResource);

      operation.encoded(payloadType);

      try {
         operation.execute();
      } catch (ResourceNotFoundException e) {
         ourLog.error("Cannot reach {} ", endpointUrl);
         ourLog.error("Exception: ", e);
         throw e;
      }
   }

   private List<String> getSubscriptionHeaders(CanonicalSubscription theSubscription) {
      return Collections.emptyList();
   }
}



Custom Header Delivery

 
This feature applies to MESSAGE subscriptions only, when Kafka, Pulsar or ActiveMQ is your defined Message broker.

By default, the MESSAGE delivery type will deliver the resource to the broker without any custom headers. If you want to add headers to the message, there is a mechanism to do so. First, note the anatomy of an outgoing ResourceModifiedMessage:

{
  "headers": {
    "retryCount": 0,
    "customHeaders": {}
  },
  "payload": {
    "operationType": "CREATE",
    "transactionId": "eebf29cf-3775-4832-a054-df72e5d399ca",
    "payload": "{\"resourceType\":\"Patient\",\"id\":\"1354\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2022-07-19T17:27:56.198-07:00\"}}",
    "payloadId": "Patient/1354/_history/1",
    "partitionId": {
      "allPartitions": true
    }
  }
}

Any elements placed in the customHeaders section stay in the JSON body of the message, and are also delivered as the broker's native headers:

BrokerDelivered asValue types
KafkaRecord headersStrings are sent as-is; any other value is sent as JSON
PulsarMessage propertiesEvery value is sent as its string form
ActiveMQJMS message propertiesStrings, numbers and booleans keep their type; any other value is sent as its string form

Smile CDR provides a pointcut, SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY, which permits you to modify this outgoing message just before it is delivered to the broker.

By writing an interceptor which implements the SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY pointcut, you can add custom headers to the outgoing message. Below is an example of such an interceptor, which adds a resource type header, a resource ID header, a header for the tags, and the patient's family name.

//package com.smilecdr.demo.subscription;
// Uncomment the line above when trying out this example from the documentation

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * This interceptor customizes an outgoing Message Sending subscription in order to modify the
 * outgoing message by adding custom headers.
 * This interceptor should be registered against the Subscription module (not the FHIR Storage module).
 */
@Interceptor
public class RecordHeaderAddingInterceptor {
   private final FhirContext myFhirContext;

   public RecordHeaderAddingInterceptor(FhirContext theFhirContext) {
      myFhirContext = theFhirContext;
   }

   @Hook(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY)
   public boolean beforeMessageDelivery(CanonicalSubscription theSubscription, ResourceDeliveryMessage theMessage, ResourceModifiedJsonMessage theWrapper) {

      /*
       * If you want this interceptor to only apply to a specific subscription, you
       * can filter here. In this case we're only adding headers to deliveries for
       * the subscription with ID: Subscription/patient-sender
       */
      if (!"patient-sender".equals(theSubscription.getIdPart())) {
         return true;
      }

      Map<String, Object> customHeaders = theWrapper.getHapiHeaders().getCustomHeaders();
      IBaseResource payload = theMessage.getPayload(myFhirContext);

      //Add some basic information
      customHeaders.put("resource-id", payload.getIdElement().getValue());
      customHeaders.put("resource-type", myFhirContext.getResourceType(payload));

      //Add a list of all tags, delivered as a single header
      List<String> tags = payload.getMeta()
            .getTag()
            .stream()
            .map(tag -> tag.getSystem() + "|" + tag.getCode())
            .collect(Collectors.toList());
      customHeaders.put("tags", tags);

      //Now some specific patient headers, since we know this subscription only works for patients
      Patient patient = (Patient) payload;
      customHeaders.put("patient-family-name", patient.getNameFirstRep().getFamily());

      return true;


   }

}

Take this interceptor in conjunction with the following Patient record:

{
  "resourceType": "Patient",
  "meta": {
    "tag": [ {
      "system": "system-1",
      "code": "code-1",
      "display": "display-1"
    }, {
      "system": "system-2",
      "code": "code-2",
      "display": "display-2"
    }, {
      "system": "system-3",
      "code": "code-3",
      "display": "display-3"
    } ]
  },
  "active": true,
  "name": [ {
    "family": "Graham"
  } ]
}

After being sent to the remote message channel, the newly-arrived message would carry the following headers, along with Smile CDR's own headers such as retryCount.

Kafka record headers:

resource-id:         Patient/1354/_history/1
resource-type:       Patient
tags:                ["system-1|code-1","system-2|code-2","system-3|code-3"]
patient-family-name: Graham

Pulsar message properties and ActiveMQ JMS message properties:

resource-id:         Patient/1354/_history/1
resource-type:       Patient
tags:                [system-1|code-1, system-2|code-2, system-3|code-3]
patient-family-name: Graham

DEPRECATED: Delivery to Site-defined External Queue

 
This feature has been replaced. Instead of using this extension, users are encouraged to use the "message" channel type instead. This feature applies to REST HOOK subscriptions only.

THIS EXTENSION IS DEPRECATED

Related to the custom delivery class, users may alternatively choose to have the resource delivered to a queue or topic in the Message Broker defined and managed separately from Smile CDR. This is useful in cases where you want to be able to retrieve resources directly off of a message queue or topic or if you have a custom handler defined in your Message Broker to manage delivery.

The following example shows a subscription that uses a site-defined external topic or queue:

{
  "resourceType": "Subscription",
  "id": "1",
  "meta": {
    "versionId": "1",
    "lastUpdated": "2017-08-19T16:52:11.041-04:00"
  },
  "status": "active",
  "reason": "Monitor new neonatal function (note, age will be determined by the monitor)",
  "criteria": "Observation?code=SNOMED-CT|1000000050&_format=xml",
  "channel": {
    "extension": [
      {
        "url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-named-channel-delivery",
        "valueString": "MyQueueName"
      }
    ],
    "type": "rest-hook",
    "endpoint": "http://localhost:40243/fhir/context",
    "payload": "application/json",
    "header": [
      "Authorization: Bearer 123"
    ]
  }
}

Note the following extension:

URLTypeDescription
https://smilecdr.com/fhir/ns/StructureDefinition/subscription-named-channel-delivery string This is the name for the external message queue or topic that resources will be copied to.