Node.js SDK Guide
Server-side guide for the Abrova Push Node.js SDK — installation, security (API key + server secret), push, inbox, devices, templates, segments, scheduled, webhooks, analytics.
Server-side SDK for sending push notifications, managing inbox, devices, and more from your Node.js backend. For the per-platform SDK reference page, see Node.js SDK reference.
Installation#
The SDK installs from Abrova Center in two steps.
- Point the
@abrovascope at Abrova Center in your project's.npmrc:
# .npmrc (in your project root) @abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/
- Install the package:
npm install @abrova/push-node
Initialize
import { AbrovaPush } from '@abrova/push-node';
const abrovaPush = new AbrovaPush({
apiKey: process.env.ABROVA_API_KEY, // ab_live_xxx
serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_xxx (required, server only)
});Quick Example
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'Order Shipped',
body: 'Your order #1234 has been shipped!',
data: { orderId: '1234' },
});Error Handling
import { AbrovaPush, AbrovaPushError } from '@abrova/push-node';
try {
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'Hello',
body: 'World',
});
} catch (error) {
if (error instanceof AbrovaPushError) {
// error.statusCode, error.message
}
}Security — Authentication#
Server-side operations require both API Key and Server Secret for authentication. This ensures that only your backend server can perform privileged operations.
Two Credential Types
- API Key (
ab_live_xxx) — Used by client SDKs (iOS, Android, Flutter, Web). Can be embedded in mobile/web apps. - Server Secret (
ab_srv_xxx) — Required for all server-side operations. Used by Node.js SDK and direct REST API calls from your backend. Never expose this in client-side code!
Why Both Are Required
- API Key alone can be extracted from mobile apps
- Server Secret ensures the request comes from your trusted backend
- Prevents unauthorized server-side operations even if API key leaks
- Simplifies security — no need to decide which operations need extra protection
Where to Find Your Credentials
- Go to Abrova Console
- Create a new project (add the Server / API platform to get a server secret) or select an existing one
- The API Key and Server Secret are shown in full at project creation
- Save both immediately — they are only shown once!
- If lost (or never created), regenerate / generate them from Project Settings → Credentials
Environment Variables
# .env file ABROVA_API_KEY=ab_live_abc123def456... ABROVA_SERVER_SECRET=ab_srv_xyz789...
import { AbrovaPush } from '@abrova/push-node';
const abrovaPush = new AbrovaPush({
apiKey: process.env.ABROVA_API_KEY,
serverSecret: process.env.ABROVA_SERVER_SECRET,
});Example — Order Update Notification
// services/notification.service.ts
import { AbrovaPush } from '@abrova/push-node';
const abrovaPush = new AbrovaPush({
apiKey: process.env.ABROVA_API_KEY,
serverSecret: process.env.ABROVA_SERVER_SECRET,
});
export async function notifyOrderShipped(userId: string, orderId: string, trackingUrl: string) {
// Send push notification
await abrovaPush.push.sendToUser({
userId,
title: 'Order Shipped!',
body: `Your order #${orderId} is on its way`,
data: { orderId, trackingUrl },
deepLink: `myapp://orders/${orderId}`,
});
// Also add to inbox for permanent record
await abrovaPush.inbox.sendToUser({
userId,
content: {
title: 'Order Shipped',
body: `Your order #${orderId} has been shipped. Track your package.`,
deepLink: `myapp://orders/${orderId}`,
},
category: 'orders',
});
}
// Usage in your order controller
app.post('/orders/:orderId/ship', async (req, res) => {
const { orderId } = req.params;
const order = await Order.findById(orderId);
order.status = 'shipped';
order.trackingUrl = req.body.trackingUrl;
await order.save();
await notifyOrderShipped(order.userId, orderId, order.trackingUrl);
res.json({ success: true });
});Client vs Server Authentication
| Option | Type | Required | Description |
|---|---|---|---|
| iOS / Android / Flutter | Mobile SDK | - | API Key only |
| Web Browser | Web SDK | - | API Key only |
| Node.js Backend | Node.js SDK | - | API Key + Server Secret |
| Direct REST API | curl / fetch / axios | - | API Key + Server Secret |
Push — Send Notifications#
Send to User
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'New Message',
body: 'You have a new message from John',
data: { chatId: 'chat-456' },
deepLink: 'myapp://chat/456',
});Send to Device
await abrovaPush.push.sendToDevice({
deviceId: 'device-uuid',
title: 'Welcome!',
body: 'Thanks for installing our app',
});Send to Multiple Devices
await abrovaPush.push.sendToDevices({
deviceIds: ['device-1', 'device-2', 'device-3'],
title: 'Update Available',
body: 'A new version is available',
});Send to Topic
await abrovaPush.push.sendToTopic({
topic: 'promotions',
title: 'Flash Sale!',
body: '50% off everything today only',
imageUrl: 'https://example.com/sale.jpg',
});Send to Segment
await abrovaPush.push.sendToSegment({
segmentId: 'vip-customers-segment-id',
title: 'Exclusive Offer',
body: 'Just for our VIP customers',
});Broadcast
await abrovaPush.push.broadcast({
title: 'App Update',
body: 'Check out the new features!',
});Rich Notifications
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'New Product',
body: 'Check out our latest arrival',
imageUrl: 'https://example.com/product.jpg',
actions: [
{ id: 'buy', title: 'Buy Now', action: 'BUY_PRODUCT' },
{ id: 'later', title: 'Save for Later' },
],
deepLink: 'myapp://product/123',
badge: 1,
sound: 'notification.wav',
priority: 'high',
ttl: 3600, // 1 hour
});Using Templates
await abrovaPush.push.sendToUser({
userId: 'user-123',
templateId: 'order-shipped-template-id',
templateVariables: {
orderNumber: '#1234',
trackingUrl: 'https://track.example.com/1234',
},
});App Identifier (Multi-app Targeting)
// Send only to a specific app on the device
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'New Order',
body: 'Your order is ready',
appIdentifier: 'com.myshop.app', // only this app receives it
});appIdentifier If your project has multiple apps (e.g. a shopping app and a chat app), use appIdentifier to target a specific app. This is the bundle ID / package name of the app (e.g. com.myapp.ios).
Response
const result = await abrovaPush.push.sendToUser({...});
console.log(result);
// {
// success: 3,
// failed: 0,
// messageId: '6f1c…', // use with abrovaPush.receipts
// billing: {
// messagesCharged: 3, // messages counted for this send
// remaining: 9997 // free messages left this month
// }
// }Inbox#
The read / update calls (getMessages, getMessage, updateStatus, updateMultiple, markAllRead, delete) use the inbox routes the client SDKs call. If the project turns on Require signed user tokens for the inbox (Project Settings → Abrova Push), those routes need a user's x-user-token, which this SDK does not send, so they return 401 (token_required) for user-owned messages. The send* / broadcast calls are not affected.
// Send to single user
await abrovaPush.inbox.sendToUser({
userId: 'user-123',
content: {
title: 'Welcome!',
body: 'Thanks for joining us. Here are some tips to get started.',
imageUrl: 'https://example.com/welcome.jpg',
deepLink: 'myapp://onboarding',
},
category: 'onboarding',
expiresAt: '2025-12-31T23:59:59Z',
});
// Send to multiple users
await abrovaPush.inbox.sendToUsers({
userIds: ['user-1', 'user-2', 'user-3'],
content: { title: 'New Feature', body: 'Check out our latest feature' },
});
// Send to device
await abrovaPush.inbox.sendToDevice({
deviceId: 'device-uuid',
content: { title: 'Device-specific Message', body: 'This message is for this device only' },
});
// Broadcast
await abrovaPush.inbox.broadcast({
content: { title: 'Holiday Sale', body: 'Up to 70% off!' },
category: 'promotions',
expiresAt: '2025-01-05T23:59:59Z',
});
// Target specific app
await abrovaPush.inbox.sendToUser({
userId: 'user-123',
appIdentifier: 'com.myshop.app',
content: { title: 'Order Shipped', body: 'Your order is on its way!' },
category: 'orders',
});
// Get user's inbox — resolves to { messages, total, unreadCount }
const inbox = await abrovaPush.inbox.getMessages({
userId: 'user-123',
status: 'unread',
limit: 20,
offset: 0,
});
// Mark single message as read
await abrovaPush.inbox.updateStatus('message-id', 'read');
// Mark multiple messages as read
await abrovaPush.inbox.updateMultiple(
['msg-1', 'msg-2', 'msg-3'],
'read'
);
// Mark all as read for a user
await abrovaPush.inbox.markAllRead({ userId: 'user-123' });Devices#
// List devices
const devices = await abrovaPush.devices.list();
// Subscribe / unsubscribe to topic
await abrovaPush.devices.subscribeTopic('device-uuid', 'news');
await abrovaPush.devices.unsubscribeTopic('device-uuid', 'promotions');
// Associate / clear user
await abrovaPush.devices.setUserId('device-uuid', 'user-123');
await abrovaPush.devices.clearUserId('device-uuid');
// Delete devices
await abrovaPush.devices.delete('device-uuid');
await abrovaPush.devices.bulkDelete(['device-1', 'device-2', 'device-3']);
// Paginated, filtered list (includes inactive devices unless isActive is set)
const { items, total } = await abrovaPush.devices.listPage({
platform: 'ios',
isActive: true,
page: 1,
limit: 50,
});
// Values you can filter on (app versions, SDK versions, failure reasons)
const options = await abrovaPush.devices.filterOptions();
// Stop sending to a device but keep its history, and undo it
await abrovaPush.devices.unregister('device-uuid');
await abrovaPush.devices.reactivate('device-uuid');Templates#
Reusable notification content with variable substitution.
// Create template
const template = await abrovaPush.templates.create({
name: 'Order Shipped',
title: 'Your order {{orderNumber}} has shipped!',
body: 'Track your package: {{trackingUrl}}',
variables: ['orderNumber', 'trackingUrl'],
localizations: {
fa: {
title: 'سفارش {{orderNumber}} ارسال شد!',
body: 'پیگیری مرسوله: {{trackingUrl}}',
},
},
});
// List
const templates = await abrovaPush.templates.list();
// Render (preview)
const rendered = await abrovaPush.templates.render(
'template-id',
{ orderNumber: '#1234', trackingUrl: 'https://track.example.com/1234' },
'en' // locale
);
// Update / Delete / Duplicate
await abrovaPush.templates.update('template-id', {
body: 'Updated body text with {{variable}}',
});
await abrovaPush.templates.delete('template-id');
const copy = await abrovaPush.templates.duplicate('template-id');Segments#
// Create segment
const segment = await abrovaPush.segments.create({
name: 'VIP Customers',
description: 'Customers with more than 10 orders',
color: '#FFD700',
filters: [
{ field: 'metadata.orderCount', operator: 'greater_than', value: 10 },
{ field: 'metadata.tier', operator: 'equals', value: 'gold' },
],
});Filter Operators
| Operator | Description |
|---|---|
| equals / not_equals | Exact match / no match |
| contains / not_contains | String (or topics) contains / does not contain |
| starts_with | String starts with |
| in / not_in | Value in / not in array |
| greater_than / greater_than_or_equal | Numeric comparison |
| less_than / less_than_or_equal | Numeric comparison |
| exists | Field is set |
// List, get, devices
const segments = await abrovaPush.segments.list();
const segment = await abrovaPush.segments.get('segment-id');
const devices = await abrovaPush.segments.getDevices('segment-id');
// Recalculate
const { deviceCount } = await abrovaPush.segments.recalculate('segment-id');
await abrovaPush.segments.recalculateAll();Scheduled Messages#
// Schedule
const scheduled = await abrovaPush.scheduled.create({
title: 'Reminder',
body: "Don't forget to complete your purchase!",
targetType: 'user',
targetValue: 'user-123',
scheduledAt: '2025-02-14T09:00:00Z',
timezone: 'Asia/Tehran',
});Target Types
| Option | Type | Required | Description |
|---|---|---|---|
| broadcast | - | - | Not required (every device). The 0.1.4 SDK type lists `all`, which the API rejects — pass `broadcast` (cast it, e.g. `targetType: 'broadcast' as any`, until the type is fixed) |
| user | - | - | targetValue = User ID |
| device | - | - | targetValue = Device ID |
| topic | - | - | targetValue = Topic name |
| segment | - | - | targetValue = Segment ID |
// List all scheduled messages
const all = await abrovaPush.scheduled.list();
// List pending only
const pending = await abrovaPush.scheduled.listPending();
// Get specific scheduled message
const message = await abrovaPush.scheduled.get('scheduled-id');
// Update scheduled message
await abrovaPush.scheduled.update('scheduled-id', {
scheduledAt: '2025-02-15T10:00:00Z',
});
// Cancel scheduled message
await abrovaPush.scheduled.cancel('scheduled-id');
// Delete scheduled message
await abrovaPush.scheduled.delete('scheduled-id');Webhooks#
// Create webhook
const webhook = await abrovaPush.webhooks.create({
name: 'Order Events',
url: 'https://example.com/webhooks/abrova-push',
events: [
'message.sent',
'message.delivered',
'message.failed',
],
secret: 'my-webhook-secret',
maxRetries: 3,
timeoutMs: 5000,
});Available Events
| Option | Type | Required | Description |
|---|---|---|---|
| message.sent | - | - | Message was sent |
| message.delivered | - | - | Message was delivered to device |
| message.failed | - | - | Message failed to send |
| message.no_recipients | - | - | Target had no registered devices |
| device.registered | - | - | New device registered |
| device.unregistered | - | - | Device was unregistered |
| device.updated | - | - | Device was updated |
// Test, get logs, regenerate secret
await abrovaPush.webhooks.test('webhook-id', 'message.delivered');
const logs = await abrovaPush.webhooks.getLogs('webhook-id', 50);
const { secret } = await abrovaPush.webhooks.regenerateSecret('webhook-id');
// Manage
const webhooks = await abrovaPush.webhooks.list();
await abrovaPush.webhooks.update('webhook-id', {
events: ['message.delivered', 'message.failed'],
isActive: true,
});
await abrovaPush.webhooks.delete('webhook-id');Analytics#
// Overview stats
const overview = await abrovaPush.analytics.getOverview();
// overview: {
// totalDevices: 15000,
// activeDevices: 12500,
// totalMessages: 50000,
// totalDelivered: 48500,
// totalFailed: 500,
// deliveryRate: 97,
// platformDistribution: { android: 8000, ios: 6500, web: 500 }
// }
// Get last 30 days of stats
const daily = await abrovaPush.analytics.getDaily(30);
// daily: [
// { date: '2025-02-01', sent: 1500, delivered: 1450, pending: 20, failed: 30 },
// { date: '2025-02-02', sent: 1600, delivered: 1550, pending: 10, failed: 40 },
// ...
// ]
// Get hourly distribution for last 7 days
const hourly = await abrovaPush.analytics.getHourly(7);
// hourly: [
// { hour: 0, count: 120 },
// { hour: 1, count: 80 },
// ...
// { hour: 23, count: 150 }
// ]
// App-level stats
const stats = await abrovaPush.analytics.getStats();
// stats: {
// totalDevices: 15000,
// activeDevices: 12500,
// androidDevices: 8000,
// iosDevices: 6500,
// totalTopics: 25,
// topics: ['news', 'promotions', 'updates', ...],
// totalUsers: 10000
// }
// Recent messages
const recent = await abrovaPush.analytics.getRecent(10);Delivery receipts
A send's success count means the push service accepted the notification. Receipts show what happened on each device. See Delivery Receipts.
const { messageId } = await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Hi', body: 'Hello' });
// Per device, for one message
const receipts = await abrovaPush.receipts.getForMessage(messageId!);
const stats = await abrovaPush.receipts.getMessageStats(messageId!);
// Receipt log with filters
const { items, total } = await abrovaPush.receipts.list({
status: 'failed',
platform: 'android',
startDate: new Date(Date.now() - 7 * 86400_000),
page: 1,
limit: 100,
});
// Delivery analytics (default: last 30 days)
const analytics = await abrovaPush.receipts.analytics({ bucket: 'day' });
console.log(analytics.totals, analytics.byPlatform, analytics.topFailureReasons);