REST API
Realtime (WebSocket)
Connect to Reverb over the Pusher protocol, authorize private channels and handle events.
Realtime updates are delivered by Laravel Reverb over the Pusher protocol, so any Pusher client (pusher-js, Laravel Echo, PusherSwift, …) works. Get the connection settings from GET /api/broadcasting/client-config — production uses wss://ws.minchatapp.com:8443.
{
"enabled": true,
"key": "<app-key>",
"host": "ws.minchatapp.com",
"port": 8443,
"scheme": "https"
}
Connecting#
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
const cfg = await fetch('https://minchatapp.com/api/broadcasting/client-config', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
}).then(r => r.json());
const echo = new Echo({
broadcaster: 'reverb',
Pusher,
key: cfg.key,
wsHost: cfg.host,
wsPort: cfg.port,
wssPort: cfg.port,
forceTLS: cfg.scheme === 'https',
enabledTransports: ['ws', 'wss'],
authEndpoint: 'https://minchatapp.com/broadcasting/auth',
auth: { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } },
});
echo.private(`users.${me.id}`)
.listen('.MessageSent', (m) => console.log('new message', m.conversation_id, m.content))
.listen('.ConversationCreated', (c) => console.log('added to', c.name));
echo.private('conversation.12')
.listen('.UserActivity', (a) => console.log(a.name, a.action));
const pusher = new Pusher(cfg.key, {
wsHost: cfg.host, wsPort: cfg.port, wssPort: cfg.port,
forceTLS: true, enabledTransports: ['ws', 'wss'], cluster: '',
channelAuthorization: {
endpoint: 'https://minchatapp.com/broadcasting/auth',
headers: { Authorization: `Bearer ${token}` },
},
});
pusher.subscribe(`private-users.${me.id}`)
.bind('MessageSent', (m) => console.log(m));
Channel authorization#
Private channels are authorized by POST /broadcasting/auth (form fields socket_id, channel_name) with the same Bearer token. Banned users are refused.
curl -X POST https://minchatapp.com/broadcasting/auth \
-H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' \
-d 'socket_id=123.456' -d 'channel_name=private-users.1'
# → {"auth":"<key>:<signature>"}
Channels#
| Channel | Who can subscribe | Purpose |
|---|---|---|
private-users.{userId} | The user only | Everything addressed to the user across all chats — the recommended single subscription |
private-conversation.{id} | Members of the conversation | Events of one open chat (typing, reads, messages) |
qr-login.{hash} | Anyone (public) | QrLoginUpdated for a QR sign-in request |
users.{id} channel, so de-duplicate by event + id if you subscribe to both. Event names are the class names (with Echo use a leading dot: .MessageSent).Events#
New message. Payload is the full message object plus conversation_id, conversation_type, conversation_name, conversation_avatar. is_own/can_delete are always false — compare user.id with yours.
{
"id": 55,
"content": "Realtime demo https://example.com",
"type": "text",
"attachments": [],
"link_previews": null,
"reply_to": {
"id": 43,
"content": "Thanks everyone!",
"type": "text",
"attachments": [],
"user": {
"id": 4,
"name": "Gleb Orlov",
"avatar": null
}
},
"user": {
"id": 1,
"name": "Anna Smith",
"avatar": null,
"is_channel": false,
"is_bot": false,
"username": "anna_s"
},
"created_at": "2026-10-22T09:11:15.000000Z",
"edited_at": null,
"conversation_id": 1,
"conversation_type": "group",
"conversation_name": "Design team",
"conversation_avatar": null,
"can_delete": false,
"is_own": false,
"read_receipt": "delivered",
"client_id": null,
"reactions": [],
"forwarded_from": null,
"views_count": null,
"permalink": null,
"system": null,
"card": null,
"group_id": null,
"mentions": [],
"silent": false,
"reply_markup": null,
"expires_at": null
}
Message edited, poll/to-do changed or bot keyboard updated.
{
"id": 55,
"content": "Realtime demo (edited)",
"edited_at": "2026-10-22T09:11:15.000000Z",
"created_at": "2026-10-22T09:11:15.000000Z",
"conversation_id": 1,
"link_previews": null,
"attachments": [],
"type": "text",
"card": null,
"group_id": null,
"reply_markup": null
}
Deleted for everyone (or, sent only to your users.{id}, deleted for you).
{
"id": 55,
"created_at": "2026-10-22T09:11:15.000000Z",
"conversation_id": 1
}
Reaction counters changed. actor_id — who reacted; emoji_id is null when removed. Set me yourself when actor_id is you.
{
"message_id": 55,
"conversation_id": 1,
"reactions": [
{
"emoji_id": 2,
"set_id": 1,
"set_title": "Min",
"set_active": true,
"shortcode": "heart",
"kind": "unicode",
"unicode": "❤️",
"image_url": null,
"count": 1,
"user_ids": [
1
]
}
],
"actor_id": 1,
"emoji_id": 2
}
Link previews for a message are ready.
{
"conversation_id": 1,
"message_id": 55,
"link_previews": [
{
"url": "https://example.com",
"provider": "generic",
"title": "example.com",
"description": null,
"image": null,
"site_name": "example.com",
"embed_url": null,
"embed_html": null,
"video_url": null,
"video_type": null,
"video_width": null,
"video_height": null,
"accessible": true,
"meta": []
}
]
}
A member read the chat up to last_read_at (read receipts).
{
"conversation_id": 1,
"user_id": 1,
"last_read_at": "2026-10-22T09:11:15+00:00"
}
Typing / recording audio / cancel. Not sent in channels.
{
"conversation_id": 1,
"user_id": 1,
"action": "typing",
"name": "Anna Smith"
}
You were added to a chat (or created/joined one). Payload is a conversation list card.
{
"id": 1,
"name": "Design team",
"type": "group",
"avatar": null,
"description": "Release discussions",
"username": null,
"visibility": "public",
"share_url": null,
"last_message": null,
"unread_count": 0
}
Chat settings or your role changed. Per-user payload (user_role, is_owner, can_send_messages, can_pin, …).
{
"id": 1,
"name": "Design team",
"avatar": null,
"description": "Release discussions",
"type": "group",
"user_role": "admin",
"is_owner": true,
"can_send_messages": true,
"members_can_pin": false,
"can_pin": true,
"can_manage_subscribers": false,
"username": null,
"visibility": "public",
"share_url": null,
"hidden_from_discovery": false,
"history_ttl": 0
}
The chat was deleted or you were removed.
{
"id": 12
}
Mute changed on another of your devices.
{
"conversation_id": 1,
"muted_until": "2026-10-22T10:11:15+00:00"
}
Pinned messages changed.
{
"conversation_id": 1,
"pinned_messages": [
{
"id": 55,
"content": "Realtime demo (edited)",
"type": "text",
"attachments": [],
"user": {
"id": 1,
"name": "Anna Smith",
"avatar": null
},
"created_at": "2026-10-22T09:11:15.000000Z",
"pinned_at": "2026-10-22T09:11:15+00:00",
"pinned_by": 1
}
]
}
Your draft changed on another device.
{
"conversation_id": 1,
"draft": {
"content": "typing a draft…",
"reply_to_message_id": null,
"updated_at": "2026-10-22T09:11:15+00:00"
}
}
Number of your pending scheduled messages in a chat changed.
{
"conversation_id": 1,
"pending_count": 5
}
Unread mention counter of a chat changed.
{
"conversation_id": 1,
"unread_mentions_count": 2
}
A reminder fired (due:true) — show it in-app.
{
"reminder": {
"id": 3,
"message_id": 45,
"conversation_id": 1,
"conversation_name": "Design team",
"sender_name": "Anna Smith",
"preview": "Hello @Boris Petrov!",
"remind_at": "2026-11-02T10:00:00+00:00",
"status": "sent"
},
"due": true
}
Sessions (token ids) were terminated. If yours is in the list, sign out locally.
{
"session_ids": [
19,
23
]
}
Someone requested a one-time sign-in code for this account; show it in the signed-in app.
{
"code": "482913",
"expires_in": 600
}
Incoming call.
{
"call_id": "7c1d…",
"conversation_id": 3,
"conversation_type": "private",
"conversation_name": "Anna Smith",
"conversation_avatar": null,
"room": "conv-3",
"mode": "video",
"ring_ttl": 45,
"caller": {
"id": 1,
"name": "Anna Smith",
"avatar": null
},
"started_at": "2026-10-22T09:30:00.000Z"
}
The call was answered on one of your devices — stop ringing on the others.
{
"call_id": "7c1d…",
"conversation_id": 3
}
Call ended. reason: ended, cancelled, declined, timeout, busy, unavailable.
{
"call_id": "7c1d…",
"conversation_id": 3,
"reason": "ended",
"by_user_id": 1
}
Server-side video download progressed.
{
"id": 7,
"url": "https://www.youtube.com/watch?v=…",
"status": "completed",
"file_url": "https://minchatapp.com/api/videos/<token>",
"title": "Demo",
"duration": 212,
"error_message": null,
"code": null
}
The phone confirmed or declined the QR request: status is confirmed or declined. On confirmed call /auth/qr/poll to get the token.
{
"status": "confirmed"
}