Quick Start Guide
Get up and running with OpenPushAPI in under 5 minutes.
Quick Start
OpenPushAPI lets you send push notifications to web, Android, and iOS users from one unified API. This guide walks you through setup in five steps.
Step 1 — Create an account and app
Create a free account at the console. Once logged in, create a new App. You'll receive two credentials:
app_key— Public key, used in your JavaScript SDK. Safe to embed in frontend code.app_secret— Private key, used for server-side API calls. Never expose this in client-side code.
Step 2 — Install the JavaScript SDK
Add these two lines to the <head> or end of <body> on every page of your website:
<!-- Add to your website -->
<script src="https://sdk.openpushapi.com/v1/sdk.js"></script>
<script>
OpenPush.init({
appKey: 'your-app-key-here',
autoPrompt: true,
promptDelay: 3000
});
</script>
Step 3 — Users opt in automatically
With autoPrompt: true, the SDK will show the browser's native push permission dialog to new visitors after promptDelay milliseconds. Once a user accepts, they're automatically registered as a subscriber in your dashboard.
Step 4 — Send your first notification
Use the REST API to send a push notification to all your subscribers:
curl -X POST https://api.openpushapi.com/v1/notifications \
-H "Content-Type: application/json" \
-H "X-App-Key: your-app-key" \
-H "X-App-Secret: your-app-secret" \
-d '{
"title": "Hello World!",
"body": "Your first push notification from OpenPushAPI.",
"url": "https://yoursite.com",
"target_type": "all"
}'
Response
{
"notification_id": 12345,
"status": "queued",
"total_recipients": 847,
"is_ab_test": false,
"message": "Notification queued successfully"
}
Step 5 — Check delivery stats
Check delivery status and click-through rates for any notification:
curl https://api.openpushapi.com/v1/stats/notifications/12345 \
-H "X-App-Key: your-app-key" \
-H "X-App-Secret: your-app-secret"
Authentication
OpenPushAPI uses two header-based credentials for authentication:
| Header | Usage | Where to find |
|---|---|---|
X-App-Key |
All requests (public & private) | Console → App → Settings |
X-App-Secret |
Server-side API requests only | Console → App → Settings |
SDK requests (subscriber registration from the browser) only need X-App-Key. The app key is safe to include in frontend JavaScript.
API requests (sending notifications, managing subscribers) require both headers. The X-App-Secret must never be exposed in frontend code — keep it server-side only.
Security notice: Treat X-App-Secret like a password. Rotate it immediately if you suspect it has been exposed. You can regenerate it from the console.
Your First Notification
Here's a more complete notification with all the commonly used fields:
{
"title": "New Feature: Dark Mode",
"body": "We've just launched dark mode. Update your settings to try it.",
"url": "https://yourapp.com/settings/appearance",
"icon_url": "https://yourapp.com/icons/feature-icon.png",
"image_url": "https://yourapp.com/images/dark-mode-preview.jpg",
"target_type": "tags",
"target_tags": {
"plan": "pro"
},
"target_platforms": ["web", "android"],
"actions": [
{ "title": "Try Dark Mode", "url": "https://yourapp.com/settings/appearance" },
{ "title": "Maybe Later" }
],
"ttl_seconds": 86400
}
Targeting options
target_type |
Description | Required field |
|---|---|---|
"all" |
Send to all active subscribers | — |
"segment" |
Send to a saved segment | target_segment_id |
"tags" |
Send to subscribers matching tags | target_tags |
"external_ids" |
Send to specific user IDs | target_ids |
"subscriber_ids" |
Send to specific subscriber IDs | target_ids |
SDK Installation
The OpenPushAPI JavaScript SDK handles Web Push subscription on your website. It registers a Service Worker, manages the subscription lifecycle, and handles all communication with our API.
/v1/).
Breaking changes will be released as /v2/ — existing /v1/ URLs remain stable indefinitely.
Current version: v1
Option 1 — CDN (recommended)
<!-- Standard (readable) -->
<script src="https://sdk.openpushapi.com/v1/sdk.js"></script>
<!-- Minified (production, ~30% smaller) -->
<script src="https://sdk.openpushapi.com/v1/sdk.min.js"></script>
Option 2 — Self-hosted
Download the SDK files and host them yourself. You'll also need to serve the Service Worker file (openpush-sw.js) from the root of your domain.
# Download SDK + Service Worker and place in your web root
curl -O https://sdk.openpushapi.com/v1/sdk.min.js
curl -O https://sdk.openpushapi.com/v1/openpush-sw.js
# Serve openpush-sw.js from https://yourdomain.com/openpush-sw.js
SDK Configuration
Pass a configuration object to OpenPush.init():
OpenPush.init({
appKey: 'your-app-key', // Required
autoPrompt: false, // Show permission prompt automatically
promptDelay: 3000, // Delay in ms before auto-prompt (if autoPrompt: true)
serviceWorkerUrl: '/openpush-sw.js', // Custom Service Worker path
softPrompt: { // Optional: show a custom UI before the browser dialog
title: 'Get notified',
body: 'Stay up to date with the latest news.',
acceptText: 'Yes, notify me',
denyText: 'No thanks',
icon: '/icon.png', // Optional icon URL
},
topics: ['news', 'deals'], // Auto-subscribe to these topics on registration
onSubscribe: function(subscriberId) {
console.log('Subscribed:', subscriberId);
},
onError: function(err) {
console.error('Push error:', err);
}
});
| Option | Type | Default | Description |
|---|---|---|---|
appKey |
string | — | Required. Your public app key. |
autoPrompt |
boolean | false |
Trigger the permission flow automatically on page load. |
promptDelay |
number | 0 |
Delay in ms before auto-prompt fires (requires autoPrompt: true). |
serviceWorkerUrl |
string | '/openpush-sw.js' |
Path to the Service Worker file. Must be served from your domain root. |
softPrompt |
object | null | null |
Show a custom UI card before the native browser permission dialog. Pass null to skip and go straight to the browser dialog. Fields: title, body, acceptText, denyText, icon. |
topics |
string[] | [] |
Topic slugs to subscribe to automatically on first registration. |
onSubscribe |
function | null |
Called with subscriberId after successful registration. |
onError |
function | null |
Called with an error object if subscription fails. |
SDK Methods
// Manually trigger the permission flow (shows softPrompt if configured)
await OpenPush.prompt();
// Set tags for the current subscriber
await OpenPush.setTags({ plan: 'pro', cohort: 'beta-users' });
// Remove specific tag keys
await OpenPush.removeTags(['cohort', 'source']);
// Set external ID (your own user ID)
await OpenPush.setExternalId('user_12345');
// Subscribe to topics
await OpenPush.subscribeToTopics(['breaking-news', 'deals']);
// Unsubscribe from topics
await OpenPush.unsubscribeFromTopics(['deals']);
// Get current subscribed topics
const topics = await OpenPush.getTopics();
// Check if user is subscribed
const subscribed = OpenPush.isSubscribed(); // synchronous, returns boolean
// Get current subscriber ID
const subscriberId = OpenPush.getSubscriberId(); // synchronous, returns string | null
// Unsubscribe the current user (removes from API + browser)
await OpenPush.unsubscribe();
Flutter SDK — Installation
The openpushapi_flutter package has no mandatory Google/Firebase dependency. iOS uses Apple's native APNs directly. For Android, you provide the FCM token yourself — using whichever push client you prefer.
1. Download and add the SDK
Download the Flutter SDK package from the console and place it in your project:
# pubspec.yaml — add as a local path dependency
dependencies:
openpushapi_flutter:
path: ./packages/openpushapi_flutter
2. iOS — enable Push Notifications capability
In Xcode: open Runner → Signing & Capabilities → click + Capability → add Push Notifications. No other iOS setup is needed — the SDK handles APNs registration automatically.
3. Android — add your push provider (optional)
Android push requires FCM (Google). Add it only if you need Android support:
dependencies:
firebase_core: ^3.0.0
firebase_messaging: ^15.0.0
Follow the FlutterFire setup guide to add google-services.json to your Android project. This dependency lives in your app, not in the SDK.
Flutter SDK — Initialization
iOS: one line — the SDK requests APNs permission and registers the device token automatically.
Android: call registerToken() after obtaining the FCM token yourself.
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:openpushapi_flutter/openpushapi_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// iOS: APNs handled automatically — nothing extra needed
await OpenPushAPI.init(
config: const OpenPushConfig(
appKey: 'YOUR_APP_KEY',
debug: true, // set false in production
),
);
// Android: provide FCM token yourself
if (!kIsWeb && Platform.isAndroid) {
// Uses firebase_messaging — add it to YOUR pubspec, not required by the SDK
// final token = await FirebaseMessaging.instance.getToken();
// if (token != null) {
// await OpenPushAPI.registerToken(token, platform: 'android');
// }
// FirebaseMessaging.instance.onTokenRefresh.listen((t) {
// OpenPushAPI.registerToken(t, platform: 'android');
// });
}
runApp(const MyApp());
}
OpenPushConfig options
| Option | Type | Default | Description |
|---|---|---|---|
appKey | String | — | Required. Your public app key. |
apiUrl | String | https://api.openpushapi.com/v1 | API base URL. Override for testing. |
autoRequestPermission | bool | true | Request notification permission on init (iOS). |
vapidKey | String? | null | VAPID public key — required for Flutter Web push. |
debug | bool | false | Print SDK logs to console. |
Flutter SDK — API Reference
| Method | Description |
|---|---|
OpenPushAPI.init(config:) | Initialize the SDK. On iOS, automatically registers APNs token. |
OpenPushAPI.registerToken(token, platform:) | Register a push token. Use for Android ('android') and Web ('web'). |
OpenPushAPI.setExternalId(id) | Link subscriber to your own user ID (call after login). |
OpenPushAPI.setTags(map) | Set or update segmentation tags. Merged server-side. |
OpenPushAPI.unsubscribe() | Delete subscriber from OpenPushAPI and clear local state. |
OpenPushAPI.trackClick(notifId) | Track a notification click event. |
OpenPushAPI.subscriberId | Current subscriber ID, or null if not yet registered. |
OpenPushAPI.isInitialized | Whether SDK has been successfully initialized. |
// Link to your user after login
await OpenPushAPI.setExternalId(currentUser.id);
// Segment by plan and country
await OpenPushAPI.setTags({'plan': 'pro', 'country': 'US'});
// Unsubscribe on logout
await OpenPushAPI.unsubscribe();
// Read the subscriber ID
print(OpenPushAPI.subscriberId);
Flutter Web — Web Push
Web Push uses the standard browser Push API with VAPID — no Firebase required. Get your VAPID public key from the OpenPushAPI console (App → Platform Settings → Web Push), obtain a push subscription in your service worker, and pass the endpoint to registerToken.
// Web — register a VAPID push endpoint
if (kIsWeb) {
// Obtain the push subscription endpoint from the browser Push API
// using your service worker, then:
await OpenPushAPI.registerToken(pushEndpointUrl, platform: 'web');
}
Service worker: place openpush-sw.js at the root of your domain. It handles background message display without any third-party push SDK.
REST API — Authentication
The base URL for all API requests is https://api.openpushapi.com/v1.
Include both headers in every server-side request:
curl https://api.openpushapi.com/v1/subscribers \
-H "X-App-Key: ak_live_xxxxxxxxxxxxx" \
-H "X-App-Secret: as_live_xxxxxxxxxxxxx"
Apps API
Manage your applications programmatically.
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/apps | Create a new app |
GET | /v1/apps | List all apps |
GET | /v1/apps/{id} | Get app details |
PUT | /v1/apps/{id} | Update app settings |
DELETE | /v1/apps/{id} | Delete an app |
Subscribers API
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/subscribers | Register a subscriber (from SDK) |
GET | /v1/subscribers | List subscribers (filterable) |
GET | /v1/subscribers/{id} | Get subscriber details |
PUT | /v1/subscribers/{id} | Update subscriber (tags, external_id) |
DELETE | /v1/subscribers/{id} | Unsubscribe |
POST | /v1/subscribers/{id}/tags | Add tags |
DELETE | /v1/subscribers/{id}/tags | Remove tags |
Filter subscribers
# Filter by platform
GET /v1/subscribers?platform=web
# Filter by active status
GET /v1/subscribers?is_active=1
# Filter by tag
GET /v1/subscribers?tags[plan]=pro
# Pagination
GET /v1/subscribers?limit=50&offset=100
Notifications API
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/notifications | Send a notification |
GET | /v1/notifications | List notifications |
GET | /v1/notifications/{id} | Get notification + stats |
POST | /v1/notifications/{id}/cancel | Cancel a scheduled notification |
Full notification payload
{
"title": "Hi {{first_name}}, Flash Sale — 24 hours only!",
"body": "50% off all Pro plans. Offer ends tonight.",
"url": "https://yourapp.com/sale",
"icon_url": "https://yourapp.com/icons/sale.png",
"image_url": "https://yourapp.com/images/sale-banner.jpg",
"badge_url": "https://yourapp.com/icons/badge.png",
"target_type": "segment",
"target_segment_id": 42,
"target_platforms": ["web", "android", "ios"],
"target_countries": ["US", "GB", "DE"],
"channels": "push",
"actions": [
{ "title": "Claim Offer", "url": "https://yourapp.com/sale" },
{ "title": "Remind Me Later" }
],
"ttl_seconds": 43200,
"send_at": "2026-04-24T09:00:00Z",
"timezone_delivery": true,
"send_time_optimized": false,
"is_ab_test": false
}
Delivery channels
channels decides where a campaign goes: push (default), push_inapp (push and a copy in the subscriber's in-app inbox) or inapp (inbox only — no push is sent). The inbox is what the notification center widget and GET /v1/inapp show. Any other value returns 422.
Segments API
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/segments | Create a segment |
GET | /v1/segments | List segments |
PUT | /v1/segments/{id} | Update segment rules |
DELETE | /v1/segments/{id} | Delete segment |
GET | /v1/segments/{id}/count | Count matching subscribers |
// Create a segment: Pro users in the US on web
{
"name": "Pro Web US",
"rules": {
"platform": "web",
"country_code": "US",
"tags.plan": "pro"
}
}
Stats API
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/stats/overview | Overall subscriber & delivery stats |
GET | /v1/stats/notifications/{id} | Per-notification stats |
How metrics are defined
These definitions are the contract behind every number in the console and the Stats API (metrics contract v1, 2026-09).
| Metric | Definition |
|---|---|
total_recipients | Devices targeted by the campaign after segment, tag, topic, platform, quiet-hours and frequency-cap filters. For A/B tests: the test sample only; the holdout is counted on the winner ("W") notification. |
delivered | The push provider (browser push service, FCM, APNs) accepted the message. This does not prove it was displayed on the device. |
failed | The provider rejected the message permanently (invalid token, missing credentials, 4xx other than 429). Transient errors (429, 5xx, network) are retried and are not counted as failed until retries are exhausted. |
expired | Subscription no longer exists (410/404) — the subscriber is unsubscribed automatically. |
clicked | Unique clicks: at most one per subscriber per notification. Clicks that cannot be tied to a subscriber are counted at most once per notification per client address per 24 h. |
| CTR | clicked / delivered (provider-accepted deliveries), not clicked / total_recipients. |
| Conversions | Events posted to /v1/conversions. Revenue is only accepted from your server (app secret / AI token) and is reported per currency — amounts in different currencies are never summed. |
| A/B winner | Higher CTR after a 4 h window with at least 100 provider-accepted deliveries per variant. A two-proportion z-score is recorded (ab_winner_z; |z| ≥ 1.96 ≈ 95 % confidence). The winner is then sent to the holdout. |
Tracking API
Use the tracking endpoint to record notification click events from mobile apps and custom integrations. The JavaScript SDK and Flutter SDK call this automatically.
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/track/click | Record a notification click |
curl -X POST https://api.openpushapi.com/v1/track/click \
-H "Content-Type: application/json" \
-H "X-App-Key: your-app-key" \
-d '{
"notification_id": "12345",
"subscriber_id": "98765"
}'
The click is deduplicated per subscriber — calling this endpoint multiple times for the same subscriber and notification only increments the counter once.
Click tracking in mobile apps
// Flutter — track click when user taps notification
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final notifId = message.data['opn_notification_id'];
if (notifId != null) OpenPushAPI.trackClick(notifId);
});
Webhooks
Webhooks allow OpenPushAPI to push delivery events to your server in real time. You can use them to sync delivery stats, trigger workflows, or log events.
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/webhooks | Register a webhook endpoint |
GET | /v1/webhooks | List registered webhooks |
PUT | /v1/webhooks/{id} | Update a webhook |
DELETE | /v1/webhooks/{id} | Delete a webhook |
Registering a webhook
curl -X POST https://api.openpushapi.com/v1/webhooks \
-H "Content-Type: application/json" \
-H "X-App-Key: your-app-key" \
-H "X-App-Secret: your-app-secret" \
-d '{
"url": "https://yourserver.com/hooks/push",
"events": ["delivered", "clicked", "failed"],
"is_active": true
}'
Available events
| Event | Triggered when |
|---|---|
delivered | A push notification is successfully delivered to a device |
clicked | A subscriber clicks/taps the notification |
failed | Delivery fails (invalid token, device unreachable, etc.) |
Webhook payload
{
"event": "clicked",
"notification_id": 12345,
"subscriber_id": 98765,
"platform": "android",
"timestamp": "2026-04-06T10:23:11Z"
}
Webhook requests are signed with an X-OpenPush-Signature header (HMAC-SHA256 of the raw body using your webhook secret). Always verify this signature before processing the payload.
Error Handling
All error responses follow a consistent JSON format:
{
"error": "Subscriber not found"
}
HTTP status codes
| Code | Meaning |
|---|---|
200 | OK — request succeeded |
201 | Created — resource created successfully |
204 | No Content — deleted successfully |
400 | Bad Request — missing or invalid parameters |
401 | Unauthorized — missing or invalid X-App-Key / X-App-Secret |
403 | Forbidden — action not allowed (e.g. plan limit reached) |
404 | Not Found — resource does not exist |
422 | Unprocessable Entity — validation failed |
429 | Too Many Requests — rate limit exceeded |
500 | Internal Server Error — contact support if this persists |
Rate limits
API rate limits are returned in response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Quotas & test apps
Server-side calls (X-App-Secret or an AI token) count towards your plan's monthly API call quota, shared by all apps you own. SDK calls from browsers and devices (public app key only) do not count. Every counted response carries:
| Header | Meaning |
|---|---|
X-Quota-Limit | Calls allowed this month (unlimited on Enterprise, test-app for test apps) |
X-Quota-Used | Calls used this month, including this one |
X-Quota-Reset | Unix time when the quota resets (1st day of next month, UTC) |
Over the limit the API answers 429 with "error": "monthly_api_quota_exceeded", the limit, used, reset and an upgrade_url. The per-second rate limit is separate and unchanged.
Test apps
Mark an app as a test app in App settings → General. Test apps are meant for development and staging: their API calls, subscribers and notifications are not counted towards your plan limits or usage, and they carry a Test badge in the console. Don't use a test app for production traffic.
Device token (SDK)
Since Web SDK 1.2.0, registration returns a subscriber_token that proves the browser owns its subscriber record. Every later SDK call about that subscriber (update, tags, topics, in-app inbox, conversions) must send it as X-Subscriber-Token; without it the API returns 403 subscriber_token_required and the SDK registers again automatically. Server-side calls with your app secret don't need it.
Personalization
Use {{variable}} placeholders in the title, body, or url of any notification. Each subscriber receives their own version with the placeholders replaced.
Supported variables
| Variable | Source |
|---|---|
{{first_name}} | Subscriber's first_name field |
{{last_name}} | Subscriber's last_name field |
{{external_id}} | Your own user ID linked to this subscriber |
{{platform}} | web, android, or ios |
{{country}} | 2-letter country code (US, DE, …) |
{{city}} | Subscriber's city |
{{language}} | Subscriber's browser/device language |
{{tags.KEY}} | Any tag value — e.g. {{tags.plan}} |
Missing or unknown variables are silently replaced with an empty string.
{
"title": "Hey {{first_name}}, your {{tags.plan}} trial ends soon!",
"body": "Upgrade today and keep all your {{tags.plan}} features.",
"url": "https://yourapp.com/upgrade?ref={{external_id}}",
"target_type": "all"
}
Personalization works on all platforms — Web Push, Android (FCM), and iOS (APNs). The substitution happens in the delivery engine, so your original notification record stores the template, not the resolved text.
A/B Testing
Test two notification variants against each other. Subscribers are randomly split according to the ab_split_percent you specify. The winner (highest CTR) is automatically selected after both variants have been delivered for at least 4 hours and each has reached 100+ deliveries.
Sending an A/B test
Set is_ab_test: true and include the variant B fields in the payload:
{
"title": "New Feature Available",
"body": "We just launched something you'll love.",
"url": "https://yourapp.com/new",
"target_type": "all",
"is_ab_test": true,
"ab_split_percent": 50,
"title_b": "You're going to love this",
"body_b": "Check out the latest update — it's live now."
}
A/B test fields
| Field | Type | Description |
|---|---|---|
is_ab_test | boolean | Enable A/B split for this notification |
ab_split_percent | integer | % of the test sample that receives variant A (10–90, default 50) |
ab_test_pct | integer | % of the audience used as the test sample (5–100, default 20). The remaining subscribers are the holdout and receive the winning variant automatically after 4 hours. 100 = plain split test without a winner send. |
title_b | string | Title for variant B |
body_b | string | Body text for variant B |
Variants A and B share the same targeting, URL, icon, schedule, and TTL. Stats for both variants appear side by side in the console notification detail page.
A/B testing requires the Pro plan or higher.
Geo-targeting
Restrict delivery to subscribers in specific countries using the target_countries field. Pass an array of ISO 3166-1 alpha-2 country codes.
{
"title": "Black Friday — US & Canada only",
"body": "Exclusive deal available in your region.",
"url": "https://yourapp.com/black-friday",
"target_type": "all",
"target_countries": ["US", "CA"]
}
Geo-targeting can be combined with any target_type — it acts as an additional filter on top of segment, tag, or ID targeting. If target_countries is omitted or empty, the notification is delivered to all countries.
Country codes are matched against the country_code field on the subscriber, which is automatically detected from the subscriber's IP address at registration time.
Smart Send Time
When send_time_optimized: true, the delivery engine sends the notification to each subscriber at their personal optimal hour — the time of day when that subscriber has historically clicked push notifications most often.
{
"title": "Weekly digest is ready",
"body": "Here's what happened this week.",
"target_type": "all",
"send_time_optimized": true
}
The preferred hour per subscriber is computed nightly from their delivery and click history. Subscribers without enough history receive the notification immediately.
How it works: The engine delivers only to subscribers whose preferred_hour matches the current local hour (±1). Subscribers not yet in their window are skipped in the current run and picked up in subsequent runs. Subscribers without a recorded preferred hour receive the notification immediately.
In-App Notification Bell
The notification center widget adds a bell icon to your web app that shows a dropdown inbox of recent push notifications. It does not require browser permission — it fetches from our API and is ideal for logged-in app experiences.
Installation
<!-- Add a target element where the bell should appear -->
<div id="openpush-center"></div>
<!-- Load the widget -->
<script src="https://sdk.openpushapi.com/v1/notification-center.js"></script>
<script>
OpenPushCenter.init({
appKey: 'your-app-key',
subscriberId: currentUser.opnSubscriberId, // your subscriber's ID
targetSelector: '#openpush-center', // where to mount the bell
maxItems: 20, // max notifications to show
});
</script>
In-App API endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/inapp?subscriber_id={id} | List unread in-app notifications |
PUT | /v1/inapp/{id}/read | Mark single notification as read |
POST | /v1/inapp/read-all | Mark all as read for a subscriber |
# Fetch unread in-app notifications
curl "https://api.openpushapi.com/v1/inapp?subscriber_id=98765&limit=20" \
-H "X-App-Key: your-app-key"
# Mark all as read
curl -X POST https://api.openpushapi.com/v1/inapp/read-all \
-H "Content-Type: application/json" \
-H "X-App-Key: your-app-key" \
-d '{"subscriber_id": 98765}'
The widget polls for new notifications every 60 seconds automatically. The unread badge count updates in real time when new items arrive.
Automation (Journeys)
Journeys let you build multi-step automated notification sequences triggered by subscriber events. For example: send a welcome push immediately after signup, wait 3 days, then send a follow-up only if the subscriber hasn't clicked anything.
Journey triggers
| Trigger | When it fires |
|---|---|
subscriber_created | A new subscriber registers |
tag_added | A specific tag is added to a subscriber |
segment_enter | A subscriber enters a segment |
event | A custom event is tracked via the API |
manual | Manually enrolled via the console or API |
Step types
| Type | Description |
|---|---|
send_push | Send a push notification |
wait | Pause for N minutes / hours / days |
condition | Branch based on subscriber field value |
ab_split | Randomly split enrollment into A/B paths |
Journeys are configured and managed from the console at Automation → Journeys. The engine processes active journey enrollments every minute.
Console only: Journeys are currently managed exclusively from the console builder at Automation → Journeys. REST API support for journeys is coming soon.
Unsubscribe Reason Tracking
When a subscriber opts out, you can capture why they unsubscribed. This data appears in the Analytics section of the console and helps you improve retention.
Passing a reason
Include a reason field when calling the unsubscribe endpoint:
curl -X DELETE https://api.openpushapi.com/v1/subscribers/98765 \
-H "Content-Type: application/json" \
-H "X-App-Key: your-app-key" \
-d '{"reason": "too_many"}'
Valid reason values
| Value | Meaning |
|---|---|
too_many | Receiving too many notifications |
not_relevant | Notifications are not relevant |
never_signed_up | Subscriber didn't intentionally opt in |
other | Other / not specified |
The Web SDK can also show a built-in reason dialog before unsubscribing:
// Show reason dialog, then unsubscribe
await OpenPush.unsubscribe({ showReasonDialog: true });
// Or unsubscribe with a known reason directly
await OpenPush.unsubscribe({ reason: 'too_many' });
Web Push (VAPID)
Web Push uses the VAPID (Voluntary Application Server Identification) protocol. OpenPushAPI automatically generates VAPID keys for each app — you don't need to manage them yourself.
Browser support: Chrome 50+, Firefox 44+, Edge 17+, Safari 16+ (macOS Ventura), Opera 42+.
The SDK registers a Service Worker in the browser, which handles incoming push messages even when the page is closed. The Service Worker is automatically generated and versioned per your app.
Custom Service Worker integration
If you already have a Service Worker, you can import ours from within it:
// your-service-worker.js
importScripts('https://sdk.openpushapi.com/v1/openpush-sw.js');
// Your existing SW code below...
self.addEventListener('fetch', (event) => {
// ...
});
Android — Firebase Cloud Messaging (FCM)
To send push notifications to Android apps, you need to configure FCM credentials in your OpenPushAPI app settings.
- Create a Firebase project at console.firebase.google.com
- Go to Project Settings → Cloud Messaging and copy your Server Key and Project ID
- In OpenPushAPI console, navigate to your App → Platform Settings → Android and enter the credentials
- In your Android app, use the Firebase SDK to get the FCM registration token and register it via our API
// Android — Register FCM token with OpenPushAPI
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
val token = task.result
// Register with OpenPushAPI
val client = OkHttpClient()
val body = JSONObject().apply {
put("platform", "android")
put("subscription_token", token)
}.toString()
val request = Request.Builder()
.url("https://api.openpushapi.com/v1/subscribers")
.addHeader("X-App-Key", "your-app-key")
.post(body.toRequestBody("application/json".toMediaType()))
.build()
client.newCall(request).enqueue(...)
}
iOS — Apple Push Notification Service (APNs)
To send push notifications to iOS apps, you need an APNs .p8 key from your Apple Developer account. The key file is uploaded directly in the console — no server-side configuration needed.
Step 1 — Get your APNs key from Apple
- Log in to developer.apple.com
- Navigate to Certificates, Identifiers & Profiles → Keys
- Click + to create a new key, enable Apple Push Notifications service (APNs)
- Download the
.p8file — you can only download it once - Note your Key ID (10 characters) and Team ID (found under Membership)
Step 2 — Upload in OpenPushAPI Console
- Go to Console → Apps → select your app → Platform Settings → iOS (APNs)
- Fill in Key ID, Team ID, and Bundle ID (e.g.
com.yourcompany.yourapp) - Click Choose file, select your
.p8file, and click Save APNs Settings - A green checkmark confirms the key is stored. You can replace it at any time by uploading a new file, or remove it with the Remove checkbox.
.p8 key file is stored securely on the server and is never exposed via the API. It cannot be set through the REST API — only through the Console UI.
// iOS — Register APNs token with OpenPushAPI
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
// Register with OpenPushAPI
var request = URLRequest(url: URL(string: "https://api.openpushapi.com/v1/subscribers")!)
request.httpMethod = "POST"
request.setValue("your-app-key", forHTTPHeaderField: "X-App-Key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: [
"platform": "ios",
"subscription_token": token
])
URLSession.shared.dataTask(with: request).resume()
}
Migrate from OneSignal
Browsers grant notification permission to your site, not to a push provider. Visitors who allowed notifications for OneSignal have already allowed them for you — after the switch the SDK re-subscribes them without showing a new prompt the next time they visit. You can also bring your existing subscriber list along so nobody is lost in between.
1. Create the app and choose your keys
- Create an app in the console and add your site's origin to Allowed origins.
- If you have your own VAPID key pair (you generated it yourself or your provider let you export it), open App settings → Web Push and paste the private key. The public key is derived and checked automatically. Existing subscriptions made with that key keep working — import them in step 3 and you can send to them straight away.
- If you don't have the private key (the usual case with OneSignal-managed keys), keep the keys OpenPushAPI generated. Old subscriptions can't be reused with a new key, but the SDK replaces them automatically on each visitor's next visit (step 2).
2. Swap the SDK and the service worker
Remove the OneSignal script and init code, delete OneSignalSDKWorker.js (or make it an empty file for a while so browsers that still request it get a valid response), and add OpenPushAPI:
<script src="https://sdk.openpushapi.com/v1/sdk.js"></script>
<script>
OpenPush.init({ appKey: 'YOUR_APP_KEY' });
// Visitors who already allowed notifications (e.g. via OneSignal) are
// re-subscribed silently — no permission prompt is shown to them.
if ('Notification' in window && Notification.permission === 'granted') {
OpenPush.prompt();
}
</script>
Serve /openpush-sw.js from your site root (see Installation). Registering it at the same scope replaces the OneSignal worker. Web SDK 1.2.1+ removes a leftover subscription that was made with a different key before subscribing again.
3. Import your subscribers (optional)
In OneSignal, export your subscribers as CSV and upload the file unchanged under Import / Export. The importer recognises OneSignal's column names:
| OneSignal column | OpenPushAPI field |
|---|---|
identifier | push endpoint |
web_p256, web_auth | subscription keys |
external_user_id | external_id |
language, tags | same |
invalid_identifier = true | row is skipped |
Imported subscribers keep their external_id and tags, so your segments and server-side targeting by user ID keep working. Duplicates (same endpoint) are skipped and your plan's subscriber limit applies.
Import only if you imported the key pair in step 1. A subscription only works with the key it was created with; without OneSignal's private key the imported web subscribers would just fail on every send. In that case skip the import — each returning visitor is re-subscribed by the SDK (step 2), and your site sets their external ID again when they sign in (OpenPush.setExternalId(userId)).
4. Update your server code
| OneSignal | OpenPushAPI |
|---|---|
POST /notifications with include_aliases.external_id | POST /v1/notifications with "target_type": "external_ids", "target_ids": [...] |
included_segments: ["Subscribed Users"] | "target_type": "all" |
filters on tags | "target_type": "tags", "target_tags": {...} or a segment |
headings / contents / url | title / body / url |
| REST API key | X-App-Key + X-App-Secret headers (server-side only) |
5. Check it
Use Test in your browser to confirm the keys and delivery work, then watch the subscriber count grow as visitors return. Keep OneSignal running in parallel until you have switched every place that sends.
Test in your browser
App settings → Web Push → Test in this browser subscribes the browser you are using as a test subscriber of the app and sends it one real push through the delivery engine. It shows each step — permission, subscription, queued, accepted by the push service, shown, clicked — and the push service's error message if something fails (for example a mismatched VAPID key). It needs the send permission on the app and is limited to 20 tests per hour.