Documentation
Integrate Pushproof
Pushproof measures how many push notifications actually reach the device. FCM and APNs confirm acceptance, not delivery. This guide covers SDK setup, send configuration, and reading your stats in the dashboard.
Overview
When you send a push, FCM or APNs tells you it was accepted for delivery — not that it reached the phone. Pushproof fills that gap: the on-device SDK detects reception and reports a receipt. The dashboard then computes the delivered / sent rate.
In practice, two parts:
- The SDK (free, open-source) — runs on the device. You keep FCM/APNs for sending.
- The managed service (subscription) — ingests receipts, computes stats, shows the dashboard. Billed by receipt volume, not by user count.
Getting started
- Create an account at app.pushproof.dev (email code login).
- Add an application. Each app has its own keys and separate stats.
- Activate your subscription — without it, ingestion and the dashboard won't work.
- Install the SDK in your app (see below), then send a test push.
Your API keys
Each application has two keys, visible in the console:
- ingest_key (
pk_ingest_…) — goes in the mobile app. Used only to send delivery receipts. - read_key (
sk_read_…) — for reading stats from your backend or this dashboard. Never put it in the app.
Installation
The Capacitor plugin wraps the native iOS and Android SDK:
npm install @pushproof/capacitor@1.0.0
npx cap sync
React Native and Flutter are coming next. The core is the same native SDK.
Configuration
Call configure() at app startup:
import { Pushproof } from '@pushproof/capacitor';
await Pushproof.configure({
ingestUrl: 'https://api.pushproof.dev/v1/receipts',
ingestKey: 'pk_ingest_…',
appGroup: 'group.com.example.app' // iOS: shared app ↔ NSE
});
Pushproof does not handle push token registration. Keep using
@capacitor/push-notifications, Firebase, or your existing native code.
Pushproof only reports receipts once a notification arrives.
iOS extension (NSE)
On iOS, delivery confirmation goes through a Notification Service Extension — a separate process the system wakes when a push arrives, before display. Every app must create its own Xcode target:
- Xcode → File → New → Target → Notification Service Extension, named
PushproofNSE. - Replace the generated
NotificationService.swiftwith the one shipped with the plugin. - Enable App Groups on the app and on the NSE, with the same identifier (e.g.
group.com.your.app). - Make sure your sends include
mutable-content: 1— otherwise the NSE never wakes.
On Android, no extension needed: the SDK intercepts reception in a
FirebaseMessagingService. Prefer data-only messages so the
service runs even in the background.
Sending side
For Pushproof to link "sent" and "delivered", your sending server must:
- generate a
notif_id(UUID) per notification and inject it into the payload; - iOS: send a real notification (title + body) with
"mutable-content": 1— a silent push does not wake the NSE; - Android: prefer a data-only message;
- declare sends via
POST /v1/sentso the dashboard can compute a rate; - (iOS foreground) call
recordDelivery()when the notification arrives while the app is open.
// FCM payload example
{
"message": {
"token": "<device_token>",
"apns": { "payload": { "aps": { "mutable-content": 1 } } },
"data": {
"notif_id": "8f14e45f-ceea-467d-9a3b-2c1d4f5e6a7b",
"title": "…", "body": "…"
}
}
}
The notif_id is the join key between "sent" (known to your backend) and
"delivered" (reported by the device). The SDK never generates it — it reads it from the
received payload.
Reading the dashboard
Once pushes go out and devices report receipts, you see:
- Pushes sent — declared via
POST /v1/sentfrom your backend. - Receipts received — confirmations reported by devices.
- Delivery rate — receipts ÷ sent. On iOS, treat it as a lower bound.
Pro plan
The Pro plan adds per-user tracking. Inject an opaque user_id in the payload,
then check in the dashboard whether a user received a given notification. The identifier is
hashed on ingestion, never stored in clear.
iOS limits
API reference
Endpoints, payloads and curl examples are documented in the dashboard,
right next to your keys: