CarePlan Events
Fire Arrow Server can automatically schedule CarePlan activities into Task resources and notify your application when those tasks become due. This enables server-side care plan orchestration -- define a care plan once, and the server handles the timeline, creates the tasks, and calls your webhook when it's time for action.
Overview
In many digital health applications, care plans include recurring activities: daily medication reminders, weekly check-ins, monthly assessments. Traditionally, the application has to manage the scheduling logic itself, which is complex, error-prone, and difficult to scale.
Fire Arrow Server's CarePlan event system solves this by:
- Materializing CarePlan activities into individual Task resources up to a configurable time horizon
- Transitioning tasks to "ready" status when they become due
- Notifying your application via webhooks when tasks are ready
Enabling CarePlan Scheduling
CarePlan scheduling is opt-in per CarePlan. Tag a CarePlan with the scheduling meta tag to tell Fire Arrow Server to manage its activities:
{
"resourceType": "CarePlan",
"meta": {
"tag": [{
"system": "https://firearrow.io/fhir/careplan-scheduling",
"code": "scheduled"
}]
},
"status": "active",
"intent": "plan",
"subject": { "reference": "Patient/123" },
"activity": [
{
"detail": {
"description": "Daily blood pressure check",
"status": "scheduled",
"scheduledTiming": {
"repeat": {
"frequency": 1,
"period": 1,
"periodUnit": "d"
}
}
}
},
{
"detail": {
"description": "Weekly symptom questionnaire",
"status": "scheduled",
"scheduledTiming": {
"repeat": {
"frequency": 1,
"period": 1,
"periodUnit": "wk"
}
}
}
}
]
}
Once this CarePlan is created (or updated with the tag), Fire Arrow Server begins materializing Task resources for each activity according to its schedule.
How Materialization Works
The server maintains a scheduling horizon -- a rolling time window (e.g., 30 days into the future) during which it creates Task resources. As time passes, the server continues to materialize new tasks to fill the horizon.
For each activity occurrence within the horizon:
- A Task resource is created with status
draft - The Task references the CarePlan via
basedOn - The Task has an
executionPeriodindicating when it should be performed - When the
executionPeriod.startis reached, the server transitions the Task toreadystatus - This status change triggers any active Subscriptions matching
Task?status=ready
The server only materializes tasks within the horizon window. For a 30-day horizon, tasks more than 30 days in the future won't exist yet. They will be created as the horizon advances.
Subscribing to Due Events
When tasks become due (transition to ready), you'll want to be notified so your application can take action. There are two ways to set up these notifications.
Easy Way: $subscribe-due-events
The $subscribe-due-events operation is a convenience endpoint that creates a properly configured Subscription for a specific CarePlan:
curl -X POST http://localhost:8080/fhir/CarePlan/789/\$subscribe-due-events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Parameters",
"parameter": [
{ "name": "endpoint", "valueUrl": "https://your-app.example.com/webhooks/careplan-tasks" }
]
}'
The server creates a Subscription with:
- Criteria:
Task?status=ready&based-on=CarePlan/789 - Channel: REST hook to your specified endpoint
- End date: Automatically set based on the server's maximum subscription TTL
Advanced: Create a Subscription Directly
For more control, create a FHIR Subscription resource yourself:
curl -X POST http://localhost:8080/fhir/Subscription \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Subscription",
"status": "requested",
"criteria": "Task?status=ready&based-on=CarePlan/789",
"channel": {
"type": "rest-hook",
"endpoint": "https://your-app.example.com/webhooks/careplan-tasks",
"payload": "application/fhir+json"
},
"end": "2026-04-28T00:00:00Z"
}'
The end field is required on all Subscriptions. The server enforces a maximum TTL -- if the end date exceeds the maximum, it will be clamped to the allowed maximum.
Webhook Payload
When a Task transitions to ready, Fire Arrow Server sends a minimal notification to your webhook endpoint. The payload is a lightweight hint that does not contain Protected Health Information (PHI):
{
"resourceType": "Task",
"id": "task-456",
"status": "ready",
"basedOn": [{ "reference": "CarePlan/789" }]
}
Your application should then fetch the full Task resource via the API to get all details:
curl http://localhost:8080/fhir/Task/task-456 \
-H "Authorization: Bearer <your-token>"
This two-step pattern keeps webhook payloads small and avoids transmitting sensitive data over webhook channels.
Completing Tasks
When the patient or practitioner completes an activity, update the Task status:
curl -X PUT http://localhost:8080/fhir/Task/task-456 \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Task",
"id": "task-456",
"status": "completed",
"basedOn": [{ "reference": "CarePlan/789" }],
"executionPeriod": {
"start": "2026-03-28T08:00:00Z",
"end": "2026-03-28T08:15:00Z"
}
}'
Nop Channel: Materialization Without Webhooks
If you don't need real-time webhook notifications, you can still benefit from CarePlan materialization by using the nop channel (no-operation). The server creates and transitions Task resources on schedule, and your application polls for ready tasks:
curl "http://localhost:8080/fhir/Task?status=ready&based-on=CarePlan/789" \
-H "Authorization: Bearer <your-token>"
Or via GraphQL:
{
TaskList(status: "ready", based_on: "CarePlan/789") {
id
code { text }
executionPeriod { start end }
}
}
This approach is simpler to set up and avoids the need for a publicly accessible webhook endpoint.
Configuration
Configure CarePlan events under the fire-arrow.careplan-events key in your application.yaml:
fire-arrow:
careplan-events:
enabled: true
scheduling:
horizon-duration: "P30D" # Materialize tasks 30 days ahead
check-interval: "PT1H" # Check for new materializations every hour
subscriptions:
max-ttl: "P90D" # Maximum subscription lifetime
delivery:
max-retries: 3 # Retry failed webhook deliveries
retry-interval: "PT30S" # Wait between retries
failure-threshold: 5 # Disable subscription after N consecutive failures
cleanup:
interval: "P1D" # Run cleanup daily
completed-task-retention: "P90D" # Keep completed tasks for 90 days
task:
auto-transition-to-ready: true # Automatically transition tasks to ready when due
| Property | Description | Default |
|---|---|---|
enabled | Enable CarePlan event processing | false |
scheduling.horizon-duration | How far ahead to materialize tasks (ISO 8601 duration) | P30D |
scheduling.check-interval | How often to check for new materializations | PT1H |
subscriptions.max-ttl | Maximum allowed subscription lifetime | P90D |
delivery.max-retries | Number of webhook delivery retry attempts | 3 |
delivery.retry-interval | Delay between retry attempts | PT30S |
delivery.failure-threshold | Consecutive failures before disabling a subscription | 5 |
cleanup.interval | How often the cleanup job runs | P1D |
cleanup.completed-task-retention | How long to keep completed tasks | P90D |
task.auto-transition-to-ready | Automatically move tasks to ready when due | true |
End-to-End Example
Here's a complete walkthrough: create a CarePlan, subscribe to events, receive a webhook, and fetch the task.
1. Create a CarePlan with Scheduling
curl -X POST http://localhost:8080/fhir/CarePlan \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "CarePlan",
"meta": {
"tag": [{
"system": "https://firearrow.io/fhir/careplan-scheduling",
"code": "scheduled"
}]
},
"status": "active",
"intent": "plan",
"subject": { "reference": "Patient/123" },
"period": {
"start": "2026-03-28",
"end": "2026-06-28"
},
"activity": [{
"detail": {
"description": "Daily blood pressure measurement",
"status": "scheduled",
"scheduledTiming": {
"repeat": {
"frequency": 1,
"period": 1,
"periodUnit": "d",
"timeOfDay": ["08:00:00"]
}
}
}
}]
}'
The server returns the created CarePlan with an id (e.g., CarePlan/789).
2. Subscribe to Due Events
curl -X POST http://localhost:8080/fhir/CarePlan/789/\$subscribe-due-events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Parameters",
"parameter": [
{ "name": "endpoint", "valueUrl": "https://your-app.example.com/webhooks/bp-check" }
]
}'
3. Receive Webhook Notification
When the daily task becomes due (at 08:00), your endpoint receives:
{
"resourceType": "Task",
"id": "task-001",
"status": "ready",
"basedOn": [{ "reference": "CarePlan/789" }]
}
4. Fetch Full Task Details
curl http://localhost:8080/fhir/Task/task-001 \
-H "Authorization: Bearer <your-token>"
5. Complete the Task
After the patient takes their measurement:
curl -X PUT http://localhost:8080/fhir/Task/task-001 \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Task",
"id": "task-001",
"status": "completed",
"basedOn": [{ "reference": "CarePlan/789" }],
"executionPeriod": {
"start": "2026-03-28T08:00:00Z",
"end": "2026-03-28T08:10:00Z"
}
}'
The server continues materializing and transitioning tasks according to the CarePlan schedule for the duration of the care plan's period.