Skip to main content

Event Model

Complete event system for Talbino marketplace including triggers, payloads, and notification rules.

Event Architecture

Events are dispatched using Laravel's event system. Each event can have multiple listeners for different purposes (notifications, analytics, side effects).

Event Dispatched → Listeners Execute → Side Effects
├─ NotificationListener (push)
├─ AnalyticsListener (logging)
└─ BusinessLogicListener (updates)

Core Events

1. RequestCreated

Trigger: Buyer successfully creates a buy request

Payload:

{
"event": "RequestCreated",
"timestamp": "2026-02-26T22:30:00Z",
"request": {
"id": "uuid",
"buyer_id": "uuid",
"category": "phone",
"product_brand": "Apple",
"product_model": "iPhone 13",
"budget_min": 8000,
"budget_max": 10000,
"location_district": "Nasr City",
"status": "active"
}
}

Listeners:

  • AnalyticsListener: Log event for marketplace metrics
  • No push notification (internal event only)

Idempotency: Event dispatched once per request creation


2. OfferCreated

Trigger: Seller successfully submits an offer on a request

Payload:

{
"event": "OfferCreated",
"timestamp": "2026-02-26T22:35:00Z",
"offer": {
"id": "uuid",
"request_id": "uuid",
"seller_id": "uuid",
"price": 9500,
"availability": "Immediate",
"status": "pending"
},
"request": {
"id": "uuid",
"buyer_id": "uuid",
"offer_count": 3
},
"seller": {
"id": "uuid",
"name": "Ahmed's Electronics",
"rating_avg": 4.5
}
}

Listeners:

  • PushNotificationListener: Send push to buyer
  • AnalyticsListener: Log offer creation
  • RequestUpdateListener: Increment request.offer_count

Push Notification (to buyer):

{
"title": "عرض جديد على طلبك",
"title_en": "New offer on your request",
"body": "Ahmed's Electronics عرض 9,500 جنيه",
"body_en": "Ahmed's Electronics offered EGP 9,500",
"data": {
"type": "new_offer",
"request_id": "uuid",
"offer_id": "uuid",
"deep_link": "talbino://requests/{request_id}/offers/{offer_id}"
}
}

Idempotency: Event dispatched once per offer creation. Duplicate offers prevented by business logic.


3. OfferAccepted

Trigger: Buyer accepts an offer (creates deal)

Payload:

{
"event": "OfferAccepted",
"timestamp": "2026-02-26T22:40:00Z",
"deal": {
"id": "uuid",
"request_id": "uuid",
"offer_id": "uuid",
"buyer_id": "uuid",
"seller_id": "uuid",
"status": "accepted"
},
"offer": {
"id": "uuid",
"price": 9500,
"status": "accepted"
},
"request": {
"id": "uuid",
"status": "closed"
},
"rejected_offer_ids": ["uuid1", "uuid2"]
}

Listeners:

  • PushNotificationListener: Send push to seller (accepted) and other sellers (rejected)
  • AnalyticsListener: Log deal creation
  • RequestCloseListener: Update request status to closed

Push Notification (to accepted seller):

{
"title": "تم قبول عرضك! 🎉",
"title_en": "Your offer was accepted! 🎉",
"body": "المشتري قبل عرضك بسعر 9,500 جنيه",
"body_en": "Buyer accepted your offer of EGP 9,500",
"data": {
"type": "offer_accepted",
"deal_id": "uuid",
"offer_id": "uuid",
"deep_link": "talbino://deals/{deal_id}"
}
}

Push Notification (to rejected sellers):

{
"title": "تم إغلاق الطلب",
"title_en": "Request closed",
"body": "المشتري قبل عرض آخر",
"body_en": "Buyer accepted another offer",
"data": {
"type": "request_closed",
"request_id": "uuid"
}
}

Idempotency: Transaction ensures only one offer can be accepted. Event dispatched once.


4. NewMessage

Trigger: User sends a chat message

Payload:

{
"event": "NewMessage",
"timestamp": "2026-02-26T22:45:00Z",
"message": {
"id": "uuid",
"conversation_id": "uuid",
"sender_id": "uuid",
"message_text": "هل المنتج متاح الآن؟",
"created_at": "2026-02-26T22:45:00Z"
},
"conversation": {
"id": "uuid",
"buyer_id": "uuid",
"seller_id": "uuid",
"offer_id": "uuid"
},
"sender": {
"id": "uuid",
"name": "محمد أحمد"
},
"recipient_id": "uuid"
}

Listeners:

  • WebSocketBroadcastListener: Broadcast to recipient if online
  • PushNotificationListener: Send push if recipient offline
  • AnalyticsListener: Log message sent

Push Notification (if recipient offline):

