In-App Inbox
Get Inbox Session
Get a Novu session token and WebSocket URL for real-time inbox updates.
POST /api/v1/notifications/get-inbox-session
{}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"socketUrl": "wss://novu-ws.yocaso.dev",
"expiresIn": "1296000"
}
| Field | Type | Description |
|---|---|---|
token | string | JWT for WebSocket auth |
socketUrl | string | Novu WebSocket URL |
expiresIn | string | Token validity in seconds (~15 days). String type (int64 protojson convention). |
Real-Time Inbox Updates
The socketUrl + token from get-inbox-session open a Socket.IO connection (Novu) that pushes inbox changes live — no polling needed for the common case. Authenticate with the session token, both as an Authorization: Bearer <token> header and as a token connect param.
The server emits these events:
| Event | Fires when |
|---|---|
notification_received | A new message arrives — refresh the feed or prepend the payload |
unseen_count_changed | The unseen badge count changes |
unread_count_changed | The unread count changes |
Novu payloads may identify a message as either
_idormessageId— accept both.
Resilience. If the socket fails to connect or drops, reconnect with exponential backoff (a handful of attempts), and while disconnected fall back to polling get-inbox-unseen-count every ~30 seconds so the badge stays roughly current. The session token expires (~15 days) — call get-inbox-session again to refresh it.
Get Inbox Feed
POST /api/v1/notifications/get-inbox-feed
{
"page": 1,
"pageSize": 20
}
Pagination is 1-based. The optional filter object supports unseenOnly,
unreadOnly, feedIds, categories and tags (plain strings), plus archived —
which is how you render an archive view:
{
"page": 1,
"pageSize": 20,
"filter": {"archived": true}
}
Archived messages are excluded from the default feed, so omitting archived gives you
the live inbox.
Response:
{
"messages": [
{
"messageId": "69c0df5391079c0a4596f7d3",
"notificationId": "69c0df5291079c0a4596f79b",
"title": "Weekly Check-in",
"body": "Time to review your progress.",
"data": {
"title": "Weekly Check-in",
"body": "Time to review your progress.",
"deep_link": "socayo://screen?name=weekly"
},
"deepLink": "socayo://screen?name=weekly",
"status": "MESSAGE_STATUS_UNSEEN",
"createdAt": "2026-03-23T06:36:03.578Z"
}
],
"totalCount": 6,
"page": 1,
"pageSize": 20
}
hasMoreonly appears whentrue. Fields likecategory,deepLink,actionsare omitted when empty.
Get Unseen Count
POST /api/v1/notifications/get-inbox-unseen-count
Response:
{
"unseenCount": 3,
"unreadCount": 5
}
Counts are capped at 10.
hasMoreUnseen/hasMoreUnreadappear whentrue— display as "9+".
Mark Messages
Mark a single message:
POST /api/v1/notifications/mark-inbox-message
{
"messageId": "msg_123",
"markAs": "MESSAGE_STATUS_READ"
}
Response:
{
"messages": [
{
"messageId": "msg_123",
"channel": "in_app",
"seen": true,
"content": "Time to review your progress.",
"status": "sent",
"createdAt": "2026-03-23T06:36:03.578Z",
"lastSeenDate": "2026-03-23T10:24:35.518Z"
}
]
}
Mark all messages:
POST /api/v1/notifications/mark-all-inbox-messages
{
"markAs": "MESSAGE_STATUS_SEEN"
}
Response: {"updatedCount": 5}
Status options: MESSAGE_STATUS_SEEN, MESSAGE_STATUS_READ, MESSAGE_STATUS_UNSEEN, MESSAGE_STATUS_UNREAD.
Delete Inbox Message
POST /api/v1/notifications/delete-inbox-message
{
"messageId": "msg_123"
}
Response:
{
"status": {
"acknowledged": true,
"status": "deleted"
}
}
Archive Messages
Archiving hides a message from the default feed but keeps it — unlike
delete-inbox-message, it is reversible.
messageId vs notificationIdThe archive and action endpoints below are addressed by notificationId, not
messageId. Both appear on every feed entry and they are different values — sending a
messageId here fails to find the message. Only mark-inbox-message and
delete-inbox-message take messageId.
POST /api/v1/notifications/archive-inbox-message
{
"notificationId": "69c0df5291079c0a4596f79b"
}
Response:
{
"status": {
"acknowledged": true,
"status": "archived"
}
}
Restore it with the mirror call:
POST /api/v1/notifications/unarchive-inbox-message
{
"notificationId": "69c0df5291079c0a4596f79b"
}
Response: the same envelope, with status: "unarchived".
Every endpoint in this section treats "not found" as idempotent success, not a 404:
you get acknowledged: true with status: "not_found". So a client can archive on every
tap without first checking, and a retry after a dropped response is safe. Branch on
status === "not_found" if you need to tell the two apart.
Archive in Bulk
Clear the whole inbox, or just the messages the user has already read — the second is what a "clear read" button should call.
POST /api/v1/notifications/archive-all-inbox-messages
{}
Response: {"archivedCount": 12}
POST /api/v1/notifications/archive-all-read-inbox-messages
{}
Response: {"archivedCount": 7}
Both accept an optional tags array to scope the sweep to particular workflow tags,
OR-ed together:
{
"tags": ["coaching", "reminders"]
}
Message Actions
A message can carry up to two call-to-action buttons. Each entry from
POST /api/v1/notifications/get-inbox-feed exposes them under actions, with
isPrimary marking which is which:
{
"actions": [
{
"actionId": "log-workout",
"label": "Log it",
"url": "travila://workout/new",
"isPrimary": true,
"completed": false
}
]
}
When the user taps one, record it so the button renders as done — and so it stays done on the user's other devices.
POST /api/v1/notifications/complete-inbox-action
{
"notificationId": "69c0df5291079c0a4596f79b",
"actionType": "ACTION_TYPE_PRIMARY"
}
Response:
{
"status": {
"acknowledged": true,
"status": "completed"
}
}
actionType is ACTION_TYPE_PRIMARY or ACTION_TYPE_SECONDARY. To undo — the user
cancelled the flow the button opened, or the write it represented failed — revert it:
POST /api/v1/notifications/revert-inbox-action
{
"notificationId": "69c0df5291079c0a4596f79b",
"actionType": "ACTION_TYPE_PRIMARY"
}
Response: the same envelope, with status: "reverted" and the action back in its
pending state.