Notifications API
FonProxy Notifications API reference documentation.
Notifications API
All endpoints require authentication (Authorization: Bearer <token> or API key).
All notification IDs are hashid-encoded.
List notifications
GET /notifications?page=1&limit=20&category=order
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 20 | Items per page (max 50) |
| category | string | β | Filter by category: general, auth, order, payment |
Response:
{
"notifications": [
{
"id": "K4Xb9rPmYz",
"type": "order_activated",
"category": "order",
"subject": "notification.order_activated_subject",
"payload": {
"subject": "notification.order_activated_subject",
"blocks": [
{ "type": "heading", "text": "notification.order_activated_heading" },
{ "type": "text", "text": "notification.order_activated_text" },
{ "type": "divider" },
{ "type": "key_value", "label": "notification.order_activated_type", "value": "Residential Giga" },
{ "type": "key_value", "label": "notification.order_activated_quantity", "value": "10 GB" },
{ "type": "key_value", "label": "notification.order_activated_total", "value": "$5.00" },
{ "type": "divider" },
{ "type": "heading", "text": "notification.order_activated_getting_started" },
{ "type": "text", "text": "notification.order_activated_step_1" },
{ "type": "text", "text": "notification.order_activated_step_2" },
{ "type": "text", "text": "notification.order_activated_step_3" },
{ "type": "copyable", "value": "https://fonproxy.com/orders/K4Xb9rPmYz", "label": "notification.order_activated_order_link" },
{ "type": "button", "text": "notification.order_activated_view_order", "url": "https://fonproxy.com/orders/K4Xb9rPmYz" },
{ "type": "divider" },
{ "type": "muted", "text": "notification.order_activated_footer" }
]
},
"read": false,
"createdAt": "2026-03-22T10:00:00.000Z"
}
],
"total": 1,
"page": 1,
"pages": 1
}
Get unread count
GET /notifications/unread-count
Response:
{
"unread": 3
}
Get single notification
GET /notifications/:id
Response:
{
"id": "K4Xb9rPmYz",
"type": "welcome",
"category": "general",
"subject": "notification.welcome_subject",
"payload": {
"subject": "notification.welcome_subject",
"blocks": [
{ "type": "heading", "text": "notification.welcome_heading" },
{ "type": "text", "text": "notification.welcome_text" },
{ "type": "key_value", "label": "notification.welcome_email_label", "value": "user@example.com" },
{ "type": "divider" },
{ "type": "muted", "text": "notification.welcome_footer" }
]
},
"read": true,
"createdAt": "2026-03-22T09:00:00.000Z"
}
Mark notification as read
PATCH /notifications/:id/read
Response:
{
"id": "K4Xb9rPmYz",
"type": "welcome",
"category": "general",
"subject": "notification.welcome_subject",
"payload": { "..." : "..." },
"read": true,
"createdAt": "2026-03-22T09:00:00.000Z"
}
Mark all notifications as read
PATCH /notifications/read-all
Response:
{
"success": true
}
Notification block types
The payload.blocks array contains structured content blocks that the frontend renders.
Each block has a type and type-specific fields:
| Type | Fields | Description |
|---|---|---|
heading | text | Section heading |
text | text | Paragraph text (may contain HTML for email) |
muted | text | Small muted/secondary text |
code | code, label? | Large monospace block (e.g. auth codes) |
copyable | value, label? | Inline copyable value (e.g. proxy endpoint, URL) |
button | text, url | Call-to-action button / link |
divider | β | Horizontal separator |
key_value | label, value | Label: value pair |
Text fields containing notification.* keys should be resolved via the translation system on the frontend.
Notification categories
| Category | Description |
|---|---|
general | Welcome, system announcements |
auth | Auth codes (email-only, not stored in DB) |
order | Order activated, expired, etc. |
payment | Top-up completed, failed, etc. |
admin | Admin broadcast notifications |
referral | Referral registration & income |
Channel restrictions
Not all notifications are delivered to all channels:
| Notification | database | telegram | Notes | |
|---|---|---|---|---|
| Auth code | β | β | β | Security: codes never persisted |
| Welcome | β | β | β | All channels |
| Order activated | β | β | β | All channels |
| Admin broadcast | β | β | β | Admin-configurable channels |
| Referral signup | β | β | β | All channels |
| Referral income | β | β | β | All channels |
Channel preferences
Users can enable or disable notification channels. Disabled channels are silently skipped when sending.
List channels
GET /notifications/channels
Auth: Required.
Response:
{
"channels": [
{
"name": "email",
"label": "Email",
"available": true,
"userControllable": true,
"enabled": true
},
{
"name": "database",
"label": "In-app notifications",
"available": true,
"userControllable": true,
"enabled": true
},
{
"name": "telegram",
"label": "Telegram",
"available": true,
"userControllable": true,
"enabled": true
}
]
}
available β whether the channel is operationally configured on the server (e.g. SMTP set up for email).
enabled β whether this user has the channel turned on.
userControllable β whether the user is allowed to toggle it (some system channels may be read-only in future).
Update channel preference
PATCH /notifications/channels/:channel
Auth: Required.
Path param: :channel β channel name (email, database, etc.)
Body:
{ "enabled": false }
Response: Full updated channels list (same shape as GET /notifications/channels).
Unsubscribe (public, no auth)
Every email sent includes a one-click unsubscribe link in the footer. Clicking it disables the email channel for that user immediately and shows a branded confirmation page.
GET /notifications/unsubscribe?token=<encrypted-token>
Returns: HTML page (200 on success, 400 on invalid token).
The token is AES-256-GCM encrypted and contains { userId, channel }. It is generated server-side and embedded in every outgoing email. No database lookup is needed to verify it β the encryption itself acts as the authentication.
Re-enabling email notifications is done from the authenticated /notifications/channels endpoint.