Lillia can notify your system automatically when something changes for your members, for example when a member completes a journey. Each update is one signed HTTPS message to an address you choose. Staging and Production are set up separately, each with its own address and signing secret.
How it works, end to end
A. Set-up, once per environment (Staging, then Production)
- You send us your HTTPS endpoint URL and a contact mailbox
- Lillia registers it and shares the signing secret with you securely
- Lillia sends a test a signed
pingmessage to your endpoint - You confirm signature verified, replied HTTP 200
B. Live, for every update you selected
- Something changes for example a member completes a journey on Lillia
- Lillia sends one message a signed HTTPS POST to your endpoint
- You verify and reply check the signature, answer
2xxwithin 10 seconds - Delivered logged for 30 days, same id on every retry. No
2xx: Lillia retries, up to 8 attempts over about 46 hours
Who shares what
| You share with Lillia, per environment | Lillia shares with you, per environment |
|---|---|
Your HTTPS endpoint URL (https:// only; no redirects; no username and password) |
The signing secret (whsec_…), shown once, sent through an agreed secure channel |
| A technical contact mailbox for delivery notices | A test message so you can confirm your set-up |
| The updates you want (see the list below) | The message format and the delivery rules on this page |
| Whether you need our source IP addresses for an allow-list | A delivery log we can share on request (30 days) |
What a message looks like
Request from Lillia (headers):
POST /hooks/lillia HTTP/1.1
Host: api.your-system.example
content-type: application/json
user-agent: Lillia-Webhooks/1
lillia-event-type: journey.completed
webhook-id: whd_2f8c1e9a-…
webhook-timestamp: 1762161262
webhook-signature: v1,K5oZ3n0k6yQ1yYH1g5a3cXl7xM2wR9pT4uV6bN8dE0s=
The four headers that matter:
| Header | Meaning |
|---|---|
lillia-event-type |
The update type, the same value as type in the body, so you can route before parsing |
webhook-id |
The delivery id. Stable across every retry of the same message; use it to discard repeats. |
webhook-timestamp |
When the message was sent, as Unix seconds |
webhook-signature |
v1, followed by the Base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body} with your secret. Two values, space separated, during a secret rotation; accept if either matches. |
Body (example: a journey completed):
{
"id": "evt_7d5f4a1e-…",
"type": "journey.completed",
"occurred_at": "2026-11-03T09:14:22Z",
"tenant_id": "your-tenant-id",
"data": {
"user_journey_id": "c3a9…",
"journey_id": "8e21…",
"patient_id": "a71d…",
"program_id": "19f4…",
"status": "completed",
"previous_status": "in_progress",
"completed_at": "2026-11-03T09:14:22Z",
"completion_mode": "automatic"
}
}
Your response (any 2xx within 10 seconds; body optional):
HTTP/1.1 200 OK
content-length: 0
How you verify the signature (Standard Webhooks format; libraries exist for most languages):
key = base64_decode(secret without the "whsec_" prefix)
signed = webhook_id + "." + webhook_timestamp + "." + raw_body
expected = base64(hmac_sha256(key, signed))
accept if constant_time_equal(expected, signature after "v1,")
and abs(now - webhook_timestamp) <= 300 seconds
Every message has the same outer fields: id, type, occurred_at, tenant_id and data. The data object holds identifiers and statuses for the update; never names, contact details or clinical data. Further detail is read by identifier through the Lillia API.
Updates available
| Group | Updates |
|---|---|
| Journeys | journey.completed · journey.enrolled · journey.exited |
| Appointments | appointment.booked · appointment.rescheduled · appointment.cancelled · appointment.completed · appointment.no_show |
| Subscriptions | subscription.activated · subscription.renewed · subscription.past_due · subscription.expired · subscription.cancelled |
| Programs | program.changed |
The same registration covers all of them. Updates can be added later without a new endpoint.
Delivery rules
| Rule | Detail |
|---|---|
| Acknowledgement | Any HTTP 2xx within 10 seconds. Redirects are not followed. |
| Retries | Up to 8 attempts over about 46 hours: immediately, then after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours |
| Duplicates and order | Possible after a retry. Discard repeats by webhook-id; use occurred_at, not arrival order. |
| Timestamp check | Reject a message whose webhook-timestamp is more than 5 minutes from your clock |
| Stopping | A 410 Gone reply stops deliveries at once. After 3 days of failed deliveries the endpoint is paused and your contact is informed. |
| Secret rotation | On request. For 24 hours each message carries two signatures, space separated, so you can switch without interruption. |
| Source IP addresses | Authenticity comes from the signature. If your policy requires an allow-list, tell us and we confirm the addresses before go-live. |
| Changes to the format | New fields may be added at any time; ignore fields you do not know. Lillia gives 30 days' notice of any change that is not backward compatible. |
Getting started
- Send us, for Staging: your endpoint URL, a contact mailbox and the updates you want. Reach your Lillia account contact or write to us through www.lilliacare.ai.
- We register the address, share the signing secret securely and send a test message.
- You confirm the test and exercise the updates you need on Staging with test data.
- Send the same details for Production. We register it, send a test, and live updates start on the agreed date.
Changes to an address, the updates or a secret are requested through your Lillia account contact.
