웹 푸시 알림: 작동 원리와 제대로 활용하는 방법
서비스 워커와 VAPID부터 iOS를 포함한 실제 브라우저 지원 현황까지, 웹 푸시 알림이 동작하는 방식과 성과를 가르는 권한 요청 UX 및 지표를 다룹니다.
웹 푸시 알림은 잘못된 설계 결정 하나로 고객에게 영구히 접근할 수 없게 되는 유일한 마케팅 채널입니다. 잘못된 순간에 권한을 요청하면 방문자가 차단을 누르고, 그 브라우저는 영원히 닫힙니다. 재요청도, 두 번째 캠페인도, 윈백 이메일도 없습니다. 메시지를 한 줄 쓰기 전에 이 메커니즘을 이해할 가치가 있는 이유는 바로 이 비대칭성 때문입니다.
웹 푸시 알림이란 무엇인가
웹 푸시 알림은 서버에서 구독자의 브라우저로 보내는 메시지로, 운영체제의 알림 센터가 표시하며, 사이트가 닫혀 있어도 전달됩니다. 마지막 부분이 핵심입니다. 페이지 내부 팝업과 달리 웹 푸시는 지금 웹사이트를 보고 있지 않은 사람에게도 도달합니다.
웹 푸시는 세 가지 웹 플랫폼 API가 협력해 만들어지며, 이 채널의 동작 대부분은 그 역할 분담으로 설명됩니다. Service Worker API, Push API, Notifications API입니다.
웹 푸시의 내부 동작
서비스 워커
서비스 워커는 웹 앱과 브라우저, 네트워크 사이에서 프록시 역할을 하는 JavaScript 워커입니다. 자체 스레드에서 실행되고, DOM에 접근하지 않으며, 자신을 등록한 페이지가 닫힌 뒤에도 계속 존재합니다. 아무도 탭을 열어 두지 않은 상태에서 푸시 메시지를 받아 알림을 표시할 수 있는 것은 이 지속성 덕분입니다. 서비스 워커는 보안 컨텍스트에서만 실행되며, 이는 HTTPS를 뜻하고 개발 목적으로는 http://localhost가 보안 컨텍스트로 취급됩니다.
구독: 엔드포인트 하나와 키 두 개
서비스 워커가 활성화되면 페이지가 registration.pushManager.subscribe()를 호출합니다. 브라우저는 해당 벤더의 푸시 서비스와 통신한 뒤 다음을 담은 PushSubscription을 반환합니다.
endpoint, 푸시 서비스가 메시지를 받아 주는 고유한 능력 URLkeys.p256dh, P-256 곡선 위의 타원 곡선 Diffie-Hellman 공개 키keys.auth, 인증용 비밀값
서버는 이 셋을 모두 저장하고 엔드포인트를 비밀로 취급해야 합니다. 이 값을 가진 사람은 누구나 그 구독자에게 전송할 수 있기 때문입니다. 키가 존재하는 이유는 페이로드가 종단 간 암호화되기 때문입니다. RFC 8291이 방식을 규정합니다. P-256 위의 ECDH 교환으로 공유 비밀을 만들고, HKDF로 키를 파생하고, aes128gcm 콘텐츠 인코딩 아래 AES-128-GCM으로 페이로드를 봉인합니다. 푸시 서비스는 자신이 읽을 수 없는 암호문을 중계할 뿐입니다.
푸시 서비스
푸시 메시지를 기기로 직접 보내지는 않습니다. 브라우저 벤더가 운영하는 푸시 서비스로 보냅니다. Chrome은 Google의 FCM 엔드포인트, Firefox는 Mozilla의 autopush, Safari는 Apple의 푸시 서비스입니다. RFC 8030, “Generic Event Delivery Using HTTP Push”가 프로토콜을 정의합니다. 서버가 구독 엔드포인트로 POST하면 푸시 서비스가 모바일 전달의 어려운 부분을 처리합니다. 기기와의 배터리 효율적인 단일 연결, 오프라인 상태에서의 큐잉, 메시지 도착 시 브라우저 깨우기입니다. 전달을 보장할 수 없는 이유도 여기에 있습니다. 기기가 충분히 오래 꺼져 있으면 메시지는 TTL에 따라 만료되어 폐기됩니다.
VAPID: 보내는 주체 증명하기
엔드포인트가 비밀이라는 것만으로는 보안이 빈약합니다. RFC 8292는 푸시 서비스가 메시지를 보낸 애플리케이션 서버가 어디인지 알 수 있도록 Voluntary Application Server Identification, 즉 VAPID를 추가합니다.
NIST P-256 곡선 위의 ECDSA 키 쌍을 한 번 생성합니다. 공개 키는 브라우저가 구독할 때 applicationServerKey에 들어가 구독을 서버에 결속합니다. 푸시를 보낼 때마다 서버는 대응하는 개인 키로 ES256을 사용해 서명한 JWT를 보내며, 이 JWT에는 푸시 서비스 출처를 가리키는 aud 클레임, 24시간을 넘지 않는 exp 클레임, 그리고 선택적으로 연락처 정보를 담은 sub 클레임이 실립니다. 푸시 서비스가 서명을 검증하므로, 엔드포인트를 훔친 것만으로는 더 이상 구독자에게 스팸을 보낼 수 없습니다.
전달 경로 전체
- 페이지가 서비스 워커를 등록하고, 권한이 허용된 뒤 VAPID 공개 키와 함께
subscribe()를 호출합니다. - 서버가 반환된 엔드포인트와 키를 구독자 레코드에 저장합니다.
- 발송할 때 서버는
p256dh와auth로 페이로드를 암호화하고, VAPID JWT에 서명한 뒤 엔드포인트로 POST합니다. - 푸시 서비스가 요청을 인증하고 암호화된 메시지를 전달합니다.
- 브라우저가
push이벤트로 서비스 워커를 깨우면, 서비스 워커가 페이로드를 복호화하고ServiceWorkerRegistration.showNotification()을 호출합니다. - 클릭하면 서비스 워커에서
notificationclick이 발생하고, 거기서 목적지 URL을 엽니다.
권한 모델이 이토록 엄격한 이유
구독이 부여하는 권한을 보십시오. 사이트가 열려 있지 않아도 실행되는 백그라운드 프로세스에 더해, 운영체제의 알림 영역에 무언가를 그릴 수 있는 능력입니다. 그래서 브라우저는 이를 출처별로 사용자가 명시적으로 허용해야 하는 권한 뒤에 두며, 대부분은 요청이 실제 사용자 제스처를 뒤따를 것을 요구합니다.
두 번째 제약은 사람들을 놀라게 합니다. Chrome과 Edge는 구독 시 userVisibleOnly: true를 요구하는데, 이는 모든 푸시가 사용자에게 보이는 알림을 만든다는 약속입니다. 따라서 조용한 백그라운드 푸시는 이 API가 지원하는 용도가 아닙니다. Firefox 역시 알림을 생성하지 않는 푸시 메시지에 할당량을 적용합니다.
브라우저와 플랫폼 지원 현황
MDN에 따르면 Push API는 2023년 3월부터 Baseline widely available 상태이며, 이는 데스크톱의 Chrome, Edge, Firefox, Safari 최신 버전과 Android의 Chrome, Firefox에서 동작한다는 뜻입니다. 다만 헤드라인보다 중요한 단서가 두 가지 있습니다.
첫째, Notifications API는 균일하게 제공되지 않습니다. MDN은 이를 제한적 가용성으로 표시하는데, Notification() 생성자가 대부분의 모바일 브라우저에서 TypeError를 던지기 때문입니다. 휴대폰에서 반드시 동작해야 하는 기능이라면 ServiceWorkerRegistration.showNotification()을 통한 영구 알림을 쓰십시오. 어차피 여러분이 쓰고 있는 서비스 워커 경로입니다.
둘째, 알림 옵션의 구현 수준이 고르지 않습니다. 액션 버튼, 배지, 이미지, requireInteraction은 브라우저와 운영체제마다 다르므로, 제목과 본문과 아이콘만으로도 제대로 읽히는 알림을 설계하십시오.
iOS와 iPadOS의 요건
이 단서는 모바일 비중이 큰 오디언스에게 웹 푸시가 쓸 만한지를 결정하는데, 거의 모든 곳에서 잘못 설명되고 있습니다.
Apple은 iOS와 iPadOS 16.4에서 웹 푸시를 도입했고, 이는 홈 화면에 추가된 웹 앱에서만 동작합니다. WebKit의 표현대로 “홈 화면 웹 앱에 대한 웹 푸시 지원을 추가”하는 것이며, “홈 화면에 추가된 웹 앱은 푸시 알림 수신 권한을 요청할 수 있습니다”. 사용자는 공유 메뉴의 “홈 화면에 추가”로 이를 설치하고, 그다음 권한 요청은 구독 버튼을 누르는 것과 같은 직접적인 사용자 상호작용에 대한 응답으로 이루어져야 합니다.
iPhone의 평범한 Safari 탭에서 열린 사이트는 푸시 구독을 만들 수 없습니다. 이는 실질적인 장벽입니다. 권한을 요청하기도 전에 설치 단계를 요구하는 셈이기 때문입니다. macOS에서는 더 쉽습니다. macOS Ventura의 Safari 16.1이 설치 단계 없이 일반 웹사이트에 대한 표준 기반 웹 푸시를 추가했기 때문입니다.
최소한의 구독 예제
클라이언트 측 흐름 전체가 이것입니다. 이 코드는 페이지 로드 시점이 아니라 클릭 핸들러 안에 있어야 합니다.
async function subscribeToPush(vapidPublicKey) { // 푸시와 서비스 워커는 보안 컨텍스트(HTTPS)를 요구합니다. if (!("serviceWorker" in navigator) || !("PushManager" in window)) return null;
const registration = await navigator.serviceWorker.register("/sw.js");
// 사용자 제스처에서 호출해야 하며, 사용자당 한 번만 호출합니다. const permission = await Notification.requestPermission(); if (permission !== "granted") return null;
const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: vapidPublicKey, // base64url로 인코딩된 P-256 공개 키 });
// 엔드포인트와 키를 서버에 저장하고, 엔드포인트는 비밀로 취급합니다. await fetch("/api/push/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(subscription), });
return subscription;}sw.js 안에서는 push 이벤트를 처리하고, self.registration.showNotification(title, options)를 호출하고, notificationclick을 처리해 목적지 URL을 엽니다.
권한 요청 UX: 대부분의 프로그램이 무너지는 지점
페이지 로드 시점에는 절대 묻지 마십시오
Lighthouse에는 이를 위한 전용 감사 항목이 있습니다. “페이지 로드 시점에 알림 전송 권한을 요청하면, 그 알림은 사용자나 사용자의 필요와 무관할 수 있습니다.” 권고 사항은 특정 유형의 알림을 제안하고 사용자가 그 유형을 선택한 뒤에만 권한을 요청하라는 것입니다. Safari 12.1을 비롯한 여러 브라우저는 한 걸음 더 나아가, 요청 자체가 가능하려면 페이지와의 상호작용이 선행되어야 한다고 요구합니다.
소프트 사전 프롬프트를 쓰십시오
먼저 페이지 안에서 자체 초대를 보여 주십시오. 가치 제안을 이름 붙여 말할 수 있고, 영구적인 대가 없이 닫을 수 있으며, 여기를 클릭했을 때만 진짜 브라우저 프롬프트가 뜹니다. 오늘 소프트 프롬프트를 무시한 사람에게는 다음 달에 다시 물어볼 수 있습니다. 차단을 누른 사람에게는 그럴 수 없습니다. 이를 작동시키는 규칙은 두 가지입니다. “업데이트를 받아 보세요”가 아니라 실제로 무엇을 보낼지 서술할 것, 그리고 실수로 누르도록 설계하지 말 것입니다. 우발적인 수락은 곧바로 구독 해지로 이어집니다.
맥락 안에서 요청하십시오
Chrome의 권고는 “사용자가 주도권을 갖고 자기 속도에 맞춰 알림을 켜도록” 기존 인터페이스 안에 토글을 은근하게 배치하고, “맥락 없이 또는 사용자가 사이트에 도착하자마자 프롬프트나 오버레이를 띄우는” 일을 피하라는 것입니다.
커머스에서 통하는 순간은 구체적입니다. 품절 상품의 “재입고되면 알려 주세요” 컨트롤, 배송 업데이트를 제안하는 주문 확인 페이지, 자주 보는 상품의 가격 알림 토글입니다. 권한은 수확되는 것이 아니라 이름 붙은 혜택과 교환되는 것입니다.
거부는 사실상 영구적입니다
누군가 알림을 차단하면 브라우저는 그 결정을 여러분의 출처에 대해 저장합니다. 이후 Notification.requestPermission() 호출은 아무것도 표시하지 않고 저장된 denied 값으로 해소되며, MDN의 예제조차 requestPermission()을 호출하기 전에 Notification.permission을 확인하는 이유가 여기에 있습니다. 차단을 되돌리려면 사이트 설정을 파고들어야 하는데, 사실상 아무도 그렇게 하지 않습니다.
잘못했을 때 브라우저가 하는 일
그 대가는 이제 단순히 낮은 수신 동의율에 그치지 않습니다.
- Chrome은 수락률이 매우 낮은 출처를 기기 유형별로 구분해 조용한 권한 UI에 자동 편입시키고, 모든 사용자에 대해 프롬프트를 억제합니다.
- Chrome은 많은 푸시 물량과 낮은 참여도가 겹치는 사이트에 발송 한도를 적용하고 HTTP 429를 반환합니다. 제재는 1일, 7일, 14일로 단계적으로 커지고, 방해가 되지 않는 상태가 42일 연속 이어져야 초기화됩니다.
- Chrome은 이제 사용자가 최근에 상호작용하지 않은 사이트의 알림 권한을 자동으로 회수합니다. “사용자 참여도는 매우 낮은데 발송되는 알림의 양은 많은” 경우가 대상입니다. Google의 근거는 냉정합니다. “전체 알림 중 1% 미만만이 사용자로부터 어떤 상호작용이라도 얻습니다.”
잘못 보내는 것만으로 이미 확보한 구독자를 잃을 수 있습니다.
이메일 및 SMS와 비교한 웹 푸시
| 요소 | 웹 푸시 | 이메일 | SMS |
|---|---|---|---|
| 도달 범위 | 수신 동의한 브라우저만 | 주소를 가진 모든 사람 | 번호를 가진 모든 사람 |
| 한계 비용 | 사실상 0 | 매우 낮음 | 건당 과금, 가장 비쌈 |
| 즉시성 | 수 초, 운영체제가 노출 | 수 분에서 수 일, 받은편지함에 묻힘 | 수 초 |
| 메시지 길이 | 제목과 짧은 본문 | 무제한, 리치 서식 | 세그먼트당 약 160자 |
| 동의 | 브라우저 프롬프트, 클릭 한 번 | 주소 수집, 이상적으로는 더블 옵트인 | 명시적이며 규제가 엄격함 |
| 아이덴티티 | 기기 하나의 브라우저 | 사람 | 사람 |
| 이동성 | 없음 | 전체 내보내기 가능 | 전체 내보내기 가능 |
전략을 바꾸는 소유권의 차이
푸시 구독은 기기 하나의 브라우저 프로필 하나에 결속된 능력 URL입니다. 사람이 아닙니다. 노트북에서 Chrome을 쓰고 휴대폰에서 Firefox를 쓰는 동일한 고객은 서로 무관한 두 개의 구독이며, 본인이 스스로를 식별하지 않는 한 같은 사람이라는 사실을 알 방법이 없습니다.
이동성도 없습니다. 이메일 리스트는 내보내서 내일 다른 플랫폼에 올릴 수 있습니다. 푸시 구독은 벤더 간에 옮길 수 없습니다. 키와 VAPID 결속이 특정 애플리케이션 서버 키를 대상으로 생성되었기 때문입니다.
그러므로 웹 푸시는 소유 채널을 가속하는 촉진제로 다루고, 결코 대체재로 보지 마십시오. 푸시의 순간을 이용해 이메일 주소나 전화번호를 얻어야지, 그 반대여서는 안 됩니다. 여러 채널을 하나의 여정으로 엮는 방법은 마케팅 자동화 완벽 가이드에서 다룹니다.
실제로 통하는 활용 사례
이 채널은 시의성이 있고, 개인적으로 관련이 있으며, 한 번의 탭으로 실행할 수 있는 메시지에 보상을 줍니다.
- 장바구니 이탈. 한 시간 안의 푸시, 그리고 이후 이메일로 보강합니다. 순서 설계는 장바구니 이탈 이메일 가이드에서 다룹니다.
- 재입고 알림. 사용자가 명시적으로 알려 달라고 요청했으므로 가장 강력한 사례입니다.
- 관심 상품의 가격 인하. 같은 논리이며, 관련성을 스스로 선택한 경우입니다.
- 배송과 주문 상태. 열람 의도가 높고 불만 위험이 낮습니다.
- 구독한 주제의 속보. 뉴스, 경기 결과, 예약 가능 시간대입니다.
실패하는 것도 마찬가지로 분명합니다. “새 글을 올렸습니다” 식의 일괄 발송, 차별화되지 않은 매일의 할인, 제목과 한 줄 이상을 필요로 하는 내용, 지난 스무 번의 알림을 무시한 구독자에게 던지는 재참여 폭탄, 그리고 지속적인 기록이 필요한 트랜잭션성 내용입니다.
빈도, 타이밍, 세그먼테이션
보수적으로 시작하십시오. 구독자당 주 1건에서 3건으로 두고, 수신 거부율과 클릭률이 유지될 때만 늘리십시오. 피로감은 이메일보다 빨리 나타납니다. 운영체제가 이미 표면에 띄운 알림을 한 번 탭하는 것만으로 음소거할 수 있기 때문입니다.
타이밍은 장점이자 위험입니다. 푸시는 즉시 도착하므로 새벽 2시에 보낸 메시지는 새벽 2시에 도착합니다. 구독 시점에 구독자의 시간대를 저장하거나 추론하고, 정해진 시간대 안에서만 발송하십시오.
세그먼테이션은 사람이 아니라 구독에 대해 아는 것에 제약되므로, 실무적으로 쓸 수 있는 축은 행동 기반입니다. 조회한 페이지, 관심 등록한 상품, 장바구니 상태, 최근 구매 시점, 플랫폼입니다. 고객 세그먼테이션 가이드에서 더 깊이 다룹니다.
웹 푸시 측정하기
네 가지 지표가 중요하며, 측정 방식은 서로 다릅니다.
- 전달. 푸시 서비스가 요청을 수락했는지 여부입니다. 201은 수락이지 전달이 아닙니다. 404나 410은 구독이 죽었다는 뜻입니다.
- 표시. 알림이 실제로 표시되었는지 여부입니다.
showNotification()이 해소될 때 서비스 워커가 되보고해야만 알 수 있습니다. - 클릭률. 클릭을 표시 횟수로 나눈 값입니다. 최적화할 가치가 있는 숫자입니다.
- 수신 거부율. 발송당 구독 해지와 권한 회수입니다. 채널 소멸의 선행 지표이므로 클릭률보다 더 면밀히 지켜보십시오.
기여도 측정의 함정
푸시의 기여도 측정은 스스로를 후하게 평가합니다. 알림은 구독자가 이미 손에 쥐고 있는 기기에 도착하므로, 어차피 일어났을 세션의 공을 가져가는 경우가 많습니다. 증분 효과를 가정하지 말고 홀드아웃 그룹을 운영하십시오. 표시는 과소 집계되는 반면 클릭은 전부 기록되므로, 발송 수를 분모로 계산한 클릭률은 성과를 과대평가합니다. 그리고 구독이 사람이 아니라 브라우저이므로, 휴대폰에서 클릭한 뒤 데스크톱에서 구매로 끝나는 흐름은 서로 무관한 두 개의 사건처럼 보입니다. 채널 전반의 측정 위생은 이메일 마케팅 지표 가이드에서 다룹니다.
동의, GDPR, 수신 거부
브라우저 권한 프롬프트는 기술적 관문입니다. 그것이 자동으로 마케팅을 위한 완결된 법적 근거가 되지는 않습니다.
EU나 영국 거주자를 대상으로 마케팅한다면 푸시를 이메일과 동일하게 취급하십시오. 프롬프트가 뜨기 전에 무엇을 보낼지 설명해 동의가 충분한 정보에 기반하고 구체적이도록 하십시오. 구독이 언제 어디서 생성되었는지 기록하고, 무관한 행동에 푸시 동의를 끼워 넣지 마십시오. 구독을 식별된 고객과 연결한다면 그 데이터는 삭제 요구를 포함한 개인정보 의무의 범위 안에 들어옵니다.
수신 거부 위생도 그만큼 중요합니다. 차단 대신 빈도를 낮출 수 있도록 사이트 안에 환경설정 컨트롤을 제공하고, 사용자가 그렇게 하면 PushSubscription.unsubscribe()를 호출하고 서버 측 레코드를 삭제하며, 404나 410이 나오면 구독을 정리하십시오. MDN의 안내는 짧고 정확합니다. 사용자에게는 “앞으로 더 받지 않을 수 있는 쉬운 방법”이 제공되어야 합니다.
채널 스택에서 웹 푸시의 자리
웹 푸시는 훌륭한 세 번째 채널이자 나쁜 첫 번째 채널입니다. 빠르고, 한계 비용이 없으며, 시간이 중요한 알림에서는 비할 데가 없지만, 기기에 묶여 있고, 내보낼 수 없으며, 클릭 한 번이면 영구히 잃습니다.
그래서 진짜 문제는 오케스트레이션입니다. 어떤 메시지를 어떤 채널로 보낼지, 푸시가 이미 전환을 만들었을 때 이메일을 어떻게 억제할지, 사람을 서로 다르게 식별하는 여러 표면에서 고객에 대한 하나의 관점을 어떻게 유지할지입니다. Brevo는 이메일과 SMS와 나란히 웹·모바일 푸시를 제공하며, Tajo는 Brevo 위에 올라가 Shopify 스토어를 위한 채널 간 로직을 조율합니다. SMS 쪽은 SMS 자동화 가이드를 참고하십시오.
핵심 요약
- 웹 푸시는 세 가지 API의 협력입니다. 백그라운드 실행을 위한 서비스 워커, 구독과 전송을 위한 Push API, 표시를 위한 Notifications API입니다. 구독은 엔드포인트에
p256dh키와auth비밀값이 더해진 것이고, 페이로드는 종단 간 암호화되며, VAPID가 발신자를 증명합니다. - 푸시는 2023년 3월부터 Baseline widely available 상태이지만, iOS와 iPadOS에서는 홈 화면에 추가된 웹 앱에서만 동작합니다.
- 페이지 로드 시점에는 절대 권한을 요청하지 마십시오. 소프트 사전 프롬프트를 쓰고, 맥락 안에서 요청하고, 차단은 그 출처에 대해 영구적임을 기억하십시오.
- Chrome은 이제 조용한 프롬프트, 발송 한도, 자동 권한 회수를 집행하므로, 잘못 보내면 이미 확보한 구독자를 잃습니다.
- 구독은 사람이 아니라 브라우저이고 내보낼 수 없습니다. 이메일 리스트를 먼저 만들고, 푸시로 그것을 가속하십시오.