Subscription (FHIR R4)
FHIR R4 Subscription resource.
This reference is generated from the canonical Huli Public API OpenAPI specification and the server's FHIR R4 CapabilityStatement — do not edit it directly.
FHIR R4 Specification
Official spec: Subscription — HL7 FHIR R4
Supported Interactions
- read — Read a single resource by ID (
GET /fhir/R4/{Resource}/{id}) - search-type — Search resources with query parameters (
GET /fhir/R4/{Resource}?...) - create — Create a new resource (
POST /fhir/R4/{Resource}) - update — Update an existing resource (
PUT /fhir/R4/{Resource}/{id}) - delete — delete
- $replay — operation
- $stats — operation
- $deliveries — operation
Scopes
Scopes are shared across FHIR releases — the same system/Subscription.* scope grants Subscription access on both /fhir/R4 and /fhir/R5.
- system/Subscription.rs — see scope reference
- system/Subscription.crud — see scope reference
Search Parameters
| Parameter | Type | Notes |
|---|---|---|
_id | token | Exact match. For identifiers use `system |
status | token | Exact match. For identifiers use `system |
_count | number | Integer. For _count: default 20, max 100. |
_offset | number | Integer. For _count: default 20, max 100. |
Endpoints
Read Subscription
Retrieve a single outbound-webhook Subscription by its ID. The response NEVER includes the signing secret — the secret is returned exactly once, on create.
/fhir/R4/Subscription/{id}Required scope: system/Subscription.rs
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.rs" \
| jq -r .access_token)
curl -X GET https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID} \
-H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}`,
{
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
},
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}",
headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class getSubscriptionExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func getSubscriptionExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00105 | 429 | Rate limit exceeded |
Update Subscription
Update an existing outbound-webhook Subscription. The mutable fields are status, criteria, and the channel target/payload/headers. The signing secret is PRESERVED (never rotated or returned here). The endpoint is re-validated (https-only, SSRF-safe) on every update. channel.type must remain rest-hook and channel.payload must remain application/fhir+json.
/fhir/R4/Subscription/{id}Required scope: system/Subscription.crud
Request Body
| name | type | required | description |
|---|---|---|---|
| resourceType | string | required | |
| id | string(uuid) | optional | |
| meta | object | optional | |
| status | string | required | Subscription status. On write accept `requested` (default), `active`, or `off`. `error` is server-owned (set by the delivery circuit breaker) and is rejected on write. |
| reason | string | required | Required free-text reason for the subscription. Accepted and validated non-empty, but NOT persisted — reads echo a fixed reason. |
| criteria | string | required | The FHIR resource type to notify on — one of `Patient`, `Appointment`, `Encounter`, `Observation`, `MedicationRequest`, `ServiceRequest`. v1 matches on resource type only: query-parameter filtering (e.g. `Encounter?status=finished`) is not yet supported and is rejected, so subscribe to the bare resource type. |
| channel | object | required | |
| extension | array | optional | On the create (`201`) response ONLY, carries the one-time plaintext signing secret under the `https://huli.io/fhir/StructureDefinition/subscription-signing-secret` extension (`valueString`). Never present on read / search / update. |
Request Example
{
"resourceType": "Subscription",
"status": "active",
"reason": "Notify our EHR mirror of encounter changes",
"criteria": "Encounter",
"channel": {
"type": "rest-hook",
"endpoint": "https://example.org/webhooks/huli",
"payload": "application/fhir+json",
"header": [
"X-Source-System: acme-ehr"
]
}
}
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.crud" \
| jq -r .access_token)
curl -X PUT https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID} \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/fhir+json" \
-d @resource.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/fhir+json",
},
body: JSON.stringify(payload),
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}
resp = requests.put(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}",
headers=headers,
json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class updateSubscriptionExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
String payload = "{}"; // your serialized FHIR resource
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.header("Content-Type", "application/fhir+json")
.method("PUT", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func updateSubscriptionExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("PUT", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"", body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/fhir+json")
// set req.Body to your serialized resource
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00101 | 400 | Validation error |
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00105 | 429 | Rate limit exceeded |
Delete Subscription
Soft-delete an outbound-webhook Subscription. The subscription is deactivated (no further deliveries fan out to it). Returns 204 on success.
/fhir/R4/Subscription/{id}Required scope: system/Subscription.crud
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.crud" \
| jq -r .access_token)
curl -X DELETE https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID} \
-H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}`,
{
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`,
},
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
resp = requests.delete(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}",
headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class deleteSubscriptionExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func deleteSubscriptionExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("DELETE", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00105 | 429 | Rate limit exceeded |
Replay Subscription events
Re-scan the organization's event history for this subscription's criteria (resource type) over a time window and re-enqueue each matching event — for outage recovery / backfill. NOTE: the scan is by criteria + window, so it includes events that predate the subscription and events already delivered; dedupe on X-Huli-Event-Id. An event that already has an in-flight replay for this subscription is skipped, so repeated calls over the same window don't duplicate. Each newly matched event creates a fresh delivery carrying the SAME X-Huli-Event-Id as the original (so receivers dedup by event) plus X-Huli-Replay: true. The window is clamped to the 30-day retention horizon (from floored at now-30d, to capped at now) and a single call re-enqueues at most 500 events (for a larger backlog, call again with the same window — in-flight replays are skipped, so each call advances to older events — until truncated is false). Only an active subscription may replay. The from/to window is supplied as a FHIR Parameters body (or as from/to RFC3339 query params); from is required, to defaults to now.
/fhir/R4/Subscription/{id}/$replayRequired scope: system/Subscription.crud
Request Example
{
"resourceType": "Parameters",
"parameter": [
{
"name": "from",
"valueInstant": "2026-06-01T00:00:00Z"
},
{
"name": "to",
"valueInstant": "2026-06-02T00:00:00Z"
}
]
}
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.crud" \
| jq -r .access_token)
curl -X POST https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID}/$replay \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/fhir+json" \
-d @$replay.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}/$replay`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/fhir+json",
},
body: JSON.stringify(payload),
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}
resp = requests.post(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}/$replay",
headers=headers,
json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class replaySubscriptionExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
String payload = "{}"; // your serialized FHIR resource
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId + "/$replay"))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.header("Content-Type", "application/fhir+json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func replaySubscriptionExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("POST", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"/$replay", body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/fhir+json")
// set req.Body to your serialized resource
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00101 | 400 | Validation error |
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00101 | 422 | Unprocessable entity |
| HPB-00105 | 429 | Rate limit exceeded |
Subscription delivery metrics
Per-subscription delivery observability. Returns a FHIR Parameters resource with the delivered / failed / pending / dead counts, the dead-letter depth, total attempts, the terminal success rate, and the enqueue-to-delivery latency p50/p95 (seconds). Never exposes payloads, endpoints, or signing secrets.
/fhir/R4/Subscription/{id}/$statsRequired scope: system/Subscription.rs
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.rs" \
| jq -r .access_token)
curl -X GET https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID}/$stats \
-H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}/$stats`,
{
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
},
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}/$stats",
headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class getSubscriptionStatsExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId + "/$stats"))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func getSubscriptionStatsExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"/$stats", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00105 | 429 | Rate limit exceeded |
Subscription delivery-attempt trail
The delivery-level audit trail for one subscription: the recent delivery attempts (newest first, paged via _count, optionally filtered by status = pending | delivered | failed | dead), returned as a FHIR Parameters resource with one delivery group per attempt. Each group carries only sanitized metadata — delivery status, attempt count, last HTTP status code, queued/delivered timestamps, the replay flag, and the source event's type — never the payload, endpoint, signing secret, or error text.
/fhir/R4/Subscription/{id}/$deliveriesRequired scope: system/Subscription.rs
Query Parameters
| name | type | required | description |
|---|---|---|---|
| _count | integer | optional | Maximum number of delivery attempts to return. |
| status | string | optional | Narrow the trail to a single delivery status. |
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.rs" \
| jq -r .access_token)
curl -X GET https://api.huli.ai/fhir/R4/Subscription/${RESOURCE_ID}/$deliveries \
-H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription/${id}/$deliveries`,
{
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
},
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(
f"https://api.huli.ai/fhir/R4/Subscription/{resource_id}/$deliveries",
headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class getSubscriptionDeliveriesExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String resourceId = "RESOURCE_ID";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription/" + resourceId + "/$deliveries"))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func getSubscriptionDeliveriesExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://api.huli.ai/fhir/R4/Subscription/"+resourceID+"/$deliveries", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00101 | 400 | Validation error |
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00102 | 404 | Resource not found |
| HPB-00105 | 429 | Rate limit exceeded |
Search Subscriptions
Search outbound-webhook Subscriptions for the authenticated organization. Supports _id and status filters plus offset pagination. Responses NEVER include the signing secret.
/fhir/R4/SubscriptionRequired scope: system/Subscription.rs
Query Parameters
| name | type | required | description |
|---|---|---|---|
| _id | string | optional | Filter by Subscription ID (UUID). |
| status | string | optional | Filter by status: `requested`, `active`, `error`, `off`. |
| _count | integer | optional | Number of results per page (default: 20, max: 100). |
| _offset | integer | optional | Number of results to skip (offset-based pagination). |
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.rs" \
| jq -r .access_token)
curl -X GET https://api.huli.ai/fhir/R4/Subscription \
-H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription`,
{
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
},
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(
f"https://api.huli.ai/fhir/R4/Subscription",
headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class searchSubscriptionsExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription"))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func searchSubscriptionsExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://api.huli.ai/fhir/R4/Subscription", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00105 | 429 | Rate limit exceeded |
Create Subscription
Create an outbound-webhook Subscription. The server assigns the resource ID and generates an HMAC signing secret, which is returned EXACTLY ONCE in the 201 response body as a valueString extension (https://huli.io/fhir/StructureDefinition/subscription-signing-secret) with a Cache-Control: no-store response header. The secret is never returned again — store it securely. Validation (all fail-closed, 400):
channel.typemust berest-hook.channel.endpointmust be anhttpsURL with no embedded credentials (SSRF-checked).channel.payload, when set, must beapplication/fhir+json.channel.headerentries must beName: value(no CR/LF, no duplicates).criteriamust be a bare resource type — one ofPatient,Appointment,Encounter,Observation,MedicationRequest,ServiceRequest. Query-parameter filtering is not supported in v1 (fan-out matches on resource type only); acriteriacarrying a query (e.g.Encounter?status=finished) is rejected.reasonis required. The201body also carries a non-blocking warningOperationOutcome(incontained) reminding the integrator of their BAA / PHI-handling responsibility for the configured endpoint.
/fhir/R4/SubscriptionRequired scope: system/Subscription.crud
Request Body
| name | type | required | description |
|---|---|---|---|
| resourceType | string | required | |
| id | string(uuid) | optional | |
| meta | object | optional | |
| status | string | required | Subscription status. On write accept `requested` (default), `active`, or `off`. `error` is server-owned (set by the delivery circuit breaker) and is rejected on write. |
| reason | string | required | Required free-text reason for the subscription. Accepted and validated non-empty, but NOT persisted — reads echo a fixed reason. |
| criteria | string | required | The FHIR resource type to notify on — one of `Patient`, `Appointment`, `Encounter`, `Observation`, `MedicationRequest`, `ServiceRequest`. v1 matches on resource type only: query-parameter filtering (e.g. `Encounter?status=finished`) is not yet supported and is rejected, so subscribe to the bare resource type. |
| channel | object | required | |
| extension | array | optional | On the create (`201`) response ONLY, carries the one-time plaintext signing secret under the `https://huli.io/fhir/StructureDefinition/subscription-signing-secret` extension (`valueString`). Never present on read / search / update. |
Request Example
{
"resourceType": "Subscription",
"status": "requested",
"reason": "Notify our EHR mirror of encounter changes",
"criteria": "Encounter",
"channel": {
"type": "rest-hook",
"endpoint": "https://example.org/webhooks/huli",
"payload": "application/fhir+json",
"header": [
"X-Source-System: acme-ehr"
]
}
}
Code Samples
TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${CLIENT_ASSERTION}" \
-d "scope=system/Subscription.crud" \
| jq -r .access_token)
curl -X POST https://api.huli.ai/fhir/R4/Subscription \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/fhir+json" \
-d @subscription.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";
const response = await fetch(
`https://api.huli.ai/fhir/R4/Subscription`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/fhir+json",
},
body: JSON.stringify(payload),
}
);
if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests
token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}
resp = requests.post(
f"https://api.huli.ai/fhir/R4/Subscription",
headers=headers,
json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class createSubscriptionExample {
public static void main(String[] args) throws Exception {
String token = System.getenv("HULI_ACCESS_TOKEN");
String payload = "{}"; // your serialized FHIR resource
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Subscription"))
.header("Authorization", "Bearer " + token)
.header("Accept", "application/fhir+json")
.header("Content-Type", "application/fhir+json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("status: " + response.statusCode());
System.out.println(response.body());
}
}
import (
"fmt"
"net/http"
"os"
)
func createSubscriptionExample() {
token := os.Getenv("HULI_ACCESS_TOKEN")
req, _ := http.NewRequest("POST", "https://api.huli.ai/fhir/R4/Subscription", body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/fhir+json")
// set req.Body to your serialized resource
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}
Errors
| Code | Status | Description |
|---|---|---|
| HPB-00101 | 400 | Validation error |
| HPB-00106 | 401 | Authentication failed |
| HPB-00104 | 403 | Insufficient scope |
| HPB-00105 | 429 | Rate limit exceeded |