Docs Getting Started

Quick Start Guide

Get up and running with OpenPushAPI in under 5 minutes.

Last updated: April 2026 10 min read

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.

SDK versioning: All SDK files are served under a version path (/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 RunnerSigning & 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

OptionTypeDefaultDescription
appKeyStringRequired. Your public app key.
apiUrlStringhttps://api.openpushapi.com/v1API base URL. Override for testing.
autoRequestPermissionbooltrueRequest notification permission on init (iOS).
vapidKeyString?nullVAPID public key — required for Flutter Web push.
debugboolfalsePrint SDK logs to console.

Flutter SDK — API Reference

MethodDescription
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.subscriberIdCurrent subscriber ID, or null if not yet registered.
OpenPushAPI.isInitializedWhether 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.

MethodEndpointDescription
POST/v1/appsCreate a new app
GET/v1/appsList 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

MethodEndpointDescription
POST/v1/subscribersRegister a subscriber (from SDK)
GET/v1/subscribersList 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}/tagsAdd tags
DELETE/v1/subscribers/{id}/tagsRemove 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

MethodEndpointDescription
POST/v1/notificationsSend a notification
GET/v1/notificationsList notifications
GET/v1/notifications/{id}Get notification + stats
POST/v1/notifications/{id}/cancelCancel 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"],
  "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
}

Segments API

MethodEndpointDescription
POST/v1/segmentsCreate a segment
GET/v1/segmentsList segments
PUT/v1/segments/{id}Update segment rules
DELETE/v1/segments/{id}Delete segment
GET/v1/segments/{id}/countCount 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

MethodEndpointDescription
GET/v1/stats/overviewOverall subscriber & delivery stats
GET/v1/stats/notifications/{id}Per-notification stats

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.

MethodEndpointDescription
POST/v1/track/clickRecord 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.

MethodEndpointDescription
POST/v1/webhooksRegister a webhook endpoint
GET/v1/webhooksList 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

EventTriggered when
deliveredA push notification is successfully delivered to a device
clickedA subscriber clicks/taps the notification
failedDelivery 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

CodeMeaning
200OK — request succeeded
201Created — resource created successfully
204No Content — deleted successfully
400Bad Request — missing or invalid parameters
401Unauthorized — missing or invalid X-App-Key / X-App-Secret
403Forbidden — action not allowed (e.g. plan limit reached)
404Not Found — resource does not exist
422Unprocessable Entity — validation failed
429Too Many Requests — rate limit exceeded
500Internal Server Error — contact support if this persists

Rate limits

API rate limits are returned in response headers:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets

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

VariableSource
{{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

FieldTypeDescription
is_ab_testbooleanEnable A/B split for this notification
ab_split_percentinteger% of subscribers who receive variant A (10–90, default 50)
title_bstringTitle for variant B
body_bstringBody 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

MethodEndpointDescription
GET/v1/inapp?subscriber_id={id}List unread in-app notifications
PUT/v1/inapp/{id}/readMark single notification as read
POST/v1/inapp/read-allMark 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

TriggerWhen it fires
subscriber_createdA new subscriber registers
tag_addedA specific tag is added to a subscriber
segment_enterA subscriber enters a segment
eventA custom event is tracked via the API
manualManually enrolled via the console or API

Step types

TypeDescription
send_pushSend a push notification
waitPause for N minutes / hours / days
conditionBranch based on subscriber field value
ab_splitRandomly 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

ValueMeaning
too_manyReceiving too many notifications
not_relevantNotifications are not relevant
never_signed_upSubscriber didn't intentionally opt in
otherOther / 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.

  1. Create a Firebase project at console.firebase.google.com
  2. Go to Project Settings → Cloud Messaging and copy your Server Key and Project ID
  3. In OpenPushAPI console, navigate to your App → Platform Settings → Android and enter the credentials
  4. 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

  1. Log in to developer.apple.com
  2. Navigate to Certificates, Identifiers & Profiles → Keys
  3. Click + to create a new key, enable Apple Push Notifications service (APNs)
  4. Download the .p8 file — you can only download it once
  5. Note your Key ID (10 characters) and Team ID (found under Membership)

Step 2 — Upload in OpenPushAPI Console

  1. Go to Console → Apps → select your app → Platform Settings → iOS (APNs)
  2. Fill in Key ID, Team ID, and Bundle ID (e.g. com.yourcompany.yourapp)
  3. Click Choose file, select your .p8 file, and click Save APNs Settings
  4. 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.
Note: The .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()
}