NotifyrFHIR Subscription Tester

A throwaway endpoint for FHIR rest-hook notifications

Point a FHIR Subscription at the URL you get below, and every notification your server sends is captured in full, validated against R4B/R5, and shown live.

Leave blank for a random id. Letters, digits, and - . _ ~ only; up to 64 characters.

Nothing is authenticated and data is kept in memory only: anyone who knows an endpoint's id can read everything sent to it, and a chosen id is only as private as it is hard to guess. A debugging tool, not a destination for real patient data.

Testing a rest-hook subscription normally means standing up a public server just to see what your FHIR server is actually sending. Notifyr replaces that with a URL you create in one click.

Every request that arrives is:

  • captured in full — the raw body exactly as sent, even when it is malformed;
  • validated — JSON, then FHIR structure and value sets, with every problem located and explained;
  • counted by type — handshakes, heartbeats and event-notifications tallied separately, so you can confirm each part of the subscription lifecycle actually fired.

How to test with fhir-candle

A five-step walkthrough against a real FHIR server, with the Subscription and Encounter to paste.

fhir-candle is a small in-memory FHIR server with a built-in Subscriptions reference implementation, which makes it the quickest way to see real notifications land here.

No interest in running anything locally? subscriptions.argo.run hosts the same fhir-candle software, with SubscriptionTopic/encounter-complete already loaded at /fhir/r4b — skip straight to step 2 below with that base URL. It's shared, third-party infrastructure Notifyr doesn't control, so the local walkthrough is the one that's always reproducible and gives you server-side logs when something doesn't arrive.

Create an endpoint first — the sample in step 2 then arrives pre-filled with its own URL, on its dashboard.

  1. 1

    Start fhir-candle with the subscriptions reference implementation

    fhir-candle --reference-implementation subscriptions -o

    It serves R4B at http://localhost:5826/fhir/r4b and -o opens its UI in a browser.

  2. 2

    Create a Subscription pointing at your webhook URL

    PUT http://localhost:5826/fhir/r4b/Subscription/notifyr-test

    {
      "resourceType": "Subscription",
      "id": "notifyr-test",
      "status": "active",
      "end": "2027-01-01T00:00:00Z",
      "reason": "Testing rest-hook notifications with Notifyr",
      "criteria": "http://example.org/FHIR/SubscriptionTopic/encounter-complete",
      "_criteria": {
        "extension": [
          {
            "url": "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-filter-criteria",
            "valueString": "Encounter?patient=Patient/example"
          }
        ]
      },
      "channel": {
        "extension": [
          {
            "url": "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-heartbeat-period",
            "valueInteger": 120
          }
        ],
        "type": "rest-hook",
        "endpoint": "https://your-notifyr-host/hook/your-hook-id",
        "payload": "application/fhir+json",
        "_payload": {
          "extension": [
            {
              "url": "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-payload-content",
              "valueCode": "id-only"
            }
          ]
        }
      }
    }

    Replace channel.endpoint with your own hook URL. The heartbeat extension is set to 120 seconds, so a heartbeat arrives every two minutes. Keep end in the future or the subscription expires.

  3. 3

    Watch the handshake arrive

    As soon as the Subscription becomes active, fhir-candle sends a handshake. It should appear here within a couple of seconds, and the Handshake counter should read 1. If it does not, the server could not reach your URL.

  4. 4

    Create an Encounter with status active

    PUT http://localhost:5826/fhir/r4b/Encounter/example

    {
      "resourceType": "Encounter",
      "id": "example",
      "text": {
        "status": "generated",
        "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Encounter with patient @example</div>"
      },
      "status": "active",
      "class": {
        "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
        "code": "IMP",
        "display": "inpatient encounter"
      },
      "subject": {
        "reference": "Patient/example"
      }
    }

    Nothing is notified yet — the topic fires on completion, not creation.

  5. 5

    Update the Encounter to status finished

    PUT the same Encounter again with "status": "finished". That satisfies the encounter-complete topic and triggers an event-notification.

You should end up with a handshake, an event-notification, and a heartbeat every two minutes for as long as the subscription stays active. Click any row to see its raw body and validation results.

How Notifyr validates

The three validation tiers, and why Subscription notifications need handling of their own.

Every request runs through three tiers. Each one gates the next, so a message is only judged on rules it could plausibly satisfy.

  1. 1. Does the body parse as JSON?

    If not, the message is invalid and answered 400. The raw body is still stored exactly as sent — an unparseable payload is usually the one you most need to look at.

  2. 2. Is it a JSON object with a resourceType?

    An array, a scalar, or an object without resourceType cannot be a FHIR resource. Invalid, answered 422.

  3. 3. Does it pass FHIR structural and value-set validation?

    Run through the fhir package against R4. Unknown elements, missing required properties and codes outside their value set are reported with the exact path, e.g. Patient.gender. Invalid, answered 422.

Subscription notifications

These need special handling. The fhir package ships R4 conformance only, and SubscriptionStatus was introduced in R4B — so left alone the validator rejects the resource type outright and marks every handshake and heartbeat invalid.

Notifyr therefore validates SubscriptionStatus entries itself against the R4B/R5 definition, and hands the rest of the Bundle to the library untouched, so a broken sibling resource is still caught.

  • Errors (make it invalid): type present and one of the five notification types; subscription present with a reference; status a known code; topic a string.
  • Warnings (shown, still valid): Bundle.type other than history; a missing Bundle.timestamp; SubscriptionStatus not first; a query-status carrying event information, which the spec prohibits.
  • A handshake or heartbeat may carry notificationEvent entries and is not flagged for it — R4B marks the element "Special" for those types, so a server catching a client up after a reconnect is behaving correctly.

eventsSinceSubscriptionStart is accepted both as a string (R4B) and as a number (R5).

Warnings never make a message invalid, and only valid notifications are counted by type — a Bundle that failed validation has not established what kind of notification it was.

References

The specification pages every rule here is checked against.

Specification

  • Subscription (R4B)

    The resource itself, plus the Channels section that defines rest-hook delivery and endpoint validation.

  • SubscriptionStatus (R4B)

    What every notification carries. Defines type (handshake, heartbeat, event-notification, query-status, query-event) as 1..1, and eventsSinceSubscriptionStart as a string.

  • SubscriptionTopic (R4B)

    Describes which events trigger a notification and what the payload should contain.

  • Topic-Based Subscriptions Framework (R5)

    The full framework, including the notification Bundle shape. R5 types eventsSinceSubscriptionStart as an integer64 rather than a string.

  • Subscriptions R5 Backport IG

    How R4 servers implement the R5 model, and the source of the backport-heartbeat-period, backport-filter-criteria and backport-payload-content extensions.

  • Notification payloads (Backport IG)

    What empty, id-only and full-resource each require a notification Bundle to carry.

Tooling

  • fhir-candle

    In-memory FHIR server with a subscriptions reference implementation. Used in the walkthrough above.

  • fhir (npm)

    The R4 validator Notifyr runs your payloads through.