web-push icon indicating copy to clipboard operation
web-push copied to clipboard

No error and no delivery when sending web push notifications to Apple (iOS) endpoints

Open zloishavrin opened this issue 8 months ago • 3 comments

Hi, I'm using the web-push library in a NestJS backend to send push notifications. It works fine with Google endpoints (e.g., FCM), but notifications are not delivered to Apple (iOS) endpoints, and no error is thrown.

Code:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { Push } from 'src/db/push.schema';
import * as webpush from "web-push";

@Injectable()
export class PushService {

  private vapidKeys = {
    publicKey: process.env.PUBLIC_VAPID_KEY,
    privateKey: process.env.PRIVATE_VAPID_KEY
  }

  constructor(
    @InjectModel(Push.name) private PushModel: Model<Push>,
  ) {
    webpush.setVapidDetails(
      'mailto: <[email protected]>',
      this.vapidKeys.publicKey,
      this.vapidKeys.privateKey
    )
  }

  async saveSubscription(
    userId: string,
    endpoint: string,
    auth: string,
    p256dh: string
  ): Promise<Push> {
    const newPush = await this.PushModel.create({
      endpoint: endpoint,
      keys: { auth, p256dh },
      user: new Types.ObjectId(userId)
    });

    webpush.sendNotification(
      { endpoint, keys: { auth, p256dh } },
      JSON.stringify({ notification: { title: 'Hello', body: 'It works!' } })
    );

    return newPush;
  }

  async sendNotificationToUser(
    userId: Types.ObjectId,
    title: string,
    message: string
  ) {
    const subscriptions = await this.PushModel.find({ user: userId });
    if(!subscriptions) return;

    for(const subscription of subscriptions) {
      try {
        const pushSub = {
          endpoint: subscription.endpoint,
          keys: {
            auth: subscription.keys.auth,
            p256dh: subscription.keys.p256dh
          }
        }

        console.log("endpoint: ", subscription.endpoint);

        const response = await webpush.sendNotification(pushSub, JSON.stringify({
          notification: {
            title: 'Hello',
            body: 'It works!',
          }
        }));

        console.log("response: ", response);
        console.log("exit");
      }
      catch(error) {
        console.error(error);
      }
    }
  }

}

Logs:

When I send a push to a Google endpoint, everything works and I get a 201 response:

endpoint: https://fcm.googleapis.com/fcm/send/...
response: {
  statusCode: 201,
  body: '',
  headers: {
    ...
  }
}
exit

But when I send a push to an Apple endpoint:

endpoint: https://web.push.apple.com/...

The request silently succeeds (no error thrown)

There’s no response body or indication of delivery

The notification never appears on the device

Expected Behavior

An error or at least some feedback if the notification fails to deliver, or correct delivery to iOS Safari.

Notes

Subscriptions are generated via the Web Push API in Safari on iOS

Using correct VAPID keys

Tested both development and production keys and endpoints

No firewall or network issues observed

Is Apple Safari web push supported by the web-push Node.js library? If yes, what’s the recommended way to troubleshoot when pushes are silently dropped?

Thanks!

zloishavrin avatar May 22 '25 09:05 zloishavrin

Update

This kind of CLI works:

web-push send-notification \
  --endpoint='https://web.push.apple.com/endpoint' \
  --key='key' \
  --auth='key'\
  --vapid-subject='mailto:[email protected]' \
  --vapid-pubkey='pub key'\
  --vapid-pvtkey='priv key' \
  --payload='{"title":"message","body":"message"}'

That said, the code still doesn't work (

UPD: Maybe the problem is that the application is running in Docker.

zloishavrin avatar May 22 '25 12:05 zloishavrin

@zloishavrin I'm facing the same issue. Did you find a solution?

benjosua avatar Jul 13 '25 22:07 benjosua

@benjosua In my case it helped that I put the push notification delivery service into a separate service (initially it was a nestjs module, and the application itself was running in docker). It still doesn't work in Docker.

zloishavrin avatar Jul 14 '25 10:07 zloishavrin