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 */ } }
}
FieldTypeNotes
idstringPrefixed evt_*. Dedupe by this.
object"event"Constant.
typeenumOne of the four values below.
api_versionstringThe version pinned at endpoint registration.
created_atISO 8601When the event was fired.
data.objectobjectPer-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.

webhook-handler.ts
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 fieldTypeNotes
idstringpf_*
status"open" | "complete" | "expired"Browser-flow lifecycle.
patient_idstringYour patient id.
connection_idstring | nullSet when the flow was direct mode.
recommended_connection_idsstring[] | nullSet when the flow was picker mode with hints.
connectionsarrayAuthorization attempts inside the flow. See PatientFlow for the field list.
metadataobjectWhatever 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):

webhook-handler.ts
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 fieldTypeNotes
idstring | nullconn_*. Null only on the original failed-auth attempt.
connection_idstringfhirsrc_*. Which EHR.
statusenumAt fire time, refresh_failed.
failure_codestring | nullStable code.
failure_atISO 8601 | nullTimestamp of failure.
created_atISO 8601When 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.

webhook-handler.ts
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 fieldTypeNotes
resource_type"records_sync"Constant discriminator.
patient_idstringYour patient id.
connection_idstringfhirsrc_*.
totalnumberResources synced this run.
errorsnumberSub-resource failures inside the run.
duration_msnumber | nullPull 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.

webhook-handler.ts
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 fieldTypeNotes
resource_type"records_sync"Constant.
patient_idstringYour patient id.
connection_idstringfhirsrc_*.
error_messagestring | nullBest-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.