Skip to main content

Architecture

Every escrow has a conversation. The conversation feed merges two data sources:
SourceDescription
messagesHuman chat messages sent by participants
notificationsEscrow lifecycle events (funded, disputed, milestone released, etc.)
The GET /message-feed endpoint returns both, sorted by created_at ASC, so your UI renders a single chronological thread.

Sending a message

POST /message-send
Authorization: Bearer <token>

{
  "conversation_id": "<id>",
  "content": "I've shipped the package.",
  "message_type": "text"
}
message_type can be text or attachment.

Fetching the feed

GET /message-feed?conversation_id=<id>&limit=20
Authorization: Bearer <token>
Each item in the feed has a type field:
  • message — human chat
  • notification — escrow lifecycle event
{
  "id": "...",
  "type": "notification",
  "entity_type": "escrow",
  "entity_id": "<escrow_id>",
  "action_label": "Fund Escrow",
  "content": "Payment of ₦500,000 received.",
  "created_at": "2026-04-18T10:00:00Z"
}
Use action_label for CTA buttons. If null, the notification is informational only.

Notifications

Lifecycle events are automatically written to the notifications table via DB triggers — one row per participant per event. You don’t write notifications; you only read them.
GET /notification-list?limit=20
Authorization: Bearer <token>
Mark as read:
PATCH /notification-mark-read
{ "notification_id": "<id>" }

Conversations list

GET /message-conversations
Authorization: Bearer <token>
Returns each conversation with last_message and unread_count, combining both messages and notifications so the badge count is always accurate.

Realtime subscriptions

Subscribe to these Supabase Realtime channels for live updates:
ChannelFilterUse for
messagesconversation_id=eq.<id>Live chat in an open conversation
message_readsconversation_id=eq.<id>Read receipt ticks
notificationsconversation_id=eq.<id>&user_id=eq.<uid>Live escrow events in open chat
notificationsuser_id=eq.<uid>Global unread badge