API Reference
PBCP
Event Catalog

Event Catalog

Event คือ business fact ที่เกิดขึ้นในระบบ — immutable, append-only ทุก event ที่ส่งเข้า PBCP ต้องตาม convention นี้

Naming convention

{entity}.{action}
{namespace}.{entity}.{action}   ← สำหรับ multi-service system

ตัวอย่าง:

  • order.created
  • customer.registered
  • pharmacy.prescription.dispensed ✅ (namespaced)
  • OrderCreated ❌ (ไม่ใช่ snake_case)
  • created_order ❌ (entity ต้องมาก่อน)

Event envelope (ทุก domain ใช้เหมือนกัน)

{
  "schemaVersion": "1.0",
  "tenantId": "your-tenant-id",
  "eventId": "uuid-v4",
  "eventName": "order.created",
  "occurredAt": "2026-07-15T10:00:00.000Z",
  "actor": {
    "type": "user | system | staff | device",
    "id": "actor-id"
  },
  "source": {
    "system": "your-system-name",
    "channel": "web | mobile | pos | api | iot"
  },
  "payload": {
    // domain-specific fields
  }
}

Pharmacy

Tenant: pharmacy-demo (pilot) | {pharmacy-slug} (production)

Eventactor.typepayload fields
customer.registeredstaff / systemcustomerId, channel, registeredVia
customer.visitedsystemcustomerId, visitType
product.searchedstaffquery, resultCount
product.viewedstaffproductId, sku, name
cart.updatedstaffcustomerId, lineItems[], total
prescription.createdstaffprescriptionId, doctorName, items[]
prescription.dispensedstaffprescriptionId, customerId, items[]
order.createdsystemorderId, customerId, lineItems[], total
order.completedsystemorderId, paidAt, paymentMethod
payment.completedsystemorderId, amount, currency, method
loyalty.points_earnedsystemcustomerId, points, reason
coupon.redeemedstaffcustomerId, couponCode, discountAmount

Payload example — prescription.dispensed:

{
  "prescriptionId": "RX-20260715-001",
  "customerId": "CUST-1",
  "doctorName": "พญ. สมหญิง รักสุขภาพ",
  "items": [
    { "productId": "PROD-42", "name": "Amlodipine 5mg", "qty": 30, "unit": "เม็ด" }
  ],
  "dispensedBy": "staff-7"
}

E-commerce

Eventactor.typepayload fields
customer.registeredsystemcustomerId, registeredVia
session.startedsystemsessionId, customerId?, channel, device
product.viewedsystemproductId, sku, name, price, categoryId
product.searchedsystemquery, resultCount, filters
product.wishlistedsystemcustomerId, productId
cart.item_addedsystemcustomerId, productId, qty, price
cart.item_removedsystemcustomerId, productId, reason?
cart.abandonedsystemcustomerId, cartTotal, itemCount
checkout.startedsystemcustomerId, cartTotal
payment.initiatedsystemorderId, amount, method
payment.completedsystemorderId, amount, currency, method
order.createdsystemorderId, customerId, lineItems[], total
order.shippedsystemorderId, trackingNumber, carrier
order.deliveredsystemorderId, deliveredAt
return.requestedsystemorderId, reason, items[]
coupon.appliedsystemcustomerId, couponCode, discountAmount

Payload example — cart.abandoned:

{
  "customerId": "CUST-99",
  "sessionId": "SES-abc",
  "cartTotal": 1850,
  "itemCount": 3,
  "lastProductViewed": "PROD-101",
  "minutesInCart": 12
}

Logistics

Eventactor.typepayload fields
shipment.createdsystemshipmentId, senderId, recipientId, origin, destination
shipment.assignedsystemshipmentId, driverId, vehicleId
shipment.picked_updrivershipmentId, driverId, pickedUpAt, location
shipment.in_transitsystemshipmentId, currentLocation, eta
shipment.out_for_deliverydrivershipmentId, driverId, departedHub
shipment.delivery_attempteddrivershipmentId, attemptNumber, reason, location
shipment.delivereddrivershipmentId, deliveredAt, signedBy?, proofUrl?
shipment.faileddrivershipmentId, reason, returnToHub
shipment.returnedsystemshipmentId, returnedAt
checkpoint.scanneddeviceshipmentId, checkpointId, checkpointName, scannedAt
driver.shift_starteddriverdriverId, shiftId, vehicleId
driver.shift_endeddriverdriverId, shiftId, deliveriesCompleted, failedCount
customer.notifiedsystemshipmentId, channel, messageType, eta