{
"title": "رسالة جديدة من محمد أحمد",
"title_en": "New message from محمد أحمد",
"body": "هل المنتج متاح الآن؟",
"body_en": "هل المنتج متاح الآن؟",
"data": {
"type": "new_message",
"conversation_id": "uuid",
"sender_id": "uuid",
"deep_link": "talbino://chat/{conversation_id}"
}
}

Idempotency: Each message creates one event. Duplicate prevention via message deduplication in client.


5. DealCompleted

Trigger: Buyer marks deal as completed

Payload:

{
"event": "DealCompleted",
"timestamp": "2026-02-27T10:00:00Z",
"deal": {
"id": "uuid",
"request_id": "uuid",
"offer_id": "uuid",
"buyer_id": "uuid",
"seller_id": "uuid",
"status": "completed",
"completed_at": "2026-02-27T10:00:00Z"
}
}

Listeners:

  • RatingPromptListener: Trigger rating UI for buyer
  • AnalyticsListener: Log deal completion
  • No push notification (in-app prompt only)

Idempotency: Deal can only be completed once (status check prevents duplicates)


6. DealCancelled

Trigger: Buyer cancels deal

Payload:

{
"event": "DealCancelled",
"timestamp": "2026-02-27T10:00:00Z",
"deal": {
"id": "uuid",
"request_id": "uuid",
"buyer_id": "uuid",
"seller_id": "uuid",
"status": "cancelled",
"cancelled_at": "2026-02-27T10:00:00Z",
"cancellation_reason": "Seller didn't show"
}
}

Listeners:

  • PushNotificationListener: Notify seller
  • AnalyticsListener: Log cancellation

Push Notification (to seller):

{
"title": "تم إلغاء الصفقة",
"title_en": "Deal cancelled",
"body": "المشتري ألغى الصفقة",
"body_en": "Buyer cancelled the deal",
"data": {
"type": "deal_cancelled",
"deal_id": "uuid",
"deep_link": "talbino://deals/{deal_id}"
}
}

Idempotency: Deal can only be cancelled once


7. RatingSubmitted

Trigger: Buyer submits rating for seller

Payload:

{
"event": "RatingSubmitted",
"timestamp": "2026-02-27T10:05:00Z",
"rating": {
"id": "uuid",
"deal_id": "uuid",
"buyer_id": "uuid",
"seller_id": "uuid",
"rating": 5,
"review_text": "بائع ممتاز، منتج كما وصف"
},
"seller_profile": {
"id": "uuid",
"rating_avg": 4.6,
"rating_count": 23
}
}

Listeners:

  • SellerRatingUpdateListener: Update seller_profiles aggregates
  • AnalyticsListener: Log rating
  • No push notification (seller sees rating in profile)

Idempotency: One rating per deal (unique constraint prevents duplicates)


8. SellerApproved

Trigger: Admin approves seller application

Payload:

{
"event": "SellerApproved",
"timestamp": "2026-02-26T15:00:00Z",
"seller_profile": {
"id": "uuid",
"user_id": "uuid",
"status": "approved",
"approved_at": "2026-02-26T15:00:00Z",
"approved_by": "admin_uuid"
},
"user": {
"id": "uuid",
"name": "Ahmed's Electronics"
}
}

Listeners:

  • PushNotificationListener: Notify seller
  • AnalyticsListener: Log approval

Push Notification (to seller):

{
"title": "تم الموافقة على حسابك! 🎉",
"title_en": "Your account was approved! 🎉",
"body": "يمكنك الآن تقديم عروض على الطلبات",
"body_en": "You can now make offers on requests",
"data": {
"type": "seller_approved",
"deep_link": "talbino://browse-requests"
}
}

Idempotency: Seller can only be approved once (status transition prevents duplicates)


9. SellerRejected

Trigger: Admin rejects seller application

Payload:

{
"event": "SellerRejected",
"timestamp": "2026-02-26T15:00:00Z",
"seller_profile": {
"id": "uuid",
"user_id": "uuid",
"status": "rejected"
}
}

Listeners:

  • PushNotificationListener: Notify user
  • AnalyticsListener: Log rejection

Push Notification:

{
"title": "طلب البائع",
"title_en": "Seller Application",
"body": "نأسف، لم تتم الموافقة على طلبك حالياً",
"body_en": "Sorry, your application was not approved at this time",
"data": {
"type": "seller_rejected"
}
}

10. ReportCreated

Trigger: Buyer reports a seller

Payload:

{
"event": "ReportCreated",
"timestamp": "2026-02-27T11:00:00Z",
"report": {
"id": "uuid",
"reporter_id": "uuid",
"reported_user_id": "uuid",
"deal_id": "uuid",
"reason": "no_show",
"description": "Seller didn't show up at agreed location",
"status": "pending"
}
}

