Legan Studio
Все статьи
~ 6 мин чтения

Web Push уведомления: как и когда внедрять

Web Push на сайте: VAPID, service worker, разрешения, бэкенд для отправки, сегментация, метрики и когда не стоит этим заниматься вообще.

  • веб
  • разработка
  • интеграции

Web Push — единственный способ дотянуться до пользователя, когда он закрыл вашу вкладку. Не email (который читают раз в день), не SMS (который дорогой) — а нативное системное уведомление прямо в трей или на экран блокировки. Звучит мощно, но 80% сайтов внедряют это так, что это бесит и уводит пользователей навсегда.

Разберём, как сделать Web Push, не превратив сайт в спам-машину.

Что такое Web Push под капотом

Это связка из трёх частей:

  1. Service Worker — фоновый скрипт в браузере, который живёт даже когда сайт закрыт.
  2. Push API — браузерный API, который через свой push-сервер (FCM у Chrome, Mozilla у Firefox, APNs у Safari) принимает сообщения от вашего бэкенда и будит SW.
  3. Notifications API — тот же SW рисует на экране нативное уведомление.

Бэкенд отправляет JSON с подписью VAPID на push-endpoint браузера. Браузер сам доставит до клиента, даже если вкладки нет.

Поддержка в 2026

БраузерДесктопМобильный
Chrome / EdgeдаAndroid — да, iOS Safari — нет
FirefoxдаAndroid — да
Safariда (16.4+)iOS 16.4+ — только если сайт добавлен на главный экран как PWA
Яндекс БраузердаAndroid — да

iOS — главная боль. Web Push работает, но только для PWA, добавленных на главный экран. Обычная вкладка в Safari ничего не получит.

VAPID-ключи

VAPID — это пара ключей, которой ваш бэкенд подписывает push-сообщения. Браузер по подписи понимает, что это вы.

npx web-push generate-vapid-keys

Выдаст два ключа — public и private. Публичный передаётся в браузер при подписке, приватный хранится на сервере как секрет.

VAPID_PUBLIC_KEY=BNb...
VAPID_PRIVATE_KEY=ZmI...
VAPID_SUBJECT=mailto:admin@example.ru

Service Worker

Минимальный SW, который умеет принимать push:

// public/sw.js
self.addEventListener("push", (event) => {
  const data = event.data?.json() ?? {};
  const title = data.title ?? "Новое уведомление";
  const options = {
    body: data.body ?? "",
    icon: "/icons/icon-192.png",
    badge: "/icons/badge-72.png",
    data: { url: data.url ?? "/" },
    tag: data.tag,
    requireInteraction: false,
  };
  event.waitUntil(self.registration.showNotification(title, options));
});

self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const url = event.notification.data?.url ?? "/";
  event.waitUntil(clients.openWindow(url));
});

Регистрация в приложении:

if ("serviceWorker" in navigator) {
  await navigator.serviceWorker.register("/sw.js");
}

Подписка пользователя

Самая важная часть — момент запроса разрешения. Сделать это сразу при первом заходе — гарантия, что 90% пользователей нажмёт «Block» навсегда. Браузер запоминает выбор и не покажет prompt повторно — больше уведомлений этому пользователю не отправить.

Правильная схема — двухступенчатый запрос: сначала показываем свой кастомный баннер «хотите получать уведомления о...», и только после клика «Да» вызываем нативный prompt.

async function subscribeUser() {
  const reg = await navigator.serviceWorker.ready;
  const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
  });
  await fetch("/api/push/subscribe", {
    method: "POST",
    body: JSON.stringify(sub),
  });
}

userVisibleOnly: true обязательно — без него в Chrome не разрешат подписку.

Бэкенд для отправки

Самый простой вариант — Node.js с библиотекой web-push:

import webpush from "web-push";

webpush.setVapidDetails(
  process.env.VAPID_SUBJECT!,
  process.env.VAPID_PUBLIC_KEY!,
  process.env.VAPID_PRIVATE_KEY!,
);

await webpush.sendNotification(subscription, JSON.stringify({
  title: "Заказ #1242",
  body: "Курьер забрал ваш заказ, доставка через 30 минут",
  url: "/orders/1242",
  tag: "order-1242",
}));

Если возвращается 410 Gone — подписка протухла, удаляйте из БД.

Хранение подписок

CREATE TABLE push_subscriptions (
  id            BIGSERIAL PRIMARY KEY,
  user_id       BIGINT REFERENCES users(id) ON DELETE CASCADE,
  endpoint      TEXT NOT NULL UNIQUE,
  p256dh        TEXT NOT NULL,
  auth          TEXT NOT NULL,
  user_agent    TEXT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  last_used_at  TIMESTAMPTZ
);
CREATE INDEX ON push_subscriptions(user_id);

Один пользователь может иметь несколько подписок (разные устройства, браузеры). При рассылке отправляйте на все, но дедуплицируйте по tag, чтобы на телефоне и ноутбуке не приходило одно и то же два раза подряд.

Сегментация и тематика

Жёсткое правило: подписка должна быть тематической, а не «на всё». Если пользователь подписался на «уведомления о доставке заказа», не шлите ему «скидка 20% на новую коллекцию». Это убивает доверие, а отписка от Web Push — это «Block» навсегда, без шанса вернуть.