Payload example — shipment.delivery_attempted:

{
  "shipmentId": "SHP-20260715-0042",
  "driverId": "DRV-7",
  "attemptNumber": 1,
  "reason": "recipient_not_home",
  "location": { "lat": 13.7563, "lng": 100.5018 },
  "nextAttemptScheduled": "2026-07-16T10:00:00.000Z"
}

SaaS / App

Eventactor.typepayload fields
user.signed_upsystemuserId, email, plan, signupVia
user.verified_emailsystemuserId
onboarding.step_completeduseruserId, step, stepName, duration
onboarding.completedsystemuserId, totalDays
session.startedsystemuserId, sessionId, platform, device
feature.useduseruserId, featureName, context?
feature.first_usedsystemuserId, featureName
workspace.createduserworkspaceId, userId, name
workspace.member_inviteduserworkspaceId, invitedEmail, role
workspace.member_joinedsystemworkspaceId, userId
subscription.upgradeduseruserId, fromPlan, toPlan
subscription.downgradeduseruserId, fromPlan, toPlan, reason?
subscription.cancelleduseruserId, plan, reason, cancelledAt
payment.completedsystemuserId, amount, plan, period
api.calledsystemuserId, endpoint, method, statusCode
integration.connecteduseruserId, integrationName

Payload example — subscription.cancelled:

{
  "userId": "usr_abc123",
  "plan": "pro",
  "reason": "too_expensive",
  "feedback": "ราคาสูงเกินไปสำหรับทีมเล็ก",
  "cancelledAt": "2026-07-15T09:00:00.000Z",
  "daysActive": 127
}

Education

Eventactor.typepayload fields
student.registeredsystemstudentId, email, registeredVia
enrollment.createdstudentstudentId, courseId, courseName, plan
lesson.startedstudentstudentId, courseId, lessonId, lessonName
lesson.completedsystemstudentId, lessonId, watchPercent, durationMs
lesson.skippedstudentstudentId, lessonId, reason?
quiz.startedstudentstudentId, quizId, lessonId
quiz.submittedstudentstudentId, quizId, answers, score
quiz.passedsystemstudentId, quizId, score, passingScore, attemptNumber
quiz.failedsystemstudentId, quizId, score, passingScore, attemptNumber
assignment.submittedstudentstudentId, assignmentId, submittedAt
assignment.gradedinstructorassignmentId, score, feedback
course.completedsystemstudentId, courseId, completionDate, totalDays
certificate.issuedsystemstudentId, courseId, certificateId, issuedAt
discussion.postedstudentstudentId, lessonId, postId
live_session.attendedsystemstudentId, sessionId, joinedAt, duration
enrollment.cancelledstudentstudentId, courseId, reason?, refundRequested

Payload example — quiz.failed:

{
  "studentId": "STU-001",
  "quizId": "QUIZ-L2-01",
  "lessonId": "L-02",
  "courseId": "python-beginners",
  "score": 55,
  "passingScore": 70,
  "attemptNumber": 2,
  "wrongTopics": ["loops", "list_comprehension"]
}

Beauty

Eventactor.typepayload fields
customer.registeredsystemcustomerId, registeredVia, referredBy?
consultation.completedstaffcustomerId, staffId, skinType, concerns[], sensitivities[]
appointment.bookedsystem / customercustomerId, appointmentId, treatmentId, staffId?, scheduledAt
appointment.confirmedstaffappointmentId, confirmedBy
appointment.reminder_sentsystemappointmentId, customerId, channel, scheduledAt
appointment.checked_instaffappointmentId, customerId, checkedInAt
appointment.no_showsystemappointmentId, customerId
appointment.cancelledcustomer / staffappointmentId, reason, cancelledBy
treatment.startedstaffappointmentId, treatmentId, treatmentName, staffId
treatment.completedstaffappointmentId, treatmentId, durationMin, staffId
product.recommendedstaffcustomerId, staffId, productId, productName, reason
product.purchasedsystemcustomerId, productId, qty, amount, linkedAppointmentId?
review.submittedcustomercustomerId, appointmentId, rating, comment?, staffId?
membership.enrolledsystemcustomerId, tier, startDate
membership.upgradedsystemcustomerId, fromTier, toTier
package.purchasedsystemcustomerId, packageId, packageName, sessions, amount
package.session_usedstaffcustomerId, packageId, appointmentId, sessionsRemaining
loyalty.points_earnedsystemcustomerId, points, reason, totalPoints
coupon.redeemedstaffcustomerId, couponCode, discountAmount, appointmentId?
referral.madesystemreferrerId, referredCustomerId

