Agent Wranglers

NTF-11Required

Use the current APNs and FCM interfaces

Send to Apple devices through APNs over HTTP/2, and to Android through the FCM HTTP v1 API. Never use the retired legacy FCM API. Prefer token-based APNs auth.

How to do it

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. Apple still accepts certificate-based connections, but each certificate serves one app and carries an expiry date. Apple calls tokens the simpler choice for more than one app. For FCM, get an OAuth 2.0 access token from a service account and POST to /v1/projects/<project>/messages:send. Firebase is moving device registration from registration tokens to Firebase Installation IDs. New apps register with an installation ID and send to the fid field. Both kinds still work. Google shut down the legacy FCM API in 2024. A provider such as Amazon SNS or OneSignal can wrap both. Act on invalid token answers as Delete dead subscriptions and tokens at once says.

Examples

JavaScript example

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 example

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,
    )

How to check

Grep the code for fcm.googleapis.com/fcm/send, and it does not appear. If the code uses an APNs certificate, a monitor alerts 30 days before its notAfter date. A staging send to one iOS and one Android device succeeds.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/notifications/native-push/apns-fcm-current-apis">Agent Wranglers notifications playbook</a>. In code you write, add a comment next to it with the same link.