# Verify Delivery

Prove that universal push works — test-send to one device or token, app health counts, and the credential probe — with the exact response fields to read.

Universal push has no receipts (APNs and FCM answer at send time and that answer is final), so verification is three small endpoints instead of a delivery log.

## 1. Test send — one device or one token

```text
POST https://app.nativenotify.com/api/universal/test-send
```

Target **either** a registered device:

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "title": "Test from the dashboard",
  "message": "If you can read this, APNs/FCM accepted it.",
  "deviceId": "A1B2C3D4-install-key"
}
```

**or** an explicit raw token:

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "title": "Test to one token",
  "message": "Hello from the credentials probe.",
  "tokenType": "apns",
  "token": "9f8a7b6c…9d8e"
}
```

```json
{
  "ok": true,
  "accepted": true,
  "transport": "apns",
  "result": "accepted",
  "reasonCode": null,
  "reasonPlainEnglish": null,
  "testSendId": 8841,
  "target": {
    "mode": "device",
    "deviceId": "A1B2C3D4-install-key",
    "platform": "ios",
    "subscriberId": "user_8241",
    "tokenType": "apns",
    "tokenMasked": "…b0c9d8e",
    "tokenSource": "device"
  },
  "checkedAt": "2026-09-23T09:44:02.000Z"
}
```

What to read:

- `result` — `accepted` or `failed`, straight from the real send pipeline, so it is what production would see.
- `reasonCode` / `reasonPlainEnglish` — when it failed: Apple/Google's answer, translated. A credential-level reason for **every** token means fix the credentials first.
- `tokenMasked` — responses never carry a full token; this is the last six characters only.

## 2. Health — what is in storage right now

```text
GET https://app.nativenotify.com/api/universal/health/:appId/:appToken
```

```json
{
  "ok": true,
  "appId": 123,
  "checkedAt": "2026-09-23T09:45:00.000Z",
  "source": "universal_push_devices/universal_push_tokens",
  "devices": { "ios": 640, "android": 661 },
  "tokens": { "apns": { "live": 640, "dead": 1 }, "fcm": { "live": 660, "dead": 2 } },
  "retired": 0,
  "masking": "counts only — no token material is ever returned"
}
```

Counts are exact and come from the universal service's own storage — use it to confirm that registration actually landed (devices > 0 after installing), and to see dead/struck tokens before a blast.

## 3. Credentials — live probe

```text
POST /api/universal/credentials/validate   (admin-only)
```

Covered in full on [Push Credentials](/docs/universal-push/credentials): Google must mint an OAuth token from the service account, and Apple must answer the invalid probe token with `BadDeviceToken`.

## Reading the failures

| Symptom                                                             | What it usually is                                 | Do this                                                                                                  |
| ------------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `failed` with a credential reason on every token                    | Wrong/expired FCM service account or APNs key      | Re-run the credential validate probe, re-save from the dashboard wizard                                  |
| `failed` with a per-token reason (`BadDeviceToken`, `Unregistered`) | That install is gone (uninstalled / token rotated) | Re-register on next app launch — registration clears the dead flag                                       |
| `skipped` counters in a send                                        | No credentials for that transport on the app       | Save the missing credential set                                                                          |
| `accepted` but the device shows nothing                             | OS-level delivery (focus, DND, permission revoked) | Check notification permission on the device — acceptance is the transport's answer, not the OS's display |

## Related

- [Tokens & Receipts](/docs/tokens-and-receipts) — how strikes and dead tokens work on both paths.
- [Send Notifications](/docs/universal-push/sending) — what a full audience send reports.