Payload example — treatment.completed:

{
  "appointmentId": "APT-20260715-042",
  "treatmentId": "TRT-facial-hydra",
  "treatmentName": "Hydra Facial 60 นาที",
  "staffId": "staff-12",
  "durationMin": 65,
  "products_used": ["PROD-serum-01", "PROD-mask-03"],
  "notes": "ผิวแห้ง ใช้ hydrating mask เพิ่ม"
}

Payload example — consultation.completed:

{
  "customerId": "CUST-B001",
  "staffId": "staff-5",
  "skinType": "combination",
  "concerns": ["acne", "dark_spots", "dehydration"],
  "sensitivities": ["fragrance"],
  "recommendedTreatments": ["hydra-facial", "vitamin-c-booster"]
}

Hotel

Eventactor.typepayload fields
reservation.createdsystem / staffreservationId, guestId, roomType, checkIn, checkOut, pax, channel
reservation.confirmedsystemreservationId, bookingRef
reservation.modifiedstaff / guestreservationId, changes[]
reservation.cancelledguest / staffreservationId, reason, cancellationFee
pre_arrival.message_sentsystemguestId, reservationId, channel
pre_arrival.preferences_receivedguestguestId, preferences (pillow, dietary, arrivalTime)
check_in.completedstaffguestId, reservationId, roomId, checkedInAt
room_upgrade.offeredsystem / staffguestId, fromRoomType, toRoomType, surcharge
room_upgrade.acceptedguestguestId, reservationId, toRoomType
room_service.orderedguestguestId, roomId, items[], total
amenity.usedsystemguestId, amenityType (spa, pool, gym, restaurant)
complaint.raisedguestguestId, roomId, issue, severity
complaint.resolvedstaffguestId, issueId, resolution, staffId
check_out.completedstaffguestId, reservationId, checkedOutAt
payment.completedsystemguestId, reservationId, amount, method
review.submittedguestguestId, reservationId, rating, platform, comment?
loyalty.points_earnedsystemguestId, points, reason
loyalty.tier_upgradedsystemguestId, fromTier, toTier

Payload example — pre_arrival.preferences_received:

{
  "guestId": "GUEST-001",
  "reservationId": "RES-20260715-0021",
  "preferences": {
    "pillow": "soft",
    "dietary": ["no_seafood"],
    "estimatedArrivalTime": "14:00",
    "specialOccasion": "anniversary",
    "floorPreference": "high"
  }
}

Restaurant

Actor types: customer (self-service app), staff (POS/table system), system (automatic)

Eventactor.typepayload fields
customer.registeredstaff / systemcustomerId, channel, registeredVia
reservation.createdcustomer / staffreservationId, customerId, partySize, scheduledAt, branch
reservation.confirmedsystemreservationId, tableId?
table.seatedstaffcustomerId?, tableId, branchId, partySize, seatedAt
order.placedstafftableId, customerId?, items[], orderId
order.item_addedstafftableId, orderId, items[]
promotion.appliedstaffcustomerId, orderId, promoCode, discountAmount
bill.requestedcustomer / stafftableId, orderId, requestedAt
payment.completedsystemorderId, amount, method, tableId
table.releasedstafftableId, branchId, releasedAt, durationMin
review.submittedcustomercustomerId, branchId, rating, comment?
loyalty.points_earnedsystemcustomerId, points, orderId
loyalty.points_redeemedstaffcustomerId, points, discountAmount, orderId
membership.upgradedsystemcustomerId, fromTier, toTier
birthday.offer_sentsystemcustomerId, channel, offerDetails
birthday.offer_redeemedstaffcustomerId, orderId, offerCode
delivery.orderedsystemcustomerId, branchId, items[], total, platform
delivery.completedsystemcustomerId, orderId, deliveredAt

