Webhooks Overview¶
Spry Webhooks deliver real-time notifications to your endpoint when patient, appointment, and case events happen in the Spry EMR. Each webhook is an HTTP POST carrying a JSON payload signed with HMAC-SHA256, and failed deliveries are retried automatically.
This reference covers:
- Event Types & Sample Events — every
event_typeand its payload - Subscribing to Webhooks — create and manage subscriptions
- Signature Verification — verify a webhook came from Spry
Payload Format¶
This documentation covers the 2026-06-11 payload version (the current, minimized format). Each webhook body is wrapped in a common envelope; the event-specific data lives under data. The minimized format carries only the resource id(s), organisation_id, and a little non-PHI status — you fetch the full record from the Spry API by id.
{
"event_id": "evt_8f3a2b9c-1234-4567-89ab-cdef01234567",
"event_type": "appointment.updated",
"api_version": "2026-06-11",
"timestamp": 1776246130,
"data": {
"organisation_id": 74,
"appointment_id": 113191,
"patient_id": 45675,
"spry_case_id": "SPRY_CASE_69d778fa05e30511a1a2b282",
"clinic_id": 44,
"status": "PATIENT_CHECKIN"
}
}
Envelope Fields¶
| Field | Type | Description |
|---|---|---|
event_id |
string | Unique webhook delivery ID, prefixed evt_. Use this as your idempotency key. |
event_type |
string | One of the values in Event Types. |
api_version |
string | The payload format version this body conforms to (2026-06-11). Pinned per subscription. |
timestamp |
integer | Epoch seconds — when Spry dispatched the webhook. |
data |
object | Identifiers + status for the affected resource. organisation_id is always present; the other fields depend on the event family. No PHI is included — fetch the full record from the Spry API by id. |
HTTP Headers¶
Every webhook POST includes:
| Header | Value | Purpose |
|---|---|---|
Content-Type |
application/json |
— |
X-Sprypt-Signature |
t=<epoch>,v1=<hmac> |
Signature — see Signature Verification |
X-Event-Type |
e.g. appointment.updated |
Mirrors event_type in body |
X-Event-Id |
e.g. evt_8f3a2b9c-... |
Mirrors event_id in body |
X-Delivery-Id |
UUID of this delivery attempt | Quote this when reporting delivery issues |
X-Sprypt-Api-Version |
e.g. 2026-06-11 |
The api_version that produced this payload |
User-Agent |
SpryWebhookService/2.0 |
— |
API Versioning¶
Every webhook payload carries an api_version that pins its format. A subscription is locked to one version at creation time, so a future breaking change to the payload shape won't break you — you keep receiving the format you subscribed to until you explicitly upgrade.
- Choosing a version: pass
api_versionwhen you create a subscription. Omit it to get the latest (2026-06-11). An unknown version is rejected with400 INVALID_API_VERSION. - Pinning: the version is fixed for the life of the subscription. New versions never silently change an existing subscription's payloads.
- Upgrading:
PATCH /api/v2/subscriptions/:idwith a newapi_versionto move a subscription forward.
Acknowledging Webhooks¶
Your endpoint must:
- Accept
POSTwith a JSON body. - Return a
2xxstatus code (typically200 OK). - Respond within 10 seconds.
- Use
event_idas an idempotency key — your endpoint should be safe to call twice with the sameevent_id.
Any other status code, a network error, or a timeout is treated as a failure and triggers the retry schedule below.
Tip: Acknowledge first, process asynchronously. Read the payload into a queue and return
200immediately; do the actual work in a background worker. Webhooks that take more than 10 seconds to process will retry unnecessarily.
Retry Behaviour¶
Failed deliveries are retried on a 5-attempt backoff schedule.
| Attempt | Delay after previous failure | Cumulative time |
|---|---|---|
| 1st (initial) | — | 0 |
| 2nd | 15 seconds | 15 s |
| 3rd | 1 minute | ~1 min 15 s |
| 4th | 10 minutes | ~11 min |
| 5th | 1 hour | ~1 h 11 min |
| Final | 6 hours | ~7 h |
If all retries fail, the delivery is considered failed and is no longer attempted.