Skip to content

Events & Delivery ​

Every meaningful change on an instance is recorded as an event. Events are the backbone of automation: workflows react to them inside the platform, webhooks push them to external systems, and in-process handlers keep derived data up to date. This page explains how events are produced, stored and delivered.

The event envelope ​

Events follow the CloudEvents structure. Each event carries:

FieldPurpose
idUnique identifier of the event; the key for idempotent processing on the receiving side.
typeDotted event type, for example medipal.submission.created.
sourceThe instance and component that produced the event.
subjectIdentifier of the affected resource, when there is one.
timeWhen the change happened, in UTC.
dataThe payload: the resource as it was after the change, plus context that consumers commonly need.
correlation and causationExtension attributes linking events that belong to the same user action, or that were caused by an earlier event.

Event families ​

Event types are grouped by the resource they describe. The current families are:

FamilyExamples
Submissionscreated, updated, deleted, anonymous submission created
Questionnairescreated, updated, published, archived, restored, deleted
Folderscreated, updated, deleted
Userscreated, updated, deleted, password changed
Authenticationsign-in succeeded or failed through a provider, token refreshed, MFA enabled, MFA challenge failed, password reset requested and completed

The full list with the exact type strings is available from the instance's event definition endpoint and in the web app's subscription editor. New families are added as the platform grows; consumers should ignore unknown types rather than fail on them.

From change to delivery ​

API request or background job
   │  (one database transaction)
   ├─ write the change
   └─ write the event to the outbox
                │
       outbox drain (background job, frequent)
                │
   ┌────────────┼──────────────────┐
   ▼            ▼                  ▼
in-process   event subscriptions   webhooks
handlers     → workflow runs       → signed HTTP deliveries

Transactional outbox. An event is written to the database in the same transaction as the change it describes. If the transaction rolls back, the event never existed; if it commits, the event will be delivered. A background job drains the outbox and turns each event into delivery work. The guarantee is at-least-once: an event can be delivered more than once (for example after a crash between delivery and acknowledgement), never zero times.

In-process handlers. Some events are consumed by the server itself (for example to keep counters or derived records current). These handlers run during the drain and are best-effort: a handler error is logged and does not block routing to subscriptions or webhooks.

Subscriptions. Enabled event subscriptions whose event type matches are evaluated in order: type match, then optional source and subject equality filters, then an optional condition expressed in JSON Logic against the whole event. Every matching subscription starts its own workflow run; one subscription failing does not affect the others.

Webhooks. Every enabled webhook whose event type list contains the type (or all types) receives a signed HTTP delivery. The request format and signature verification are described in Events & Webhooks.

Delivery tasks ​

Each unit of delivery work is tracked as a delivery task with a status:

StatusMeaning
PENDINGWaiting to be processed, or waiting for the next retry.
IN_PROGRESSBeing processed right now.
DONEDelivered and acknowledged.
FAILEDThe last attempt failed; another attempt is scheduled while the retry budget allows.
DEAD_LETTERThe retry budget is exhausted; the task is kept for inspection and manual retry.

Webhook deliveries keep their own record per webhook and event (pending, success, failed, dead letter), with the HTTP status, the response body and the number of attempts, so the delivery log in the web app can show exactly what happened. Failed deliveries are retried with exponential backoff until the retry budget is used up; a manual retry is always possible from the log.

Completed tasks and old delivery records are removed by periodic cleanup jobs after a retention period, which keeps the tables small without losing recent history. See Background Jobs.

Ordering and idempotency ​

  • Events are drained in the order they were written, but retries, parallel workers and network latency mean consumers must not rely on arrival order. Use the time attribute and the resource's own timestamps to reason about sequence.
  • Because delivery is at-least-once, consumers should treat the event id (or the delivery identifier header on webhooks) as an idempotency key and ignore repeats.
  • The payload describes the resource at the time of the change. When a consumer needs the current state, it should fetch the resource through the API rather than reconstruct it from a series of events.

Where to look ​