Хорошая практика — категории подписок:

ТемаКогда отправлять
Статус заказаизменение состояния доставки
Сообщения от поддержкиновый ответ в чате
Напоминанияпользователь сам поставил «напомни через X»
Маркетинговые акциитолько если явно подписан отдельно

Метрики

Минимум, что нужно мерить:

  • Conversion rate баннера → нативного prompt → подписки.
  • Доля «Block» (потерянных навсегда).
  • Доля доставленных push (410 → отписан).
  • CTR — процент кликов от доставленных.
  • Доля unsubscribe (отозвал разрешение).

Хорошие цифры: CTR 5-15%, отписка менее 1% в месяц. Если CTR менее 2% — вы шлёте мусор, остановитесь.

Когда не стоит вообще

  • Контент-сайт без аккаунтов и без транзакционных событий — не о чём уведомлять.
  • B2B-сервис с email как основным каналом — пользователь и так читает почту.
  • Лендинг — нечего и зачем пушить, кроме «купи».
  • Если нет ресурса на сегментацию — вы скатитесь в спам и испортите репутацию.

Web Push хорош там, где есть события в реальном времени: маркетплейсы (статус заказа), мессенджеры, такси, доставка, биржи, тикет-системы поддержки.

iOS-специфика

Для iOS-пользователей:

  1. Сайт должен быть PWA с manifest.json и установлен «На экран Домой».
  2. После установки запросить разрешение можно только в ответ на пользовательское действие (клик).
  3. Уведомления приходят в нативный Notification Center.

Если iOS — большая доля аудитории и PWA нет, инвестируйте сначала в PWA, потом в Push.

Альтернативы

КаналПлюсыМинусы
Web Pushбесплатно, мгновенноне работает при offline-устройстве, iOS только PWA
Emailуниверсально, доступно офлайнзадержка, попадание в спам
SMSгарантированная доставкадорого (1.5-3 ₽ в РФ за штуку)
Telegram-ботбесплатно, быстронужно подписать на бота отдельно
In-app уведомленияработают на любом устройстветолько когда пользователь на сайте

Часто Web Push не вместо, а вместе с email и Telegram-ботом — для разных типов событий.

Итого

Web Push — мощный канал, но требует уважения к пользователю. Правильно: тематические подписки, явное согласие на каждый тип, метрики, разумная частота. Неправильно — prompt при первом заходе, рассылка скидок «всем подписанным» и удивление, почему отписка 30%.

Частые вопросы

Можно ли отправлять Web Push без согласия пользователя?

Нет, ни технически, ни юридически. Браузер не разрешит подписку без явного Notification.permission === "granted". И с точки зрения 152-ФЗ согласие на обработку контактных данных для рассылок должно быть явным и отзывным. Хорошая практика — отдельный чекбокс «согласен получать уведомления о...» с понятным описанием, что именно вы будете слать.

Сколько стоит инфраструктура для Web Push?

Сами push-серверы Chrome/Firefox/Safari бесплатны. Платите вы только за свой бэкенд: VPS для очереди отправки и БД для хранения подписок. Для 10-50 тысяч активных подписок хватает $10-20/мес сервера. Если рассылок миллионы в день — стоит вынести в отдельный воркер с очередью на RabbitMQ или Redis.

Что делать с Safari на iOS?

Только через PWA. Нужен валидный manifest.json, иконки правильных размеров, обслуживание по HTTPS, и сайт должен быть «Добавлен на экран Домой». Тогда Push работает. Если у вас 80% iOS-аудитории и нет PWA — Web Push для вас не вариант, рассматривайте email и Telegram.

Какая частота отправки безопасна?

Зависит от типа. Транзакционные (изменился статус заказа) — по событию, хоть 5 в день. Маркетинговые — не чаще 1-2 в неделю. Если шлёте каждый день «акция-акция-акция», получите массовую отписку и попадание под антиспам-эвристики браузеров (некоторые версии Chrome тихо снижают приоритет частых отправителей).

Как тестировать Push в разработке?

На localhost Chrome разрешает Push без HTTPS. На staging-окружении нужен валидный сертификат (Let's Encrypt). Тестировать удобно через DevTools → Application → Service Workers → Push, можно эмулировать получение сообщения. Для нагрузочного тестирования — отдельный скрипт, который шлёт через web-push на тестовые endpoints.

Если пользователь нажал Block, можно ли как-то вернуться?

Технически — нет, разрешение «забетонировано» в настройках браузера. Пользователь должен зайти в настройки сайта и вручную сменить на Allow. На практике этого почти никто не делает. Поэтому первый запрос — самый важный, и его нельзя транжирить на «может быть». Лучше потерять 50% потенциальных подписчиков на этапе своего баннера, чем сжечь их навсегда нативным prompt.

Можно ли через Web Push отправлять только текст или ещё картинки?

Картинки можно: параметр image в showNotification() поддерживается в Chrome и Edge. В Safari и Firefox — игнорируется, показывается только иконка. Поэтому проектируйте уведомление так, чтобы оно работало без картинки, а картинка была усилением. И помните про лимит payload — 4 КБ на push-сообщение, картинку лучше отдавать как ссылку, а не base64.