Notification Settings
Let each user choose which chat push notifications they get — turn chat pushes off, only on mentions, mute a room for a while, or turn off single event types. Node.js and REST.
AbrovaChat sends a push to participants who are offline when something happens in their rooms (see Push Setup). Some users want every one of them, others want none. Notification settings let each user decide, and you expose the choice in your own app's settings screen.
A user without settings gets every push, exactly as before. Your project-wide push configuration still applies on top: a push the project doesn't send is never sent, whatever the user chooses.
What a user can choose#
Settings exist at two levels. A push is sent only if both allow it.
| Level | Use it for |
|---|---|
| App-wide | "Chat notifications" switch in your app settings |
| Per room | "Mute this conversation", "Only notify me when mentioned" |
Each level has the same three fields:
| Option | Type | Required | Description |
|---|---|---|---|
| pushLevel | 'all' | 'mentions' | 'none' | 'all' | `all` — every chat push. `mentions` — only when the user is @mentioned. `none` — no chat pushes. |
| mutedUntil | ISO date | null | null | No chat pushes until this time, then back to normal. Send `null` to unmute early. |
| disabledEvents | string[] | [] | Event types to turn off, e.g. `["reaction", "message_pinned"]`. |
Event types you can turn off:
| Option | Type | Required | Description |
|---|---|---|---|
| new_message | - | - | New text message |
| file_shared | - | - | Image or file message |
| mention | - | - | The user was @mentioned |
| reaction | - | - | Someone reacted to the user's message |
| message_pinned | - | - | A message was pinned |
| room_created | - | - | The user was put in a new room |
| participant_joined | - | - | Someone joined the room |
| participant_removed | - | - | Someone left or was removed |
Settings only affect push notifications. Messages, realtime events and unread counts are unchanged.
Recommended structure#
Keep your server secret on your server, and let your app talk to your backend:
Your app ──(your login)──▶ Your backend ──(API key + server secret)──▶ AbrovaChat
Your backend already knows who the signed-in user is, so it passes that user's id to AbrovaChat. The app never chooses whose settings it changes, and never holds the secret.
Your backend (Node.js)#
Install the server SDK from Abrova Center (the .npmrc line points the @abrova scope there):
# .npmrc @abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/
npm install @abrova/chat@^0.2.0
import express from 'express';
import { AbrovaChat, AbrovaChatError } from '@abrova/chat';
const chat = new AbrovaChat({
apiKey: process.env.ABROVA_API_KEY!,
serverSecret: process.env.ABROVA_SERVER_SECRET!, // server-side only
});
const router = express.Router();
// requireLogin is YOUR auth middleware; req.user.id is YOUR user id,
// the same id you use as externalUserId in chat rooms.
// App-wide settings
router.get('/chat/notification-settings', requireLogin, async (req, res) => {
res.json(await chat.users.getNotificationSettings(req.user.id));
});
router.put('/chat/notification-settings', requireLogin, async (req, res) => {
const { pushLevel, mutedUntil, disabledEvents } = req.body;
res.json(
await chat.users.updateNotificationSettings(req.user.id, { pushLevel, mutedUntil, disabledEvents }),
);
});
// One room
router.put('/chat/rooms/:roomId/notification-settings', requireLogin, async (req, res) => {
const { pushLevel, mutedUntil, disabledEvents } = req.body;
try {
res.json(
await chat.rooms.updateNotificationSettings(req.params.roomId, req.user.id, {
pushLevel,
mutedUntil,
disabledEvents,
}),
);
} catch (e) {
if (e instanceof AbrovaChatError) return res.status(e.statusCode).json({ message: e.message });
throw e;
}
});Only pass on the three fields shown. Never take the user id from the request body — use the id from your own session.
Common calls:
// Chat notifications off
await chat.users.updateNotificationSettings(userId, { pushLevel: 'none' });
// Back on
await chat.users.updateNotificationSettings(userId, { pushLevel: 'all' });
// Only when mentioned, and never for reactions
await chat.users.updateNotificationSettings(userId, {
pushLevel: 'mentions',
disabledEvents: ['reaction'],
});
// Mute one room for 8 hours, or unmute it
await chat.rooms.updateNotificationSettings(roomId, userId, {
mutedUntil: new Date(Date.now() + 8 * 60 * 60 * 1000),
});
await chat.rooms.updateNotificationSettings(roomId, userId, { mutedUntil: null });Updates are partial: fields you leave out keep their current value.
Your app#
The app calls your backend like any other request of yours. For example, a settings switch:
async function setChatNotifications(enabled: boolean) {
await fetch('https://api.example.com/chat/notification-settings', {
method: 'PUT',
headers: { Authorization: `Bearer ${sessionToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ pushLevel: enabled ? 'all' : 'none' }),
});
}// Flutter
Future<void> muteRoom(String roomId, Duration duration) => dio.put(
'/chat/rooms/$roomId/notification-settings',
data: {'mutedUntil': DateTime.now().add(duration).toUtc().toIso8601String()},
);REST reference#
All endpoints take x-api-key, plus either x-server-secret (your server, acting for any user) or x-user-token (a user token, acting only for that user).
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/users/notification-settings?userId=… | App-wide settings |
PUT | /api/v1/users/notification-settings | Update app-wide settings |
GET | /api/v1/rooms/{roomId}/notification-settings?userId=… | Settings for one room |
PUT | /api/v1/rooms/{roomId}/notification-settings | Update settings for one room |
curl -X PUT https://chat.abrova.ir/api/v1/rooms/$ROOM_ID/notification-settings \
-H "x-api-key: $ABROVA_API_KEY" \
-H "x-server-secret: $ABROVA_SERVER_SECRET" \
-H "Content-Type: application/json" \
-d '{"userId": "user-123", "pushLevel": "mentions"}'With a user token, leave userId out; the token decides the user.
Response:
{
"userId": "user-123",
"scope": "room",
"roomId": "5b1f…",
"pushLevel": "mentions",
"mutedUntil": null,
"disabledEvents": [],
"updatedAt": "2026-09-16T10:00:00.000Z"
}App-wide settings return "scope": "app" and no roomId.
| Status | When |
|---|---|
400 | Missing userId (server secret), or an invalid value |
403 user_mismatch | A user token tried to change another user |
404 | The room doesn't exist in your project, or the user isn't in it |
Best Practices#
Your server knows who is signed in; the app should never pick the user id or hold the server secret
Offer "1 hour", "8 hours", "Until I turn it back on" — timed mutes end on their own
pushLevel 'mentions' keeps large rooms quiet without missing what matters
Read the settings when the screen opens instead of keeping a local copy