Node.js SDK
Server-side Node.js SDK for sending push notifications, managing devices, templates, segments, scheduled messages, webhooks, and A/B tests.
Push to a single device, all of a user's devices, or topic / segment audiences
Single call to push to every registered device
Images, action buttons, deep links, custom data
Reusable notification copy with variable substitution
Server-evaluated audiences from user metadata
Send at a specific time + timezone
Subscribe to message sent / delivered / failed and device events
Inbox sends, per-device delivery receipts and delivery analytics from the server
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
Quick Start#
import { AbrovaPush } from '@abrova/push-node';
const abrovaPush = new AbrovaPush({
apiKey: process.env.ABROVA_API_KEY, // ab_live_...
serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_..., server only
});
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'Order Shipped',
body: 'Your order #1234 has been shipped!',
data: { orderId: '1234' },
});Both apiKey and serverSecret are required for all server-side operations. Find them in your Abrova Console under project settings. Never ship the serverSecret to client-side code.
Send Push Notifications#
// Send to a specific device
await abrovaPush.push.sendToDevice({
deviceId: 'device-uuid',
title: 'Hello',
body: 'Welcome to our app!',
});
// Send to all of a user's devices
await abrovaPush.push.sendToUser({
userId: 'user-123',
title: 'New Message',
body: 'You have a new message',
});
// Send to multiple specific devices
await abrovaPush.push.sendToDevices({
deviceIds: ['device-1', 'device-2', 'device-3'],
title: 'Update Available',
body: 'A new version is available',
});
// Send to a topic
await abrovaPush.push.sendToTopic({
topic: 'promotions',
title: 'Flash Sale!',
body: '50% off everything today only',
imageUrl: 'https://example.com/sale.jpg',
});
// Send to a segment
await abrovaPush.push.sendToSegment({
segmentId: 'segment-uuid',
title: 'Exclusive Offer',
body: 'Just for our VIP customers',
});
// Broadcast to every registered device
await abrovaPush.push.broadcast({
title: 'App Update',
body: 'Check out the new features!',
});Multi-App Targeting#
If your project hosts multiple apps (e.g. a shopping app and a chat app), use appIdentifier (the bundle ID / package name) to target a specific app:
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 is optional. When omitted, the notification is sent to all apps registered under the project. Single-app projects don't need to set it.
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: 'OPEN_PRODUCT' },
{ id: 'later', title: 'Save for Later' },
],
deepLink: 'myapp://product/123',
data: { productId: '123' },
});Templates#
Reusable copy with variable substitution. Useful when the same message shape ships in many flows (order shipped, password reset, etc.).
// Send using a template
await abrovaPush.push.sendToUser({
userId: 'user-123',
templateId: 'order-shipped-template',
templateVariables: {
orderNumber: '#1234',
trackingUrl: 'https://track.example.com/1234',
},
});
// Create a template
const template = await abrovaPush.templates.create({
name: 'Order Shipped',
title: 'Your order {{orderNumber}} has shipped!',
body: 'Track your package: {{trackingUrl}}',
variables: ['orderNumber', 'trackingUrl'],
});
// List templates
const templates = await abrovaPush.templates.list();
// Preview render
const rendered = await abrovaPush.templates.render(template.id, {
orderNumber: '#1234',
trackingUrl: 'https://track.example.com/1234',
});Inbox Messages#
Push delivers a message to the device. Inbox stores it persistently so the user can revisit later.
// Send inbox message to a user
await abrovaPush.inbox.sendToUser({
userId: 'user-123',
content: {
title: 'Welcome!',
body: 'Thanks for joining us.',
imageUrl: 'https://example.com/welcome.jpg',
deepLink: 'myapp://onboarding',
},
});
// 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' },
});
// Broadcast with expiry
await abrovaPush.inbox.broadcast({
content: { title: 'Holiday Sale', body: 'Up to 70% off!' },
expiresAt: '2025-12-31T23:59:59Z',
});Device Management#
// List all devices
const devices = await abrovaPush.devices.list();
// Subscribe a device to a topic
await abrovaPush.devices.subscribeTopic('device-uuid', 'news');
await abrovaPush.devices.unsubscribeTopic('device-uuid', 'news');
// Associate device with a user
await abrovaPush.devices.setUserId('device-uuid', 'user-123');
// Delete a device
await abrovaPush.devices.delete('device-uuid');
// 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');Segments#
Segments evaluate user metadata server-side. Use them when you need "all users with more than 10 orders" or "all users in Tehran on the premium tier."
// Create a segment
const segment = await abrovaPush.segments.create({
name: 'VIP Customers',
description: 'Customers with more than 10 orders',
filters: [
{ field: 'metadata.orders', operator: 'greater_than', value: 10 },
],
});
// Get devices in a segment
const devices = await abrovaPush.segments.getDevices(segment.id);
// Recalculate segment membership (e.g. after metadata changes)
await abrovaPush.segments.recalculate(segment.id);Scheduled Messages#
// Schedule a message
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',
});
// List pending messages
const pending = await abrovaPush.scheduled.listPending();
// Cancel a scheduled message
await abrovaPush.scheduled.cancel(scheduled.id);Webhooks#
Subscribe to message (message.sent, message.delivered, message.failed, message.no_recipients) and device (device.registered, device.unregistered, device.updated) events so your backend can react in real time.
// Create a 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-secret-key',
});
// Test a webhook
await abrovaPush.webhooks.test(webhook.id, 'message.delivered');
// Read delivery logs for debugging
const logs = await abrovaPush.webhooks.getLogs(webhook.id);Analytics#
// Device and message totals, delivery rate, platform distribution const overview = await abrovaPush.analytics.getOverview(); // Daily breakdown const daily = await abrovaPush.analytics.getDaily(30); // last 30 days // App-level totals (devices per platform, topics, users) const stats = await abrovaPush.analytics.getStats();
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);A/B Tests and In-App Messages#
Create A/B tests and in-app messages in Abrova Console.
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, error.response
}
}Configuration#
const abrovaPush = new AbrovaPush({
apiKey: process.env.ABROVA_API_KEY!,
serverSecret: process.env.ABROVA_SERVER_SECRET!,
});Environment Variables
We recommend storing credentials in environment variables, never in version control:
# .env ABROVA_API_KEY=ab_live_your_api_key ABROVA_SERVER_SECRET=ab_srv_your_server_secret
Credential Reference
| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | String | Yes | Used for client-side SDKs and server-side SDKs (ab_live_... format) |
| serverSecret | String | Yes | Required for server-side operations (ab_srv_... format). Never expose in client code. |
API Reference#
push — sending notifications
| Option | Type | Required | Description |
|---|---|---|---|
| sendToUser(payload) | - | - | Send to all of a user's devices |
| sendToDevice(payload) | - | - | Send to a single device |
| sendToDevices(payload) | - | - | Send to multiple specific devices |
| sendToTopic(payload) | - | - | Send to every subscriber of a topic |
| sendToSegment(payload) | - | - | Send to a segment |
| broadcast(payload) | - | - | Send to every registered device |
inbox — persistent message center
| Option | Type | Required | Description |
|---|---|---|---|
| sendToUser(payload) | - | - | Add an inbox message for a single user |
| sendToUsers(payload) | - | - | Add an inbox message for multiple users |
| broadcast(payload) | - | - | Add an inbox message for every user that has a device with a user ID |
devices — device management
| Option | Type | Required | Description |
|---|---|---|---|
| list() | - | - | List all active devices |
| listPage(filters) | - | - | Paginated, filtered device list (platform, active, user, app or SDK version, last seen) |
| filterOptions() | - | - | App versions, SDK versions and failure reasons you can filter on |
| unregister(deviceId) | - | - | Stop sending to a device, keep its history |
| reactivate(deviceId) | - | - | Undo unregister |
| subscribeTopic(deviceId, topic) | - | - | Subscribe a device to a topic |
| unsubscribeTopic(deviceId, topic) | - | - | Unsubscribe a device from a topic |
| setUserId(deviceId, userId) | - | - | Associate a device with a user |
| delete(deviceId) | - | - | Delete a device |
receipts — per-device delivery
| Option | Type | Required | Description |
|---|---|---|---|
| getForMessage(messageId) | - | - | Receipts for every device a message went to |
| getMessageStats(messageId) | - | - | Sent / delivered / failed counts for one message |
| getActionStats(messageId) | - | - | Clicks per action button |
| getForDevice(deviceId, limit?) | - | - | Recent receipts for one device |
| list(filters) | - | - | Paginated receipt log (platform, status, error, user, device, message, dates) |
| analytics(options) | - | - | Totals, per-platform and per-SDK breakdowns, timeseries, top failure reasons |
templates — reusable copy
| Option | Type | Required | Description |
|---|---|---|---|
| create(payload) | - | - | Create a template |
| list() | - | - | List templates |
| render(id, variables) | - | - | Render a template with variable substitution |
segments — audience targeting
| Option | Type | Required | Description |
|---|---|---|---|
| create(payload) | - | - | Create a segment with metadata filters |
| getDevices(segmentId) | - | - | Get devices in a segment |
| recalculate(segmentId) | - | - | Recalculate segment membership |
scheduled — time-based delivery
| Option | Type | Required | Description |
|---|---|---|---|
| create(payload) | - | - | Schedule a message for a specific time + timezone |
| listPending() | - | - | List pending scheduled messages |
| cancel(id) | - | - | Cancel a scheduled message |
webhooks — delivery event subscriptions
| Option | Type | Required | Description |
|---|---|---|---|
| create(payload) | - | - | Create a webhook for message and device events |
| test(id, eventType) | - | - | Trigger a test event |
| getLogs(id) | - | - | Read webhook delivery logs |
analytics — delivery + engagement metrics
| Option | Type | Required | Description |
|---|---|---|---|
| getOverview() | - | - | Device and message totals, delivery rate, platform distribution |
| getDaily(days) | - | - | Per-day stats for the last N days |
| getStats() | - | - | App-level totals (devices per platform, topics, users) |
Best Practices#
Never hardcode apiKey or serverSecret — load from env so credentials don't end up in version control
When the same message shape ships in many flows (order shipped, password reset), templates keep copy consistent and edit-able without a deploy
Topics suit large audiences but the user opts in client-side. Segments evaluate server-side metadata — better for "all premium users in Tehran"-style targeting.
Wrap calls in try/catch and check for AbrovaPushError so you can surface statusCode + response to your logs
message.delivered / message.failed / message.no_recipients let your backend react in real time (e.g. mark order as notified, retry on miss)