---
title: "Use the current APNs and FCM interfaces · Agent Wranglers"
url: https://agentwranglers.se/playbook/notifications/push-notifications/apns-fcm-current-apis
updated: 2026-10-20
licence: "All rights reserved"
---

# Use the current APNs and FCM interfaces

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Push notifications](https://agentwranglers.se/playbook/notifications/push-notifications)
- Rule: Send to Apple devices through APNs over HTTP/2 with token-based auth, and to Android through the FCM HTTP v1 API. Never use the retired legacy FCM API.
- How: For APNs, sign a JWT with your `.p8` key using ES256. Reuse it for 20 to 60 minutes, because APNs rejects older tokens and answers 429 to tokens changed too often. Share one token across servers. Set `apns-push-type` and `apns-topic` on every request. For FCM, get an OAuth 2.0 access token from a service account and POST to `/v1/projects/<project>/messages:send`. Google shut down the legacy FCM API in 2024. A provider such as Amazon SNS or OneSignal can wrap both.
- Check: Grep the code for `fcm.googleapis.com/fcm/send` and for certificate-based APNs settings. Neither appears. A staging send to one iOS and one Android device succeeds.

Example:

```js
import { SignJWT, importPKCS8 } from 'jose';

const TOKEN_LIFETIME_MS = 30 * 60 * 1000;
let cached = { token: '', madeAt: 0 };

export async function apnsToken() {
  if (Date.now() - cached.madeAt < TOKEN_LIFETIME_MS) return cached.token;
  const key = await importPKCS8(process.env.APNS_KEY_P8, 'ES256');
  const token = await new SignJWT({}).setProtectedHeader({ alg: 'ES256', kid: process.env.APNS_KEY_ID })
    .setIssuer(process.env.APPLE_TEAM_ID).setIssuedAt().sign(key);
  cached = { token, madeAt: Date.now() };
  return token;
}
```

```python
import google.auth.transport.requests
import requests
from google.oauth2 import service_account

SCOPES = ["https://www.googleapis.com/auth/firebase.messaging"]
credentials = service_account.Credentials.from_service_account_file("fcm-service-account.json", scopes=SCOPES)

def send_fcm(token: str, title: str, body: str):
    credentials.refresh(google.auth.transport.requests.Request())
    return requests.post(
        "https://fcm.googleapis.com/v1/projects/example-shop/messages:send",
        headers={"Authorization": f"Bearer {credentials.token}"},
        json={"message": {"token": token, "notification": {"title": title, "body": body}}},
        timeout=10,
    )
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers notifications playbook: https://agentwranglers.se/playbook/notifications/push-notifications/apns-fcm-current-apis`.

- Previous: [Send web push with VAPID and an encrypted payload](https://agentwranglers.se/playbook/notifications/push-notifications/web-push-vapid)
- Next: [Give every push a lifetime and a topic](https://agentwranglers.se/playbook/notifications/push-notifications/push-ttl-and-collapse)
- [Push notifications](https://agentwranglers.se/playbook/notifications/push-notifications)
