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

  1. Create an account at app.pushproof.dev (email code login).
  2. Add an application. Each app has its own keys and separate stats.
  3. Activate your subscription — without it, ingestion and the dashboard won't work.
  4. 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.
If someone gets your ingest_key, they can only send fake receipts for that app. The read_key exposes all your stats — keep it on the server.

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:

  1. Xcode → File → New → Target → Notification Service Extension, named PushproofNSE.
  2. Replace the generated NotificationService.swift with the one shipped with the plugin.
  3. Enable App Groups on the app and on the NSE, with the same identifier (e.g. group.com.your.app).
  4. 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/sent so 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/sent from 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

The iOS rate shown is a lower bound. iOS may suspend or kill the NSE (memory, battery) before the network call finishes. The real rate is at least what the dashboard shows.

API reference

Endpoints, payloads and curl examples are documented in the dashboard, right next to your keys:

Open the documentation →