Payload example — table.seated:

{
  "tableId": "B7",
  "branchId": "branch-central-westgate",
  "customerId": "CUST-M001",
  "partySize": 4,
  "seatedAt": "2026-07-15T19:04:00.000Z",
  "reservationId": "RES-20260715-042"
}

Payload example — order.placed:

{
  "orderId": "ORD-20260715-B7-001",
  "tableId": "B7",
  "customerId": "CUST-M001",
  "items": [
    { "menuItemId": "MENU-001", "name": "หมูสไลด์", "qty": 1, "price": 189 },
    { "menuItemId": "MENU-012", "name": "กุ้งสด", "qty": 1, "price": 249 },
    { "menuItemId": "MENU-031", "name": "น้ำซุป MK", "qty": 1, "price": 59 }
  ],
  "itemTotal": 497
}

Agriculture

Eventactor.typepayload fields
farm.registeredsystemfarmId, farmerId, province, totalAreaRai
plot.createdfarmerplotId, farmId, areaRai, gpsPolygon, soilType
crop.plantedfarmerplotId, cropId, variety, plantedAt, seedQtyKg, methodId
crop.fertilizedfarmerplotId, productId, productName, qtyKgPerRai, method
crop.irrigatedfarmer / deviceplotId, volumeLiters, method (drip/sprinkler/flood), source
crop.sprayedfarmerplotId, productId, qtyMlPerRai, reason, targetPest?
crop.disease_detectedfarmer / systemplotId, diseaseType, affectedAreaRai, severity
crop.disease_treatedfarmerplotId, treatmentId, productId, durationDays
sensor.readingdeviceplotId, sensorId, readings{} (soil_moisture, soil_temp, air_temp, humidity, ph, ec)
sensor.alertsystemplotId, sensorId, alertType, value, threshold, recommendation
weather.eventsystemplotId, eventType (heavy_rain, drought, storm), severity, rainfallMm?
crop.harvest_startedfarmerplotId, cropId, startedAt, estimatedYieldKg
crop.harvestedfarmerplotId, harvestId, yieldKg, qualityNotes?
harvest.quality_checkedstaff / buyerharvestId, grade, moisturePercent, checkedBy
harvest.soldfarmerharvestId, buyerId, pricePerKg, weightKg, totalAmount
harvest.delivereddriverharvestId, destinationId, deliveredAt, proofUrl?
payment.receivedsystemharvestId, amount, method, receivedAt
input.purchasedfarmerfarmId, inputType (seed/fertilizer/pesticide), productId, qty, amount
loan.appliedfarmerfarmId, amount, purpose, season
loan.approvedsystemfarmId, loanId, approvedAmount, yieldHistoryUsed
certification.appliedfarmerfarmId, certType (GAP/Organic/GlobalGAP)
certification.approvedsystemfarmId, certId, certType, validUntil

Payload example — crop.harvested:

{
  "plotId": "plot-B2",
  "harvestId": "HRV-20260522-B2",
  "cropId": "crop-20260315-B2",
  "yieldKg": 3200,
  "yieldPerRai": 1600,
  "harvestedAt": "2026-05-22T08:00:00.000Z",
  "qualityNotes": "grade A 80%, grade B 20%, size uniform"
}

Payload example — sensor.alert:

{
  "plotId": "plot-B2",
  "sensorId": "sensor-B2-001",
  "alertType": "soil_moisture_low",
  "value": 22.3,
  "threshold": 30,
  "unit": "%",
  "recommendation": "irrigate within 12 hours",
  "severity": "warning"
}

Adding a new domain

ถ้าต้องการเพิ่ม domain ใหม่:

  1. Define entity types — อะไรคือ "สิ่งที่มีเรื่องราว" ในธุรกิจนี้ (customer? shipment? student?)
  2. Define events — อะไรเกิดขึ้น ใครทำ เมื่อไหร่
  3. Follow naming{entity}.{action} snake_case เสมอ
  4. Keep payload flat — nested object ได้ แต่ไม่ควรลึกเกิน 2 ระดับ
  5. Instrument ทันทีที่เกิด — ไม่ backfill จาก DB (events = facts ที่เกิดจริง)

