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
| Event | Push Notification | Analytics | Side Effects |
|---|---|---|---|
| RequestCreated | ❌ | ✅ | - |
| OfferCreated | ✅ (to buyer) | ✅ | Increment offer_count |
| OfferAccepted | ✅ (to all sellers) | ✅ | Close request, reject other offers |
| NewMessage | ✅ (if offline) | ✅ | WebSocket broadcast |
| DealCompleted | ❌ | ✅ | Trigger rating prompt |
| DealCancelled | ✅ (to seller) | ✅ | - |
| RatingSubmitted | ❌ | ✅ | Update 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