Skip to main content

EVENT_SCHEMAS — bff-tenant-booking-service

Sibling: API_CONTRACTS · DATA_MODEL · DOMAIN_MODEL

Cross-cutting: 04 Event-Driven Architecture · Standards · NAMING

1. Scope and posture

This BFF emits only telemetry events under the melmastoon.bff.tenant.* subject prefix. It emits no domain events — the booking domain belongs to reservation-service, money belongs to payment-gateway-service, theme belongs to theme-config-service. The BFF subscribes to a small set of platform events solely for cache invalidation.

AspectPosture
BackboneGCP Pub/Sub
OutboxPostgres outbox table (per-service, drained by outbox-relay worker)
Delivery semanticsAt-least-once; consumers must dedupe by eventId
Subject patternmelmastoon.bff.tenant.<aggregate>.<verb-past-tense>.v<n>
Retention classoperational for bootstrap.*, flow.*, locale.*, currency.*; audit for handoff.consumed.v1; regulated for booking.draft.converted.v1, payment_intent.created.v1, confirmation.viewed.v1 (regulated because they are evidence of consumer-facing booking events)
Schema registrySchemas live in @ghasi/event-envelope/schemas/bff-tenant/ and are validated in CI
PII postureGuest fields hashed unless explicitly opted-in for analytics; raw email/phone NEVER on telemetry

2. Event envelope

{
"envelope": {
"eventId": "evt_01H8YN7QV4D8KZ4F8Y5CK4MV3D",
"subject": "melmastoon.bff.tenant.booking.draft.created.v1",
"version": 1,
"occurredAt": "2026-04-23T09:14:22.041Z",
"publishedAt": "2026-04-23T09:14:22.058Z",
"producer": "bff-tenant-booking-service",
"producerInstance": "bff-tenant-asia-south1-7f8d9c-x4z2",
"tenantId": "tnt_01H8Y...",
"userId": null,
"sessionId": "tnt_session_01H8Y...",
"requestId": "req_01H8YN7QF9RTBRZG4F8Y5CK4MV",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"causationId": "evt_01H8YN7QV4D8KZ4F8Y5CK4MV3C",
"correlationId": "req_01H8YN7QF9RTBRZG4F8Y5CK4MV",
"schemaUri": "https://schemas.melmastoon.ghasi.io/bff-tenant/booking-draft-created/v1.json",
"retentionClass": "regulated",
"samplingRate": 1.0,
"marketingAttribution": { "source": "google", "medium": "cpc", "campaign": "spring-2026" }
},
"payload": { /* event-specific */ }
}

tenantId is always non-null. marketingAttribution is added at the envelope level for funnel reconstruction across funnel-step events.

3. Events published

3.1 melmastoon.bff.tenant.bootstrap.served.v1

When: GET /bootstrap succeeded for a session (debounced at 1× per session per 5 min). Sample rate: 100% (after debounce) Retention: operational (90 days)

{
"tenantId": "tnt_01H8Y...",
"tenantSlug": "kabul-grand-hotel",
"sessionId": "tnt_session_01H8Y...",
"locale": "ps-AF",
"currency": "AFN",
"themeVersion": "thm_01H8Y_v3",
"tenantConfigVersion": "v12",
"fanoutLatencyMs": { "tenant": 14, "theme": 38, "policies": 22 },
"cache": "MISS",
"handoffArrivalId": "bha_01H8Y..."
}

3.2 melmastoon.bff.tenant.handoff.consumed.v1

When: Inbound BookingHandoffArrival consumed. Sample rate: 100% Retention: audit (1 year)

{
"tenantId": "tnt_01H8Y...",
"handoffArrivalId": "bha_01H8Y...",
"consumerSessionId": "gms_01H8Y...",
"propertyId": "ppt_01H8Y...",
"campaign": { "source": "google", "medium": "cpc", "campaign": "spring-2026" },
"mintedAt": "2026-04-23T09:14:00.000Z",
"consumedAt": "2026-04-23T09:14:22.000Z",
"elapsedMs": 22000,
"hmacSignatureFingerprint": "sha256:9c8b..."
}

3.3 melmastoon.bff.tenant.booking.draft.created.v1

When: First successful POST /hold for a session. Sample rate: 100% Retention: regulated (3 years)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"reservationId": "rsv_01H8Y...",
"holdExpiresAt": "2026-04-23T10:14:22.041Z",
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"ratePlanId": "rate_01H8Y...",
"stayWindow": { "checkIn": "2026-05-20", "checkOut": "2026-05-22", "nights": 2 },
"occupancy": { "adults": 2, "children": 0, "rooms": 1 },
"totalDisplay": { "currency": "AFN", "amountMinor": 12500 },
"promoCode": "SPRING25",
"deviceClass": "browser-mobile",
"handoffArrivalId": "bha_01H8Y..."
}

3.4 melmastoon.bff.tenant.booking.draft.abandoned.v1

When: Draft TTL expired without conversion (sweep job emits). Sample rate: 100% Retention: regulated (3 years)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"lastFlowState": "collecting_details",
"createdAt": "2026-04-23T09:14:22.041Z",
"abandonedAt": "2026-04-23T09:46:11.000Z",
"lastActivityAt": "2026-04-23T09:18:00.000Z",
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"totalDisplayPotential": { "currency": "AFN", "amountMinor": 12500 },
"errorTrail": [],
"guestEmailHash": "sha256:..."
}

3.5 melmastoon.bff.tenant.booking.draft.converted.v1