Event API reference →

Event Catalog

Events are business facts that occur in the system — immutable, append-only. Every event sent to PBCP must follow this convention.

Naming convention

{entity}.{action}
{namespace}.{entity}.{action}   ← for multi-service systems

Examples:

  • order.created
  • customer.registered
  • pharmacy.prescription.dispensed ✅ (namespaced)
  • OrderCreated ❌ (not snake_case)
  • created_order ❌ (entity must come first)

Event envelope (used by all domains)

{
  "schemaVersion": "1.0",
  "tenantId": "your-tenant-id",
  "eventId": "uuid-v4",
  "eventName": "order.created",
  "occurredAt": "2026-07-15T10:00:00.000Z",
  "actor": {
    "type": "user | system | staff | device",
    "id": "actor-id"
  },
  "source": {
    "system": "your-system-name",
    "channel": "web | mobile | pos | api | iot"
  },
  "payload": {
    // domain-specific fields
  }
}

Pharmacy

Tenant: pharmacy-demo (pilot) | {pharmacy-slug} (production)

Eventactor.typepayload fields
customer.registeredstaff / systemcustomerId, channel, registeredVia
customer.visitedsystemcustomerId, visitType
product.searchedstaffquery, resultCount
product.viewedstaffproductId, sku, name
cart.updatedstaffcustomerId, lineItems[], total
prescription.createdstaffprescriptionId, doctorName, items[]
prescription.dispensedstaffprescriptionId, customerId, items[]
order.createdsystemorderId, customerId, lineItems[], total
order.completedsystemorderId, paidAt, paymentMethod
payment.completedsystemorderId, amount, currency, method
loyalty.points_earnedsystemcustomerId, points, reason
coupon.redeemedstaffcustomerId, couponCode, discountAmount

Payload example — prescription.dispensed:

{
  "prescriptionId": "RX-20260715-001",
  "customerId": "CUST-1",
  "doctorName": "พญ. สมหญิง รักสุขภาพ",
  "items": [
    { "productId": "PROD-42", "name": "Amlodipine 5mg", "qty": 30, "unit": "เม็ด" }
  ],
  "dispensedBy": "staff-7"
}

E-commerce

Eventactor.typepayload fields
customer.registeredsystemcustomerId, registeredVia
session.startedsystemsessionId, customerId?, channel, device
product.viewedsystemproductId, sku, name, price, categoryId
product.searchedsystemquery, resultCount, filters
product.wishlistedsystemcustomerId, productId
cart.item_addedsystemcustomerId, productId, qty, price
cart.item_removedsystemcustomerId, productId, reason?
cart.abandonedsystemcustomerId, cartTotal, itemCount
checkout.startedsystemcustomerId, cartTotal
payment.initiatedsystemorderId, amount, method
payment.completedsystemorderId, amount, currency, method
order.createdsystemorderId, customerId, lineItems[], total
order.shippedsystemorderId, trackingNumber, carrier
order.deliveredsystemorderId, deliveredAt
return.requestedsystemorderId, reason, items[]
coupon.appliedsystemcustomerId, couponCode, discountAmount

Payload example — cart.abandoned:

{
  "customerId": "CUST-99",
  "sessionId": "SES-abc",
  "cartTotal": 1850,
  "itemCount": 3,
  "lastProductViewed": "PROD-101",
  "minutesInCart": 12
}

Logistics

Eventactor.typepayload fields
shipment.createdsystemshipmentId, senderId, recipientId, origin, destination
shipment.assignedsystemshipmentId, driverId, vehicleId
shipment.picked_updrivershipmentId, driverId, pickedUpAt, location
shipment.in_transitsystemshipmentId, currentLocation, eta
shipment.out_for_deliverydrivershipmentId, driverId, departedHub
shipment.delivery_attempteddrivershipmentId, attemptNumber, reason, location
shipment.delivereddrivershipmentId, deliveredAt, signedBy?, proofUrl?
shipment.faileddrivershipmentId, reason, returnToHub
shipment.returnedsystemshipmentId, returnedAt
checkpoint.scanneddeviceshipmentId, checkpointId, checkpointName, scannedAt
driver.shift_starteddriverdriverId, shiftId, vehicleId
driver.shift_endeddriverdriverId, shiftId, deliveriesCompleted, failedCount
customer.notifiedsystemshipmentId, channel, messageType, eta

