05 — Event Schemas
Status: ✅ Draft
Prerequisite: 04 — Event Catalog
Next: 06 — Authentication Flow
Formal payload schemas for every event type in the catalog. Each schema defines the fields inside the
payloadblock of the standard envelope. The envelope itself is fixed — every event carries it unchanged.Core-published event schemas (C-020 to C-026) are the authoritative contract. Module-published event schemas included here cover the events Core subscribes to — modules own the full definition of all other events they publish.
1. Standard Envelope Schema
Every event on the platform carries this envelope. It does not vary by event type.
{
"eventId": "uuid-v4",
"eventType": "registration.confirmed",
"schemaVersion": "1.0",
"timestamp": "2026-07-15T10:30:00Z",
"idempotencyKey": "uuid-v4",
"sourceModule": "ams",
"miqaatId": "MQ-1447-KHI",
"itsId": "ITS-1234567",
"correlationId": "uuid-v4",
"payload": { }
}
Envelope field definitions
| Field | Type | Required | Description |
|---|---|---|---|
eventId | string (UUID v4) | ✅ | Unique identifier for this event instance. Never reused. |
eventType | string | ✅ | The registered event type name (domain.action notation). |
schemaVersion | string | ✅ | Schema version of the payload block (e.g. "1.0", "2.0"). Consumers use this to select their parser. |
timestamp | string (ISO 8601) | ✅ | When the event was produced — UTC. Set by the publisher at the moment of publish. |
idempotencyKey | string (UUID v4) | ✅ | Consumers use this to detect duplicate delivery. Two events with the same idempotencyKey carry the same logical event — the second delivery is a duplicate and must be discarded. |
sourceModule | string | ✅ | Module identifier of the publisher (e.g. ams, vms, rbac-engine). Matches X-Module-Id in API calls. |
miqaatId | string | ✅ | The Miqaat scope this event belongs to. All events are scoped to a Miqaat. |
itsId | string | ❌ | The itsId of the Mumin this event concerns — present on person-specific events, absent on structural events (e.g. miqaat.phase.changed). |
correlationId | string (UUID v4) | ❌ | Carries the X-Correlation-Id from the triggering API call, if the event was produced as a result of a synchronous request. Enables tracing from API call → event. |
payload | object | ✅ | Event-specific data. Defined per event type below. |
2. Schema Versioning
Version format
schemaVersion follows semantic versioning with two levels: MAJOR.MINOR.
| Change type | Version bump | Consumer impact |
|---|---|---|
New optional field added to payload | MINOR (e.g. 1.0 → 1.1) | Safe — existing consumers ignore unknown fields |
Removing a field from payload | MAJOR (e.g. 1.x → 2.0) | Breaking — consumers must migrate |
| Renaming or retyping a field | MAJOR | Breaking |
| Changing the meaning of an existing field | MAJOR | Breaking |
Parallel version support
When a MAJOR version bump occurs, the old schema version continues to be published in parallel until all declared subscribers have migrated. The transition window is agreed between the Core team and affected module teams before the new version is deployed.
During the parallel window, both 1.x and 2.0 events may be in the stream. Consumers check schemaVersion and route to the appropriate parser.
// Consumer version routing
if (event.schemaVersion.startsWith("1.")) {
parseV1(event.payload);
} else if (event.schemaVersion.startsWith("2.")) {
parseV2(event.payload);
}
Old version support is removed only after Platform Admin confirms all subscribers have migrated. Premature removal of a version is a breaking contract violation.
3. Core-Published Event Schemas (C-020 to C-026)
Core engine event schemas are the authoritative contracts. These are the events modules consume from Core.
miqaat.created · C-020a
Payload
{
"miqaatId": "MQ-1447-KHI",
"name": "Ashara Mubaraka 1447 — Karachi",
"type": "ashara_mubaraka",
"createdAt": "2026-05-01T08:00:00Z",
"createdBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The newly assigned Miqaat identifier |
name | string | ✅ | Display name of the Miqaat |
type | string | ✅ | Miqaat type (ashara_mubaraka, misaaq, safar, etc.) |
createdAt | string (ISO 8601) | ✅ | When Platform Admin created the Miqaat |
createdBy | string | ✅ | itsId of the Platform Admin user who created it |
itsIdis absent from the envelope for this event — this is a structural event, not a person-specific event.
miqaat.phase.changed · C-020
Payload
{
"miqaatId": "MQ-1447-KHI",
"fromPhase": "setup",
"toPhase": "registration_open",
"changedAt": "2026-07-01T00:00:00Z",
"changedBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat whose phase changed |
fromPhase | string | ✅ | The previous phase |
toPhase | string | ✅ | The new active phase |
changedAt | string (ISO 8601) | ✅ | When the transition occurred |
changedBy | string | ✅ | itsId of the Platform Admin user who triggered the transition |
Valid phase values: setup · registration_open · registration_closed · operational · closed · archived
itsIdis absent from the envelope — structural event.
miqaat.archived · C-020b
Payload
{
"miqaatId": "MQ-1447-KHI",
"archivedAt": "2026-11-01T00:00:00Z",
"archivedBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The archived Miqaat |
archivedAt | string (ISO 8601) | ✅ | When the Miqaat was archived |
archivedBy | string | ✅ | itsId of the Platform Admin user who triggered archival |
allocation.confirmed · C-021
Payload
{
"allocationId": "ALO-20261001-00042",
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"category": "residential",
"zone": "KHI-NORTH",
"requestedBy": "ams",
"confirmedAt": "2026-07-15T10:30:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
allocationId | string | ✅ | The allocation record identifier |
itsId | string | ✅ | The Mumin who received the allocation |
miqaatId | string | ✅ | The Miqaat scope |
category | string | ✅ | Allocation category (e.g. residential, day_visitor, khidmatguzar) |
zone | string | ❌ | Zone assigned, if zone-quota allocation mode is active |
requestedBy | string | ✅ | The module that submitted the allocation request |
confirmedAt | string (ISO 8601) | ✅ | When the allocation was confirmed |
allocation.waitlisted · C-022
Payload
{
"allocationId": "ALO-20261001-00043",
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"category": "residential",
"waitlistPosition": 14,
"requestedBy": "ams",
"waitlistedAt": "2026-07-15T10:30:01Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
allocationId | string | ✅ | The allocation record identifier (waitlisted state) |
itsId | string | ✅ | The Mumin placed on the waitlist |
miqaatId | string | ✅ | The Miqaat scope |
category | string | ✅ | Allocation category |
waitlistPosition | integer | ✅ | Current position in the waitlist at time of placement |
requestedBy | string | ✅ | The module that submitted the request |
waitlistedAt | string (ISO 8601) | ✅ | When the Mumin was placed on the waitlist |
allocation.released · C-022a
Payload
{
"allocationId": "ALO-20261001-00042",
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"category": "residential",
"reason": "cancelled_by_mumin",
"releasedAt": "2026-08-01T09:00:00Z",
"releasedBy": "ITS-1234567",
"nextWaitlistId": "ITS-9876543"
}
| Field | Type | Required | Description |
|---|---|---|---|
allocationId | string | ✅ | The allocation that was released |
itsId | string | ✅ | The Mumin whose allocation was released |
miqaatId | string | ✅ | The Miqaat scope |
category | string | ✅ | Allocation category |
reason | string | ✅ | Why the allocation was released. Values: cancelled_by_mumin · revoked_by_operator · capacity_change |
releasedAt | string (ISO 8601) | ✅ | When the release occurred |
releasedBy | string | ✅ | itsId of the person who triggered the release |
nextWaitlistId | string | ❌ | itsId of the next person on the waitlist who will be promoted. Absent if waitlist is empty. |
capacity.imbalance.detected · C-023
Payload
{
"miqaatId": "MQ-1447-KHI",
"detectedAt": "2026-06-12T08:00:01Z",
"imbalances": [
{
"description": "Vaaz capacity (8,000) is more than double Mawaid capacity (3,200). Ratio exceeds threshold.",
"venueA": "vaaz",
"capacityA": 8000,
"venueB": "mawaid",
"capacityB": 3200,
"ratio": 2.5,
"threshold": 2.0
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat where the imbalance was detected |
detectedAt | string (ISO 8601) | ✅ | When the imbalance was detected |
imbalances | array | ✅ | One object per detected imbalance |
imbalances[].description | string | ✅ | Human-readable description for Platform Admin display |
imbalances[].venueA | string | ✅ | First venue in the comparison |
imbalances[].capacityA | integer | ✅ | Capacity reported by venueA |
imbalances[].venueB | string | ✅ | Second venue in the comparison |
imbalances[].capacityB | integer | ✅ | Capacity reported by venueB |
imbalances[].ratio | number | ✅ | Actual ratio (capacityA / capacityB) |
imbalances[].threshold | number | ✅ | The configured threshold that was exceeded |
itsIdis absent from the envelope — structural event, not person-specific.
eligibility.status.changed · C-024
Payload
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"ruleSetId": "ISTEFADAH_2026_GENTS",
"previousStatus": "eligible",
"newStatus": "ineligible",
"changedAt": "2026-07-20T14:00:00Z",
"reason": "PRIOR_ATTENDANCE rule failed after ITS sync update"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin whose eligibility changed |
miqaatId | string | ✅ | The Miqaat scope |
ruleSetId | string | ✅ | The rule set whose evaluation changed |
previousStatus | string | ✅ | The eligibility status before this change. Values: eligible · ineligible · unknown |
newStatus | string | ✅ | The new eligibility status |
changedAt | string (ISO 8601) | ✅ | When the status changed |
reason | string | ❌ | Human-readable description of what caused the status change — for logging and audit, not for display |
role.assigned · C-025
Payload
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"roleId": "VMS_Coordinator",
"assignedAt": "2026-06-01T08:00:00Z",
"assignedBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin who was assigned the role |
miqaatId | string | ✅ | The Miqaat scope the role applies in |
roleId | string | ✅ | The role identifier assigned |
assignedAt | string (ISO 8601) | ✅ | When the assignment occurred |
assignedBy | string | ✅ | itsId of the Platform Admin user who made the assignment |
role.revoked · C-026
Payload
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"roleId": "VMS_Coordinator",
"revokedAt": "2026-09-15T10:00:00Z",
"revokedBy": "ITS-9999999",
"reason": "Role reassigned to different coordinator"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin whose role was revoked |
miqaatId | string | ✅ | The Miqaat scope |
roleId | string | ✅ | The role that was revoked |
revokedAt | string (ISO 8601) | ✅ | When the revocation occurred |
revokedBy | string | ✅ | itsId of the Platform Admin user who revoked it |
reason | string | ❌ | Optional reason for revocation |
Cache invalidation: any module that receives
role.revokedmust immediately clear its local permission cache for the affecteditsId+miqaatId. See 03 — API Contracts, C-010.
4. Module-Published Event Schemas
Core subscribes to the events below. The schema here is the minimum Core requires from the payload. Modules may include additional fields — Core ignores unknown fields.
Module-published events that Core does not subscribe to (most of section 4 of the Event Catalog) are owned entirely by the module. Core does not define their payload schema.
registration.confirmed
Core subscriber: Eligibility Engine, HR Bank, Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"registrationId": "REG-AMS-2026-00891",
"registrationType": "residential",
"confirmedAt": "2026-07-10T12:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin whose registration was confirmed |
miqaatId | string | ✅ | The Miqaat scope |
registrationId | string | ✅ | The module's own registration record identifier |
registrationType | string | ✅ | Type of registration (e.g. residential, day_visitor, istefadah) |
confirmedAt | string (ISO 8601) | ✅ | When the registration was confirmed |
attendance.recorded
Core subscriber: Eligibility Engine (prior attendance rules), HR Bank, Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"sessionId": "SESSION-WAAZ-D3",
"sessionType": "waaz",
"recordedAt": "2026-10-03T09:30:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin whose attendance was recorded |
miqaatId | string | ✅ | The Miqaat scope |
sessionId | string | ✅ | The module's identifier for the session (day, vaaz, majlis, etc.) |
sessionType | string | ✅ | Type of session (e.g. waaz, majlis, khidmat_shift, general) |
recordedAt | string (ISO 8601) | ✅ | When attendance was recorded |
khidmat.assigned
Core subscriber: HR Bank (builds cross-Miqaat history record), Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"khidmatRole": "VMS_Coordinator",
"isPrimary": true,
"assignedAt": "2026-06-15T10:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin assigned to the khidmat |
miqaatId | string | ✅ | The Miqaat scope |
khidmatRole | string | ✅ | The khidmat role identifier — should align with the RBAC role name where applicable |
isPrimary | boolean | ✅ | Whether this is the Mumin's primary khidmat for this Miqaat (governs the one-primary-khidmat rule) |
assignedAt | string (ISO 8601) | ✅ | When the assignment was made |
khidmat.completed
Core subscriber: HR Bank (marks history record as completed), Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"khidmatRole": "VMS_Coordinator",
"completedAt": "2026-10-10T20:00:00Z",
"confirmedBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin who completed the khidmat |
miqaatId | string | ✅ | The Miqaat scope |
khidmatRole | string | ✅ | The khidmat role that was completed |
completedAt | string (ISO 8601) | ✅ | When the khidmat was completed |
confirmedBy | string | ✅ | itsId of the coordinator who confirmed completion |
capacity.configured
Core subscriber: Capacity Balance Engine, Audit Log
Minimum payload required by Core
{
"miqaatId": "MQ-1447-KHI",
"venue": "vaaz",
"capacity": 8000,
"configuredAt": "2026-06-10T09:00:00Z",
"isFinal": true
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope |
venue | string | ✅ | The venue or service type reporting capacity (e.g. vaaz, mawaid, kitchen, accommodation) |
capacity | integer | ✅ | The capacity figure being reported |
configuredAt | string (ISO 8601) | ✅ | When this figure was set |
isFinal | boolean | ✅ | Whether this is a final figure or a provisional estimate. Capacity Balance Engine only alerts on isFinal: true configurations. |
If
isFinal: false, the Capacity Balance Engine records the figure but does not trigger imbalance detection until a final figure is received from each expected venue.
payment.received
Core subscriber: Eligibility Engine (payment-gate rule family), Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"paymentId": "PAY-2026-00312",
"paymentType": "vajebaat",
"amount": 1500,
"currency": "INR",
"receivedAt": "2026-07-12T14:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin for whom payment was received |
miqaatId | string | ✅ | The Miqaat scope |
paymentId | string | ✅ | The module's payment record identifier |
paymentType | string | ✅ | Type of payment (e.g. vajebaat, registration_fee, accommodation_fee) |
amount | number | ✅ | Amount received |
currency | string | ✅ | ISO 4217 currency code (e.g. INR, PKR, USD) |
receivedAt | string (ISO 8601) | ✅ | When payment was confirmed received and cleared |
misaaq.recorded
Core subscriber: Eligibility Engine (Misaaq rule family), Audit Log
Minimum payload required by Core
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"recordedAt": "2026-10-05T11:00:00Z",
"recordedBy": "ITS-9999999"
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin for whom Misaaq was recorded |
miqaatId | string | ✅ | The Miqaat scope at which Misaaq was taken |
recordedAt | string (ISO 8601) | ✅ | When the Misaaq was recorded |
recordedBy | string | ✅ | itsId of the authorised user who recorded it |
indent.raised
Core subscriber: Vendor Registry (budget tracking, conflict detection), Audit Log
Minimum payload required by Core
{
"miqaatId": "MQ-1447-KHI",
"indentId": "INDENT-AMS-2026-0041",
"category": "catering",
"vendorId": "VND-KHI-00142",
"amount": 50000,
"currency": "INR",
"raisedAt": "2026-08-01T10:00:00Z",
"raisedBy": "ITS-1234567"
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope |
indentId | string | ✅ | The module's indent record identifier |
category | string | ✅ | Procurement category (e.g. catering, seating, transport, printing) |
vendorId | string | ❌ | The vendor from Core's Vendor Registry, if a specific vendor is named |
amount | number | ✅ | Proposed procurement amount |
currency | string | ✅ | ISO 4217 currency code |
raisedAt | string (ISO 8601) | ✅ | When the indent was raised |
raisedBy | string | ✅ | itsId of the user who raised the indent |
5. Schema Registry and Registration Process
The Domain Event Schema Registry (see 01 — Service Decomposition) is the authoritative store for all registered event schemas.
Before a module can publish an event:
- Module team submits the proposed event type name and payload schema to Core team
- Core team reviews: naming convention compliance, no collision with existing event types, minimum required fields present
- Schema is registered in the Domain Event Schema Registry with
schemaVersion: "1.0" - Module team is notified — the registered event type name is now the permanent contract identifier
Schema updates:
MINORadditions — module team notifies Core team; schema is updated in the registry; no consumer impactMAJORchanges — Core team coordinates the migration window with all declared subscribers before the new schema is registered
6. Open Questions
| Question | Blocked On |
|---|---|
Full payload schema for module-published events not consumed by Core (e.g. checkpoint.scanned, pass.generated) | Module discovery sessions — modules own these schemas |
schemaVersion format: should patch version be added (1.0.0)? | Architecture decision — current MAJOR.MINOR may be sufficient |
| Whether Core validates incoming module event payloads against the registry schema, or accepts and logs errors | Infrastructure / event bus session |
Misaaq rule set mapping — which specific rule sets does the Eligibility Engine react to on misaaq.recorded? | Business session |