When: Draft transitions to confirmed. Sample rate: 100% Retention: regulated (7 years)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"reservationId": "rsv_01H8Y...",
"createdAt": "2026-04-23T09:14:22.041Z",
"convertedAt": "2026-04-23T09:21:33.000Z",
"elapsedMs": 431000,
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"ratePlanId": "rate_01H8Y...",
"totalCapturedMinor": 12500,
"totalCapturedCurrency": "AFN",
"paymentMethod": "card",
"paymentProvider": "adyen",
"marketingAttribution": { "source": "google", "medium": "cpc", "campaign": "spring-2026" },
"handoffArrivalId": "bha_01H8Y...",
"deviceClass": "browser-mobile"
}

3.6 melmastoon.bff.tenant.payment_intent.created.v1

When: PaymentIntent created via gateway. Sample rate: 100% Retention: regulated (7 years)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"intentId": "pyi_01H8Y...",
"method": "card",
"provider": "adyen",
"amountMinor": 12500,
"currency": "AFN",
"redirectFlow": true,
"createdAt": "2026-04-23T09:18:11.000Z"
}

3.7 melmastoon.bff.tenant.confirmation.viewed.v1

When: GET /confirmation/{reservationId} rendered. Sample rate: 100% (debounced 1× per reservation per session per 24 h) Retention: operational (90 days)

{
"tenantId": "tnt_01H8Y...",
"reservationId": "rsv_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"viewedAt": "2026-04-23T09:21:55.000Z",
"elapsedFromConfirmMs": 22000,
"deviceClass": "browser-mobile",
"locale": "ps-AF",
"cache": "HIT"
}

3.8 melmastoon.bff.tenant.flow.step_completed.v1

When: Flow step transitioned forward. Sample rate: 25% (high-cardinality) Retention: operational (30 days)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"fromState": "quoting",
"toState": "holding",
"stepLatencyMs": 412,
"completedAt": "2026-04-23T09:14:22.041Z"
}

3.9 melmastoon.bff.tenant.flow.error_encountered.v1

When: Any flow step returned an error visible to the user. Sample rate: 100% Retention: operational (90 days)

{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"atState": "paying",
"errorCode": "MELMASTOON.PAYMENT.DECLINED",
"upstream": "payment-gateway-service",
"occurredAt": "2026-04-23T09:18:33.000Z",
"userActionable": true,
"retryAfterSec": 0
}

3.10 melmastoon.bff.tenant.locale.changed.v1

When: Locale changed mid-session. Sample rate: 100% Retention: operational (90 days)

{
"tenantId": "tnt_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"from": "ps-AF",
"to": "fa-AF",
"isRtlChange": false,
"changedAt": "2026-04-23T09:14:22.041Z"
}

3.11 melmastoon.bff.tenant.currency.changed.v1

When: Display currency changed. Sample rate: 100% Retention: operational (90 days)

{
"tenantId": "tnt_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"from": "AFN",
"to": "USD",
"draftId": "bdr_01H8Y...",
"changedAt": "2026-04-23T09:14:22.041Z"
}

4. Events consumed

The BFF consumes platform events solely for cache invalidation:

SubjectEffect
melmastoon.theme.published.v1cache.invalidate('tenant-bootstrap:<tenantId>:*')
melmastoon.tenant.config_updated.v1invalidate bootstrap + slug map for the tenant
melmastoon.tenant.suspended.v1mark slug suspended in cache; subsequent bootstrap returns 503
melmastoon.pricing.rate_plan.published.v1invalidate cheapest: + availability: for affected (tenantId, propertyId)
melmastoon.inventory.allocation.committed.v1invalidate availability: for affected (tenantId, propertyId, dateRange)
melmastoon.iam.session.revoked.v1 (Phase 2+)drop loyalty context for matching authenticated session

Subscriptions named: bff-tenant-booking.<topic>. Inbox table dedupes by eventId.

5. PII rules

  • email, phone, nationalIdNumber are NEVER in raw form on telemetry. SHA-256 hash with per-environment pepper, prefixed sha256:.
  • firstName, lastName are NEVER in telemetry; analytics receives only guestNameInitialsHash.
  • address and country (when collected for billing) are NEVER in telemetry.
  • marketingAttribution is acceptable in plain text (UTM is intentionally non-sensitive).

PII test (test/integration/telemetry-pii.spec.ts) asserts every event payload against a strict allow-list and fails CI if any disallowed field appears.

6. Sampling configuration

Sample rates are configuration, not code. They live in bff-tenant-flags Memorystore key, refreshable in 30 s. Defaults:

SubjectDefault sample rateOverride conditions
bootstrap.served.v11.0 (after 5-min session debounce)none
handoff.consumed.v11.0always
booking.draft.created.v11.0always
booking.draft.abandoned.v11.0always
booking.draft.converted.v11.0always
payment_intent.created.v11.0always
confirmation.viewed.v11.0 (debounced)always
flow.step_completed.v10.25raise to 1.0 during incident analysis
flow.error_encountered.v11.0always
locale.changed.v11.0always
currency.changed.v11.0always

7. Schema evolution rules

  • Additive fields ship under same v1.
  • Removed or renamed fields require a new v2 topic; v1 retained for at least 90 days with dual-publish.
  • Type changes always require v2.
  • Schema diff CI gate enforces.

8. Replay + back-fill

Telemetry events have no replay semantics for downstream consumers; analytics consumers idempotently aggregate by (eventId, draftId). If a telemetry event is lost (impossible with at-least-once + outbox + DLQ, but defensively), no domain effect occurs.

9. DLQ posture

All bff-tenant.* topics have a DLQ subscription bff-tenant-dlq.<topic> with 7-day retention. Failed delivery alerts at queue-depth > 50.