Payload example — shipment.delivery_attempted:

{
  "shipmentId": "SHP-20260715-0042",
  "driverId": "DRV-7",
  "attemptNumber": 1,
  "reason": "recipient_not_home",
  "location": { "lat": 13.7563, "lng": 100.5018 },
  "nextAttemptScheduled": "2026-07-16T10:00:00.000Z"
}

SaaS / App

Eventactor.typepayload fields
user.signed_upsystemuserId, email, plan, signupVia
user.verified_emailsystemuserId
onboarding.step_completeduseruserId, step, stepName, duration
onboarding.completedsystemuserId, totalDays
session.startedsystemuserId, sessionId, platform, device
feature.useduseruserId, featureName, context?
feature.first_usedsystemuserId, featureName
workspace.createduserworkspaceId, userId, name
workspace.member_inviteduserworkspaceId, invitedEmail, role
workspace.member_joinedsystemworkspaceId, userId
subscription.upgradeduseruserId, fromPlan, toPlan
subscription.downgradeduseruserId, fromPlan, toPlan, reason?
subscription.cancelleduseruserId, plan, reason, cancelledAt
payment.completedsystemuserId, amount, plan, period
api.calledsystemuserId, endpoint, method, statusCode
integration.connecteduseruserId, integrationName

Payload example — subscription.cancelled:

{
  "userId": "usr_abc123",
  "plan": "pro",
  "reason": "too_expensive",
  "feedback": "ราคาสูงเกินไปสำหรับทีมเล็ก",
  "cancelledAt": "2026-07-15T09:00:00.000Z",
  "daysActive": 127
}

Education

Eventactor.typepayload fields
student.registeredsystemstudentId, email, registeredVia
enrollment.createdstudentstudentId, courseId, courseName, plan
lesson.startedstudentstudentId, courseId, lessonId, lessonName
lesson.completedsystemstudentId, lessonId, watchPercent, durationMs
lesson.skippedstudentstudentId, lessonId, reason?
quiz.startedstudentstudentId, quizId, lessonId
quiz.submittedstudentstudentId, quizId, answers, score
quiz.passedsystemstudentId, quizId, score, passingScore, attemptNumber
quiz.failedsystemstudentId, quizId, score, passingScore, attemptNumber
assignment.submittedstudentstudentId, assignmentId, submittedAt
assignment.gradedinstructorassignmentId, score, feedback
course.completedsystemstudentId, courseId, completionDate, totalDays
certificate.issuedsystemstudentId, courseId, certificateId, issuedAt
discussion.postedstudentstudentId, lessonId, postId
live_session.attendedsystemstudentId, sessionId, joinedAt, duration
enrollment.cancelledstudentstudentId, courseId, reason?, refundRequested

Payload example — quiz.failed:

{
  "studentId": "STU-001",
  "quizId": "QUIZ-L2-01",
  "lessonId": "L-02",
  "courseId": "python-beginners",
  "score": 55,
  "passingScore": 70,
  "attemptNumber": 2,
  "wrongTopics": ["loops", "list_comprehension"]
}

Beauty

Eventactor.typepayload fields
customer.registeredsystemcustomerId, registeredVia, referredBy?
consultation.completedstaffcustomerId, staffId, skinType, concerns[], sensitivities[]
appointment.bookedsystem / customercustomerId, appointmentId, treatmentId, staffId?, scheduledAt
appointment.confirmedstaffappointmentId, confirmedBy
appointment.reminder_sentsystemappointmentId, customerId, channel, scheduledAt
appointment.checked_instaffappointmentId, customerId, checkedInAt
appointment.no_showsystemappointmentId, customerId
appointment.cancelledcustomer / staffappointmentId, reason, cancelledBy
treatment.startedstaffappointmentId, treatmentId, treatmentName, staffId
treatment.completedstaffappointmentId, treatmentId, durationMin, staffId
product.recommendedstaffcustomerId, staffId, productId, productName, reason
product.purchasedsystemcustomerId, productId, qty, amount, linkedAppointmentId?
review.submittedcustomercustomerId, appointmentId, rating, comment?, staffId?
membership.enrolledsystemcustomerId, tier, startDate
membership.upgradedsystemcustomerId, fromTier, toTier
package.purchasedsystemcustomerId, packageId, packageName, sessions, amount
package.session_usedstaffcustomerId, packageId, appointmentId, sessionsRemaining
loyalty.points_earnedsystemcustomerId, points, reason, totalPoints
coupon.redeemedstaffcustomerId, couponCode, discountAmount, appointmentId?
referral.madesystemreferrerId, referredCustomerId

