Events Catalog
Every event type Medblocks delivers, when it fires, and the shape of event.data.object.
The four event types and their payloads. event.type is the discriminator. After Medblocks.webhooks.constructEvent, TypeScript narrows event.data.object to the right shape.
Envelope
Every delivery uses the same envelope:
{
"id": "evt_01J9YR9N3X4VZ6P2K5RH7M3LMP",
"object": "event",
"type": "patient_flow.completed",
"api_version": "2026-04-25",
"created_at": "2026-04-25T14:35:00.000Z",
"data": { "object": { /* payload shaped by type */ } }
}| Field | Type | Notes |
|---|---|---|
id | string | Prefixed evt_*. Dedupe by this. |
object | "event" | Constant. |
type | enum | One of the four values below. |
api_version | string | The version pinned at endpoint registration. |
created_at | ISO 8601 | When the event was fired. |
data.object | object | Per-type payload. |
The WebhookEvent type re-exported from @medblocks/connect is a discriminated union over type.
patient_flow.completed
Fires when a patient finishes the hosted flow. Whether they connected an EHR, failed authorization, or just clicked Done. data.object is the full PatientFlow.
import type { WebhookEvent } from "@medblocks/connect";
function handle(event: WebhookEvent) {
if (event.type !== "patient_flow.completed") return;
const flow = event.data.object;
const active = flow.connections.filter((c) => c.status === "active");
const failed = flow.connections.filter((c) => c.status === "failed");
console.log(`patient ${flow.patient_id}: ${active.length} active, ${failed.length} failed`);
}data.object field | Type | Notes |
|---|---|---|
id | string | pf_* |
status | "open" | "complete" | "expired" | Browser-flow lifecycle. |
patient_id | string | Your patient id. |
connection_id | string | null | Set when the flow was direct mode. |
recommended_connection_ids | string[] | null | Set when the flow was picker mode with hints. |
connections | array | Authorization attempts inside the flow. See PatientFlow for the field list. |
metadata | object | Whatever you passed at init. |
Use it for: updating your “connected” UI, kicking off downstream onboarding, sending a welcome email.
connection.token_refresh_failed
Fires when a previously active connection’s refresh token stops working. The EHR rejected the refresh and we cleared the stored tokens. The patient needs to reconnect.
data.object is the single PatientFlowConnection that failed (the same shape you see inside PatientFlow.connections):
function handle(event: WebhookEvent) {
if (event.type !== "connection.token_refresh_failed") return;
const conn = event.data.object;
console.warn(`connection ${conn.connection_id} needs reauth`);
// Trigger a "please reconnect" email / push notification
}data.object field | Type | Notes |
|---|---|---|
id | string | null | conn_*. Null only on the original failed-auth attempt. |
connection_id | string | fhirsrc_*. Which EHR. |
status | enum | At fire time, refresh_failed. |
failure_code | string | null | Stable code. |
failure_at | ISO 8601 | null | Timestamp of failure. |
created_at | ISO 8601 | When this connection record was first created. |
Use it for: prompting the patient to start a new PatientFlow against the same source.
records.sync.completed
Fires when a background pull for one (patient, connection) pair finishes successfully and new records have landed.
function handle(event: WebhookEvent) {
if (event.type !== "records.sync.completed") return;
const sync = event.data.object;
console.log(
`patient ${sync.patient_id}: pulled ${sync.total} resources in ${sync.duration_ms ?? "?"}ms`,
);
}data.object field | Type | Notes |
|---|---|---|
resource_type | "records_sync" | Constant discriminator. |
patient_id | string | Your patient id. |
connection_id | string | fhirsrc_*. |
total | number | Resources synced this run. |
errors | number | Sub-resource failures inside the run. |
duration_ms | number | null | Pull duration, when measurable. |
Use it for: triggering downstream processing, refreshing dashboards, telling a workflow the patient is ready.
records.sync.failed
Fires when a background pull errored without recovering.
function handle(event: WebhookEvent) {
if (event.type !== "records.sync.failed") return;
const failure = event.data.object;
alerting.page("medblocks sync failed", {
patient: failure.patient_id,
connection: failure.connection_id,
error: failure.error_message,
});
}data.object field | Type | Notes |
|---|---|---|
resource_type | "records_sync" | Constant. |
patient_id | string | Your patient id. |
connection_id | string | fhirsrc_*. |
error_message | string | null | Best-effort human-readable cause. |
Use it for: paging on-call, surfacing the failure in your ops dashboard, retrying via Medblocks support.
Endpoint Auto-Disable
After the delivery worker exhausts retries on one of your endpoints, that endpoint is flipped to status: "disabled" — no event is emitted for this transition. Detect it by reading status on the endpoint via GET /webhooks/{id} or mb.webhooks.retrieve(id), or by polling mb.webhooks.list() on a cron and alerting on any row with status === "disabled". Re-enable via mb.webhooks.update(id, { status: "active" }). See Retries & Auto-Disable.
Picking The Right Subscription
| You want to know when… | Subscribe to |
|---|---|
| The patient finished the browser flow. | patient_flow.completed |
| A connection broke and needs the patient to reconnect. | connection.token_refresh_failed |
| The patient’s records are ready to query. | records.sync.completed |
| Background pull failed for this patient. | records.sync.failed |
Subscribing to ["*"] delivers all four. Subscribing to a narrow list (max 4 types) is cheaper for your receiver to handle.
Related
- Signatures. Verifying the delivery.
- Managing Endpoints · Create. Programmatic subscription.
- Reference · Register a webhook endpoint. Exact request schema.
