Web push bildirimi PWA’da neye yarıyor?
Minishlink ile bildirim alt yapısını çok basit bir şekilde kurduk. Bu alt yapı, uygulamanızı web siteni kullanan kişilerin telefonuna bildirim gönderir. Ayrıca abonelik sitemine kayıt olmuş kişilere push notification dediğimiz havalı bildirimler gönderir. Kurduğumuz Web Push yapısı, sekme kapalıyken bile CRM’deki hareketi ekrana taşıyor. Kütüphaneyi ekliyor, VAPID anahtarlarını üretiyor, Service Worker dinleyicilerini bağlıyoruz. CRM projelerimizde kullandığımız akışı aşağıya kodlarıyla yerleştirdik.
CRM’de güncelleme, mesaj ya da randevu kaybolmasın diye çoğu projede Minishlink tarafını seçiyoruz. Tarayıcı kapalıyken yeni müşteri kaydı için masaüstü bildirimi gösteriyoruz. Görev açıldığında ya da müşteri atandığında bildirim aynı akışla düşer.
PHP tabanlı işlerde Web Push için Minishlink/web-push kütüphanesini kullanıyoruz.
CRM’den gelen push bildirimi cihaza nasıl ulaşıyor?
Bu yapıda sunucu, tarayıcı, Service Worker ve push servisi aynı zincirin farklı noktalarında çalışıyor. Kullanıcı izin verdiğinde tarayıcı bir PushSubscription üretir; içinde endpoint, p256dh ve auth anahtarları bulunur. Bunları backend’e kaydederiz, ardından PHP tarafı şifreli mesajı yollar. Mesaj gelince Service Worker bildirimi gösterir. Sekme kapalı olsa da bildirim gelir; test ortamında bunu masaüstünde sık görürüz. (MDN Web Docs)
- Kullanıcı izin verir
- Tarayıcı PushSubscription oluşturur
- Abonelik backend’e gider
- Veritabanına kaydedilir
- Minishlink şifreli mesajı push servisine yollar
- Service Worker olayı yakalar
- Bildirim ekrana düşer
Kuruluma başlamadan önce hangi şartlar tamam olmalı?
Paket gelmeden önce sunucu tarafındaki bağımlılıkları tek tek kontrol ediyoruz. Yeni sunucu kurulumunda eksik kalan tek bir uzantı, ilk push denemesinin çalışmamasına yetiyor.
- HTTPS bağlantısı — push, güvenli bağlam dışında çalışmıyor
- Service Worker kaydı
- PHP 8.2 ve üzeri
- mbstring, curl ve openssl uzantıları
- bcmath ya da gmp — zorunlu değil, işi rahatlatır
- Composer
Paket projeye nasıl dahil ediliyor?
Composer’ı eklemek için terminalde şu komutu çalıştırıyoruz.
composer require minishlink/web-push
Composer paketi yerleştirdiğinde, PHP dosyanızın başında autoloader çağrısını açık tutuyoruz:
require_once __DIR__ . '/vendor/autoload.php';
Sınıfları şu şekilde kullanıyoruz:
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
Eğerki bu durum size karmaşık geliyorsa cursor ide si ile projenizi açın. Prompt olarak sadece şunu yazın: ben projeme minishlink kütphanesi eklemek ve bildirimleri bu kütüphane ile yönetmek istiyorum diyin. Gerini cursor a bırakın.
VAPID anahtarları neyi değiştirir?
VAPID anahtar çifti, bildirimi gönderen tarafın biz olduğumuzu tarayıcı servislerine kanıtlıyor. Doğrulamayı bu iki anahtar birlikte yürütür. Minishlink ile PHP üzerinden üretmek mümkündür:
use Minishlink\WebPush\VAPID;
$keys = VAPID::createVapidKeys();
print_r($keys);
Çıktı şu yapıya benzer:
[
'publicKey' => '...',
'privateKey' => '...'
]
Anahtarları .env içinde tutuyoruz:
VAPID_PUBLIC_KEY=PUBLIC_KEY_BURA
VAPID_PRIVATE_KEY=PRIVATE_KEY_BURA
Gizli anahtar JavaScript koduna hiç girmiyor. Tarayıcı yalnızca açık anahtarı görüyor. VAPID çifti değişirse eski abonelikleri yeniden oluşturmak gerekiyor. Bu yüzden anahtarları proje boyunca sabit bırakıyoruz. Minishlink dokümantasyonu da aynı yaklaşımı öneriyor. (GitHub)
Service Worker bildirimi nasıl yakalıyor?
Sunucu mesajı gönderdiğinde tarayıcı Service Worker’ı arka planda çalıştırıyor. Dinleyici, 'push' olayındaki veriyi alıp bildirim seçeneklerine dönüştürüyor. Sayfa açık değilse bile işlem devam ediyor.
Service Worker içine push dinleyicisini ekliyoruz. Kodu çoğu projede kökteki /service-worker.js dosyasında topluyoruz.
self.addEventListener('push', event => {
let data = {};
if (event.data) {
data = event.data.json();
}
const title = data.title || 'Yeni Bildirim';
const options = {
body: data.body || '',
icon: data.icon || '/icons/icon-192.png',
badge: '/icons/badge-72.png',
data: {
url: data.url || '/'
}
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
PHP’den gelen push isteği bu dinleyiciye düşüyor. showNotification() masaüstünde sağ altta, mobilde ise bildirim alanında görünür.
Bildirime tıklanınca hangi sayfa açılıyor?
Bildirime tıklanınca, bildirime eklenen bağlantı açılır. Adres yoksa ana sayfa açılır.
Kullanıcı bildirime bastığında doğrudan ilgili CRM kaydını açıyoruz. Bu yüzden notificationclick dinleyicisini ekliyoruz:
self.addEventListener('notificationclick', event => {
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.openWindow(url)
);
});
CRM’de yeni müşteri bildirimi gönderiyorsak /crm/customer/1542 gibi bir adresi payload içine koyuyoruz. Kullanıcı bildirime dokunduğunda genel panele değil, ilgili müşteri kartına gidiyor.
İzin isteme hangi anda devreye girmeli?
İzin penceresini sayfa yüklenir yüklenmez açmıyoruz. Biz bunu "Bildirimleri Aç" butonuna bağladığımızda kabul oranı yükseldi; tarayıcı da isteği daha doğal karşıladı.
const permission = await Notification.requestPermission();
Tıklama anında izin penceresi açılınca ziyaretçi neye onay verdiğini görür. Kullanıcı tetiklediğinde tarayıcılar izni daha seyrek engeller. (MDN Web Docs)
<button id="enableNotifications">
Bildirimleri Aç
</button>
JavaScript:
document
.getElementById('enableNotifications')
.addEventListener('click', async () => {
const permission =
await Notification.requestPermission();
if (permission === 'granted') {
subscribePush();
}
});
Tarayıcıda abonelik nasıl oluşturuluyor?
İzin verildikten sonra abonelik kaydını PushManager.subscribe() ile açıyoruz. Dönen nesnede endpoint ve şifreleme anahtarları bulunuyor. Öncesinde VAPID açık anahtarını Uint8Array biçimine çeviriyoruz. Hazırlanan kaydı backend’e gönderiyoruz.
Tarayıcı ham anahtarı kabul etmediğinden açık VAPID anahtarını önce uygun diziye dönüştürüyoruz:
function urlBase64ToUint8Array(base64String) {
const padding =
'='.repeat((4 - base64String.length % 4) % 4);
const base64 =
(base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
return Uint8Array.from(
[...rawData].map(char => char.charCodeAt(0))
);
}
Ardından kayıt açıyoruz:
async function subscribePush() {
const registration =
await navigator.serviceWorker.ready;
const subscription =
await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:
urlBase64ToUint8Array(
'VAPID_PUBLIC_KEY'
)
});
await fetch('/api/push/subscribe.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
});
}
PushManager.subscribe() tamamlandığında tarayıcının ürettiği abonelik kaydı elimizde oluyor. Endpoint ile şifreleme anahtarları aynı nesnede bulunuyor. Kullanıcı tarayıcıyı kapatsa bile kayıt sunucuda geçerliliğini koruyor. (MDN Web Docs)
Abonelik bilgisi nerede saklanıyor?
Tarayıcıdan gelen abonelik verisini basit bir tabloda tutuyoruz. Endpoint, p256dh ve auth alanları yetiyor.
PHP tarafında JSON verisini alıyoruz:
$data = json_decode(
file_get_contents('php://input'),
true
);
Bizim tabloda şu alanlar yeterli oluyor:
CREATE TABLE push_subscriptions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NULL,
endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Alanları şöyle ayırıyoruz:
$endpoint = $data['endpoint'];
$p256dh = $data['keys']['p256dh'];
$auth = $data['keys']['auth'];
PDO ile kayıt:
$stmt = $pdo->prepare("
INSERT INTO push_subscriptions
(user_id, endpoint, p256dh, auth)
VALUES (?, ?, ?, ?)
");
$stmt->execute([
$userId,
$endpoint,
$p256dh,
$auth
]);
Endpoint kolonunu benzersiz tutuyoruz. Aynı aboneliği iki kez yazmak, testte gereksiz yük çıkarıyor.
Gönderim sırasında hangi adımları uyguluyoruz?
Gönderimde önce VAPID anahtarlarını tanımlıyoruz. Sonra veritabanındaki abonelikle bildirim gövdesini eşliyoruz. Şifrelenen paketi ilgili push servisine iletiyoruz.
Bildirim gönderirken önce VAPID kimliğini hazırlıyoruz:
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
'VAPID' => [
'subject' => 'https://example.com',
'publicKey' =>
$_ENV['VAPID_PUBLIC_KEY'],
'privateKey' =>
$_ENV['VAPID_PRIVATE_KEY']
]
];
$webPush = new WebPush($auth);
$subscription = Subscription::create([
'endpoint' => $row['endpoint'],
'keys' => [
'p256dh' => $row['p256dh'],
'auth' => $row['auth']
]
]);
Payload örneği şöyledir:
{
"title": "Yeni müşteri atandı",
"body": "Ayşe Yılmaz için 14:30 görüşmesi eklendi",
"url": "/crm/customer/1542",
"icon": "/icons/icon-192.png"
}
Mesajı gönderdikten sonra sonucu kontrol ediyoruz:
$report = $webPush->sendOneNotification($subscription, json_encode($payload));
if ($report->isSuccess()) {
echo "Gönderildi";
} else {
echo "Hata oluştu";
}
410 yanıtı veren abonelikleri siliyoruz; aksi halde geçersiz endpoint’ler tabloyu gereksiz büyütüyor.
Peki bu yapıyı CRM projelerinde hangi anlarda kullanıyoruz?
Bir sağlık turizmi CRM’inde hasta kaydı açıldığında ilgili danışmana anında bildirim gönderiyoruz. Böylece ekip, paneli yeniden yüklemeden yeni başvuruyu görüyor. 2024’te kurduğumuz bir projede, aynı gün içinde gelen 30 başvurunun 27’si bildirime tıklanarak açıldı.
Biz şu yolu tercih ediyoruz:
- Yeni kayıt oluşturulunca bildirim tetiklemek
- Görev atamasında ilgili personele ayrı mesaj göndermek
- Randevu saatinden 15 dakika önce uyarı üretmek
- Silinen ya da iptal edilen işlem için farklı başlık kullanmak
Kimi projede sadece yöneticiler bildirim alıyor. Kimi projede satış ekibinin tamamı alıyor. Yetki yapısı burada belirleyici; herkese aynı mesajı yollamıyoruz.
Push’u CRM’e bağladığımız projelerde ekip, yeni başvuruyu paneli yenilemeden görüp daha erken arıyor. Masaüstünde sekme arka planda kalsa bile bildirim doğrudan ekrana ulaşır.
Sıkça sorulan sorular
- Web Push bildirimini kurmadan önce sunucuda hangi teknik şartların hazır olması gerekiyor?
- HTTPS bağlantısı olmadan tarayıcı push isteğini zaten kabul etmiyor. Bunun yanında Service Worker kaydı, PHP 8.2 ve üstü sürüm, mbstring, curl ile eliptik eğri destekli openssl uzantıları şart. bcmath veya gmp zorunlu değil ama şifreleme işlemini hızlandırıyor, biz projelerde genelde ikisinden birini açık tutuyoruz.
- Kullanıcı bildirim izni verdikten sonra mesaj ekrana kadar hangi adımlardan geçiyor?
- Akış yedi aşamada ilerliyor: kullanıcı izin verir, tarayıcı PushSubscription oluşturur, bu bilgi backend'e gönderilir, veritabanına kaydedilir, Minishlink şifreli mesajı push servisine yollar, Service Worker mesajı yakalar ve son olarak bildirim kullanıcının ekranında görünür. CRM projelerimizde bu zinciri değiştirmeden kullanıyoruz.
- VAPID anahtarlarını nasıl üretiyoruz ve saklama konusunda nelere dikkat ediyoruz?
- Minishlink\WebPush\VAPID sınıfındaki createVapidKeys() fonksiyonu public ve private key çiftini tek satırda üretiyor. Bu anahtarları .env dosyasında tutuyoruz; private key JavaScript tarafına asla taşınmıyor, sadece public key tarayıcıda kullanılıyor. Anahtar değiştirildiğinde eski tüm abonelikler düştüğü için biz anahtarı sabit tutmayı tercih ediyoruz.
- Kullanıcı sitedeki sekmeyi ya da uygulamayı tamamen kapatırsa bildirim yine gelir mi?
- Evet, geliyor. Push mesajı Service Worker tarafından yakalandığı için tarayıcı sekmesinin açık olmasına gerek kalmıyor. CRM ekranlarında müşteri ataması düştüğü anda ya da görev oluşturulduğunda bildirimi tetikliyoruz; kullanıcı uygulamayı kapatmış olsa bile sistem bildirimi masaüstünde veya telefonda görünüyor.
- Bildirim izni istemek için doğru zamanlama nedir, sayfa açılır açılmaz sorulmalı mı?
- Hayır, biz bu şekilde çalışmıyoruz. Notification.requestPermission() çağrısını sayfa yüklenir yüklenmez değil, kullanıcı "Bildirimleri Aç" butonuna bastığında tetikliyoruz. Tarayıcılar da otomatik izin isteklerini artık daha sıkı denetlediği için bu yaklaşım hem kabul oranını yükseltiyor hem de kullanıcı deneyimini bozmuyor.
Kaynakça
- MDN Web Docs: Push API — Push API mimarisi, güvenli bağlam (HTTPS) zorunluluğu ve Service Worker üzerinden bildirim karşılama mekanizmasının dayanağıdır.
- MDN Web Docs: PushManager.subscribe() — Tarayıcıda abonelik oluşturma adımları, endpoint yapısı ve p256dh ile auth anahtarlarını barındıran PushSubscription nesnesinin dayanağıdır.
- MDN Web Docs: Notification: requestPermission() static method — Kullanıcı etkileşimiyle bildirim izni isteme standartları ve izin akışının dayanağıdır.
- GitHub: web-push-libs/web-push-php (Minishlink) — PHP sürüm gereksinimleri, VAPID anahtarı üretimi ve backend bildirim gönderim süreçlerinin üretici dokümantasyonudur.
- IETF RFC 8292: Voluntary Application Server Identification (VAPID) for Web Push — Uygulama sunucularının push servislerine kendilerini tanıtmasını sağlayan VAPID standartlarının resmî şartnamesidir.



