Appearance
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:
| Field | Purpose |
|---|---|
id | Unique identifier of the event; the key for idempotent processing on the receiving side. |
type | Dotted event type, for example medipal.submission.created. |
source | The instance and component that produced the event. |
subject | Identifier of the affected resource, when there is one. |
time | When the change happened, in UTC. |
data | The payload: the resource as it was after the change, plus context that consumers commonly need. |
| correlation and causation | Extension 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:
| Family | Examples |
|---|---|
| Submissions | created, updated, deleted, anonymous submission created |
| Questionnaires | created, updated, published, archived, restored, deleted |
| Folders | created, updated, deleted |
| Users | created, updated, deleted, password changed |
| Authentication | sign-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 deliveriesTransactional 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:
| Status | Meaning |
|---|---|
PENDING | Waiting to be processed, or waiting for the next retry. |
IN_PROGRESS | Being processed right now. |
DONE | Delivered and acknowledged. |
FAILED | The last attempt failed; another attempt is scheduled while the retry budget allows. |
DEAD_LETTER | The 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
timeattribute 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
- Configuring webhooks and reading the delivery log: Webhooks & Subscriptions.
- Reacting to events inside the platform: Workflows and the Workflow Engine.
- The HTTP contract for receivers: Events & Webhooks.