Payload example — treatment.completed:

{
  "appointmentId": "APT-20260715-042",
  "treatmentId": "TRT-facial-hydra",
  "treatmentName": "Hydra Facial 60 นาที",
  "staffId": "staff-12",
  "durationMin": 65,
  "products_used": ["PROD-serum-01", "PROD-mask-03"],
  "notes": "ผิวแห้ง ใช้ hydrating mask เพิ่ม"
}

Payload example — consultation.completed:

{
  "customerId": "CUST-B001",
  "staffId": "staff-5",
  "skinType": "combination",
  "concerns": ["acne", "dark_spots", "dehydration"],
  "sensitivities": ["fragrance"],
  "recommendedTreatments": ["hydra-facial", "vitamin-c-booster"]
}

Hotel

Eventactor.typepayload fields
reservation.createdsystem / staffreservationId, guestId, roomType, checkIn, checkOut, pax, channel
reservation.confirmedsystemreservationId, bookingRef
reservation.modifiedstaff / guestreservationId, changes[]
reservation.cancelledguest / staffreservationId, reason, cancellationFee
pre_arrival.message_sentsystemguestId, reservationId, channel
pre_arrival.preferences_receivedguestguestId, preferences (pillow, dietary, arrivalTime)
check_in.completedstaffguestId, reservationId, roomId, checkedInAt
room_upgrade.offeredsystem / staffguestId, fromRoomType, toRoomType, surcharge
room_upgrade.acceptedguestguestId, reservationId, toRoomType
room_service.orderedguestguestId, roomId, items[], total
amenity.usedsystemguestId, amenityType (spa, pool, gym, restaurant)
complaint.raisedguestguestId, roomId, issue, severity
complaint.resolvedstaffguestId, issueId, resolution, staffId
check_out.completedstaffguestId, reservationId, checkedOutAt
payment.completedsystemguestId, reservationId, amount, method
review.submittedguestguestId, reservationId, rating, platform, comment?
loyalty.points_earnedsystemguestId, points, reason
loyalty.tier_upgradedsystemguestId, fromTier, toTier

Payload example — pre_arrival.preferences_received:

{
  "guestId": "GUEST-001",
  "reservationId": "RES-20260715-0021",
  "preferences": {
    "pillow": "soft",
    "dietary": ["no_seafood"],
    "estimatedArrivalTime": "14:00",
    "specialOccasion": "anniversary",
    "floorPreference": "high"
  }
}

Restaurant

Actor types: customer (self-service app), staff (POS/table system), system (automatic)

Eventactor.typepayload fields
customer.registeredstaff / systemcustomerId, channel, registeredVia
reservation.createdcustomer / staffreservationId, customerId, partySize, scheduledAt, branch
reservation.confirmedsystemreservationId, tableId?
table.seatedstaffcustomerId?, tableId, branchId, partySize, seatedAt
order.placedstafftableId, customerId?, items[], orderId
order.item_addedstafftableId, orderId, items[]
promotion.appliedstaffcustomerId, orderId, promoCode, discountAmount
bill.requestedcustomer / stafftableId, orderId, requestedAt
payment.completedsystemorderId, amount, method, tableId
table.releasedstafftableId, branchId, releasedAt, durationMin
review.submittedcustomercustomerId, branchId, rating, comment?
loyalty.points_earnedsystemcustomerId, points, orderId
loyalty.points_redeemedstaffcustomerId, points, discountAmount, orderId
membership.upgradedsystemcustomerId, fromTier, toTier
birthday.offer_sentsystemcustomerId, channel, offerDetails
birthday.offer_redeemedstaffcustomerId, orderId, offerCode
delivery.orderedsystemcustomerId, branchId, items[], total, platform
delivery.completedsystemcustomerId, orderId, deliveredAt