Listeners:

  • AdminNotificationListener: Notify admin team
  • AnalyticsListener: Log report
  • No push to reported user (admin reviews first)

Admin Notification (internal system):

  • Email to admin team
  • Admin dashboard alert

Idempotency: Multiple reports allowed per deal/user


11. SellerSuspended

Trigger: Admin suspends seller account

Payload:

{
"event": "SellerSuspended",
"timestamp": "2026-02-27T12:00:00Z",
"seller_profile": {
"id": "uuid",
"user_id": "uuid",
"status": "suspended"
},
"reason": "Multiple no-show reports"
}

Listeners:

  • PushNotificationListener: Notify seller
  • AnalyticsListener: Log suspension
  • OfferBlockListener: Prevent new offers

Push Notification:

{
"title": "تم تعليق حسابك",
"title_en": "Your account was suspended",
"body": "يرجى التواصل مع الدعم",
"body_en": "Please contact support",
"data": {
"type": "seller_suspended"
}
}

Event Summary Table

EventPush NotificationAnalyticsSide Effects
RequestCreated-
OfferCreated✅ (to buyer)Increment offer_count
OfferAccepted✅ (to all sellers)Close request, reject other offers
NewMessage✅ (if offline)WebSocket broadcast
DealCompletedTrigger rating prompt
DealCancelled✅ (to seller)-
RatingSubmittedUpdate seller rating aggregates
SellerApproved✅ (to seller)-
SellerRejected✅ (to seller)-
ReportCreated✅ (to admin)-
SellerSuspended✅ (to seller)Block new offers

Push Notification Rules

Delivery Logic

1. Check if user has active device_tokens
2. If user online (WebSocket connected):
- Send via WebSocket (for chat messages)
- Skip push notification
3. If user offline:
- Queue NotificationJob
- Send via FCM to all user's devices
4. Log notification in notifications table (audit)

Priority Levels

  • High: OfferAccepted, NewMessage, SellerApproved
  • Normal: OfferCreated, DealCancelled
  • Low: SellerRejected, SellerSuspended

Notification Preferences (Phase 2)

MVP: All notifications enabled by default. Phase 2: User preferences.

Idempotency Rules

Event Deduplication

  • RequestCreated: One event per request (unique ID)
  • OfferCreated: One event per offer (unique ID)
  • OfferAccepted: Database transaction ensures single acceptance
  • NewMessage: Client-side deduplication (message ID)
  • DealCompleted/Cancelled: Status check prevents duplicate transitions
  • RatingSubmitted: Unique constraint (one rating per deal)
  • SellerApproved/Rejected: Status transition prevents duplicates

Retry Logic

  • Failed push notifications: Retry 3 times with exponential backoff
  • Failed analytics events: Log error, don't block main flow
  • Failed side effects: Retry via queue, alert if persistent failure

Event Storage

Analytics Events

Stored in dedicated analytics system (e.g., Mixpanel, Amplitude, or custom):

{
"event_name": "offer_created",
"timestamp": "2026-02-26T22:35:00Z",
"user_id": "uuid",
"properties": {
"request_id": "uuid",
"offer_id": "uuid",
"price": 9500,
"category": "phone",
"location": "Nasr City"
}
}

Notification Audit

Stored in notifications table for debugging:

INSERT INTO notifications (user_id, type, title, body, data, sent_at)
VALUES (?, 'new_offer', 'عرض جديد', '...', '{"offer_id": "..."}', NOW());

Error Handling

Failed Push Notification

1. Log error with details (token, error code)
2. If token invalid: Mark device_token as stale
3. Retry up to 3 times
4. If all retries fail: Log to monitoring system
5. Don't block main transaction

Failed Event Dispatch

1. Log error with stack trace
2. Alert engineering team (if critical event)
3. Main transaction continues (events are side effects)
4. Manual replay available for critical events

Monitoring

Metrics to Track

  • Event dispatch rate: Events/minute by type
  • Push notification delivery rate: Success/failure ratio
  • Push notification latency: Time from event to delivery
  • Queue depth: Pending notification jobs
  • Failed events: Count by type and reason

Alerts

  • Push notification failure rate > 10%
  • Event queue depth > 1000
  • Push notification latency > 10s (p95)
  • Critical event dispatch failure (OfferAccepted, DealCompleted)

Testing Strategy

Unit Tests

  • Event payload structure
  • Listener execution
  • Idempotency checks

Integration Tests

  • End-to-end event flow
  • Push notification delivery
  • Transaction rollback scenarios

Load Tests

  • 100 events/second sustained
  • 1000 concurrent push notifications
  • Queue processing under load