Payload example — table.seated:

{
  "tableId": "B7",
  "branchId": "branch-central-westgate",
  "customerId": "CUST-M001",
  "partySize": 4,
  "seatedAt": "2026-07-15T19:04:00.000Z",
  "reservationId": "RES-20260715-042"
}

Payload example — order.placed:

{
  "orderId": "ORD-20260715-B7-001",
  "tableId": "B7",
  "customerId": "CUST-M001",
  "items": [
    { "menuItemId": "MENU-001", "name": "หมูสไลด์", "qty": 1, "price": 189 },
    { "menuItemId": "MENU-012", "name": "กุ้งสด", "qty": 1, "price": 249 },
    { "menuItemId": "MENU-031", "name": "น้ำซุป MK", "qty": 1, "price": 59 }
  ],
  "itemTotal": 497
}

Agriculture

Eventactor.typepayload fields
farm.registeredsystemfarmId, farmerId, province, totalAreaRai
plot.createdfarmerplotId, farmId, areaRai, gpsPolygon, soilType
crop.plantedfarmerplotId, cropId, variety, plantedAt, seedQtyKg, methodId
crop.fertilizedfarmerplotId, productId, productName, qtyKgPerRai, method
crop.irrigatedfarmer / deviceplotId, volumeLiters, method (drip/sprinkler/flood), source
crop.sprayedfarmerplotId, productId, qtyMlPerRai, reason, targetPest?
crop.disease_detectedfarmer / systemplotId, diseaseType, affectedAreaRai, severity
crop.disease_treatedfarmerplotId, treatmentId, productId, durationDays
sensor.readingdeviceplotId, sensorId, readings{} (soil_moisture, soil_temp, air_temp, humidity, ph, ec)
sensor.alertsystemplotId, sensorId, alertType, value, threshold, recommendation
weather.eventsystemplotId, eventType (heavy_rain, drought, storm), severity, rainfallMm?
crop.harvest_startedfarmerplotId, cropId, startedAt, estimatedYieldKg
crop.harvestedfarmerplotId, harvestId, yieldKg, qualityNotes?
harvest.quality_checkedstaff / buyerharvestId, grade, moisturePercent, checkedBy
harvest.soldfarmerharvestId, buyerId, pricePerKg, weightKg, totalAmount
harvest.delivereddriverharvestId, destinationId, deliveredAt, proofUrl?
payment.receivedsystemharvestId, amount, method, receivedAt
input.purchasedfarmerfarmId, inputType (seed/fertilizer/pesticide), productId, qty, amount
loan.appliedfarmerfarmId, amount, purpose, season
loan.approvedsystemfarmId, loanId, approvedAmount, yieldHistoryUsed
certification.appliedfarmerfarmId, certType (GAP/Organic/GlobalGAP)
certification.approvedsystemfarmId, certId, certType, validUntil

Payload example — crop.harvested:

{
  "plotId": "plot-B2",
  "harvestId": "HRV-20260522-B2",
  "cropId": "crop-20260315-B2",
  "yieldKg": 3200,
  "yieldPerRai": 1600,
  "harvestedAt": "2026-05-22T08:00:00.000Z",
  "qualityNotes": "grade A 80%, grade B 20%, size uniform"
}

Payload example — sensor.alert:

{
  "plotId": "plot-B2",
  "sensorId": "sensor-B2-001",
  "alertType": "soil_moisture_low",
  "value": 22.3,
  "threshold": 30,
  "unit": "%",
  "recommendation": "irrigate within 12 hours",
  "severity": "warning"
}

Adding a new domain

To add a new domain:

  1. Define entity types — What are the "things with stories" in this business (customer? shipment? student?)
  2. Define events — What happens, who does it, when
  3. Follow naming{entity}.{action} always snake_case
  4. Keep payload flat — nested objects are allowed but should not exceed 2 levels deep
  5. Instrument at the moment it occurs — do not backfill from DB (events = facts that actually occurred)

Event API reference →