---
term: '푸시 알림 (APNs와 FCM)'
seoTitle: '푸시 알림이란? APNs, FCM, 디바이스 토큰, 웹 푸시'
headline: '푸시 알림(APNs와 FCM)이란?'
slug: push-notifications-apns-fcm
category: backend-compute
shortDefinition: '푸시 알림은 앱이 닫혀 있어도 플랫폼 푸시 서비스가 디바이스에 전달하는, 서버가 시작하는 메시지입니다.'
relatedTerms:
  - websockets-real-time-sync
  - real-time-live-queries
  - background-jobs-task-schedulers
  - presence-online-status
contrastsWith:
  - websockets-real-time-sync
aboutTerms:
  - 'Device Token'
  - 'APNs'
  - 'FCM'
  - 'Web Push (VAPID)'
faq:
  - question: '푸시 알림이란 무엇입니까?'
    answer: '운영체제의 푸시 서비스가 디바이스에 전달하고, 앱이 실행 중이 아니어도 표시되는 서버 시작 메시지입니다. 이 마지막 속성이 정의의 핵심입니다. 앱이 열려 있어야 하는 인앱 메시지, 살아 있는 연결이 필요한 소켓과 푸시를 구분하는 기준이기 때문입니다.'
  - question: '푸시 알림은 처음부터 끝까지 어떻게 작동합니까?'
    answer: '앱이 플랫폼의 푸시 서비스에 등록하고 디바이스 토큰을 받습니다. 앱은 그 토큰을 백엔드에 넘기고, 백엔드는 페이로드와 토큰을 APNs나 FCM에 보냅니다. 푸시 서비스는 운영체제가 유지하는 지속적인 연결로 이를 전달하고, 운영체제가 알림을 표시하거나 앱을 깨웁니다.'
  - question: '디바이스 토큰이란 무엇입니까?'
    answer: '플랫폼 푸시 서비스가 발급하는, 한 디바이스의 한 앱 설치를 가리키는 불투명한 식별자입니다. 비밀이 아니라 주소입니다. 재설치, 복원, 데이터 삭제 시 바뀌므로 앱은 실행할 때마다 토큰을 백엔드에 다시 동기화하고, 백엔드는 전송 결과가 토큰이 사라졌다고 알리면 이를 삭제합니다.'
  - question: 'APNs와 FCM의 차이는 무엇입니까?'
    answer: 'APNs(Apple Push Notification service)는 Apple 기기로 가는 유일한 길입니다. FCM은 Android 플랫폼의 푸시 서비스이며, 크로스 플랫폼 계층 역할도 겸합니다. APNs 자격 증명을 업로드해 두면 요청 하나를 받아 Apple 기기용으로 APNs 규격에 맞는 요청을 다시 발행합니다. API 하나로 두 플랫폼을 모두 다룹니다.'
  - question: '서버가 디바이스로 직접 푸시할 수 없는 이유는 무엇입니까?'
    answer: '배터리에 최적화된 단 하나의 지속적인 연결을 푸시 서비스와 유지하는 것은 운영체제뿐이기 때문입니다. 휴대폰의 모든 앱이 그 소켓 하나를 공유합니다. 임의의 서버는 무선 통신, NAT, 절전 상태를 거치며 연결을 열어 둘 수 없으므로 모든 푸시는 플랫폼 게이트웨이를 거칩니다.'
  - question: '웹 푸시는 어떻게 작동합니까?'
    answer: '서비스 워커를 통해 작동합니다. 페이지가 VAPID 공개 키로 구독하면 브라우저의 푸시 서비스가 엔드포인트 URL과 암호화 키로 이루어진 구독 정보를 반환하고, 서버는 Web Push Protocol에 따라 암호화된 페이로드를 그 엔드포인트로 POST합니다. 서비스 워커의 push 이벤트가 사이트가 닫혀 있어도 알림을 표시합니다.'
  - question: '푸시 전달은 보장됩니까?'
    answer: '보장되지 않습니다. APNs나 FCM의 성공 응답은 전달되었다는 뜻이 아니라 접수되었다는 뜻입니다. 오프라인 디바이스에는 병합되거나 기한이 있는 큐가 적용되고, 배터리 최적화 기능이 전달을 지연시키며, 사용자는 알림을 완전히 끌 수 있습니다. 푸시는 설계상 최선 노력 방식이므로, 중요한 것은 다른 채널도 함께 갖춰야 합니다.'
  - question: '사일런트 푸시 알림이란 무엇입니까?'
    answer: '알림을 표시하지 않고 앱을 깨워 데이터를 가져오게 하는 백그라운드 푸시입니다. 강하게 제한됩니다. 시간당 작은 예산이 있다고 생각하면 되며, 예산을 넘은 전송은 오류 없이 버려집니다. 따라서 새로 고침 힌트로는 쓸 수 있지만, 신뢰할 수 있는 동기화 수단으로는 결코 쓸 수 없습니다.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'Apple — Sending notification requests to APNs'
    url: 'https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns'
  - name: 'RFC 8030 — Generic Event Delivery Using HTTP Push'
    url: 'https://datatracker.ietf.org/doc/html/rfc8030'
  - name: 'RFC 8292 — VAPID for Web Push'
    url: 'https://datatracker.ietf.org/doc/html/rfc8292'
  - name: 'W3C Push API'
    url: 'https://www.w3.org/TR/push-api/'
cta:
  title: '모든 디바이스를 위한 하나의 푸시 API'
  text: 'Back4app의 푸시 서비스는 APNs와 FCM 자격 증명을 한 번만 보관하고, 모든 설치를 Installation 객체로 추적하며, Cloud Code, SDK, 대시보드에서 채널이나 쿼리 단위로 전송합니다.'
  linkText: '무료로 시작하기'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-10-29'
translationKey: push-notifications-apns-fcm
---

**푸시 알림은 앱이 닫혀 있어도 플랫폼 푸시 서비스가 디바이스에 전달하는, 서버가 시작하는 메시지입니다.** 제품 팀은 푸시를 참여 유도 채널로 알고 있지만, 이 항목은 전달 메커니즘을 다룹니다. 제품 팀이 불평하는 모든 특이 동작을 그 메커니즘이 설명해 주기 때문입니다. 핵심 사실은 이렇습니다. 자사 서버는 휴대폰과 직접 대화하지 않습니다. 각 모바일 운영체제는 제조사의 푸시 게이트웨이와 배터리에 최적화된 지속적인 연결을 정확히 하나만 유지합니다. Apple 기기에는 **APNs**(Apple Push Notification service), Android에는 **FCM**이며, 모든 앱의 모든 알림이 그 공유된 통로를 탑니다.

## 핵심 요약

| 질문 | 답변 |
| --- | --- |
| 전달 경로 | 자사 백엔드 → APNs/FCM → 운영체제의 단일 지속 소켓 → 앱 |
| 주소 | 디바이스 토큰 – 설치별로 발급되고 계속 바뀌며, 동기화와 정리가 필요 |
| 계약 | 최선 노력: 접수 ≠ 전달, 제한 적용, 사용자가 끌 수 있음 |
| 한도 | ~4 KB 페이로드 · 우선순위 플래그 · 시간당 아주 작은 예산의 사일런트 푸시 |
| vs. 소켓 | 푸시는 *닫힌* 앱에 도달하고, [웹소켓](/glossary/ko/websockets-real-time-sync/)은 *열린* 앱을 지원 |

## 전달 경로, 처음부터 끝까지

```text
1  앱이 운영체제에 푸시 등록을 요청
2  플랫폼 푸시 서비스가 디바이스 토큰(주소)을 발급
3  앱이 토큰을 자사 백엔드로 보내고, 백엔드가 이를 저장
4  이벤트 발생 → 백엔드가 { token, payload ≤4 KB }를 APNs / FCM에 POST
5  푸시 서비스가 디바이스의 지속적인 연결을 찾아 → 전달
6  운영체제가 알림을 표시 — 또는 사일런트 푸시라면 앱을 잠깐 깨움

FCM의 두 번째 역할: APNs 자격 증명을 업로드해 두면 요청 하나를 받아
Apple 기기용으로 APNs 규격에 맞는 요청을 다시 발행함 —
두 플랫폼에 API 표면 하나. BaaS는 그것마저 추상화함.
```

코드로 보는 양쪽 절반입니다. 클라이언트는 자신의 주소를 등록하고, 백엔드는 채널로 전송합니다:

**JavaScript:**

```javascript
// JavaScript — Cloud Code (cloud/main.js)
// One send call reaches both platform push services
await Parse.Push.send({
  channels: ['scores'], // or `where:` with an Installation query
  data: {
    alert: 'Kickoff! Follow the match live.',
    badge: 'Increment',
    uri: 'app://match/8fk2',
  },
}, { useMasterKey: true });
// Back4app routes to APNs for Apple devices and FCM for Android — one API
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// Register this install for push: token + channels live in an Installation
final installation = await ParseInstallation.currentInstallation();
installation
  ..set('channels', ['scores']) // subscribe to a topic-like channel
  ..set('user', currentUser);   // enables targeting by user later
await installation.save();
// deviceToken and deviceType are captured automatically — your backend
// now addresses this device without touching APNs or FCM directly.
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// Register this install for push: token + channels live in an Installation
var installation = ParseInstallation.current
installation?.channels = ["scores"] // topic-like channel
installation?.user = currentUser    // enables targeting by user later
try await installation?.save()
// deviceToken and deviceType are captured automatically — your backend
// now addresses this device without touching APNs directly.
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// Register this install for push: token + channels live in an Installation
val installation = ParseInstallation.getCurrentInstallation()
installation.put("channels", listOf("scores")) // topic-like channel
installation.put("user", ParseUser.getCurrentUser()) // target by user later
installation.saveInBackground()
// deviceToken and deviceType are captured automatically — your backend
// now addresses this device without touching the push services directly.
```

```mermaid
flowchart LR
  accTitle: 플랫폼 푸시 서비스를 통한 푸시 알림 전달
  accDescr: 앱은 플랫폼 푸시 서비스에 등록해 디바이스 토큰을 받고, 이를 애플리케이션 백엔드에 동기화합니다. 백엔드는 페이로드와 토큰을 APNs나 FCM에 보내고, 푸시 서비스는 각 디바이스 운영체제가 유지하는 단일 지속 연결로 이를 전달하며, 운영체제는 앱이 닫혀 있어도 알림을 표시합니다.
  A["디바이스의 앱"] -->|"1 · 등록"| PS["플랫폼 푸시 서비스<br/>APNs / FCM"]
  PS -->|"2 · 디바이스 토큰"| A
  A -->|"3 · 토큰 동기화"| B["자사 백엔드<br/>토큰 레지스트리"]
  B -->|"4 · 페이로드 + 토큰"| PS
  PS -->|"5 · 운영체제의<br/>단일 지속 연결"| OS["디바이스 운영체제"]
  OS -->|"6 · 표시 / 앱 깨우기"| N["알림<br/>(앱이 닫혀 있어도 됨)"]
```

## 디바이스 토큰: 계속 바뀌는 주소

토큰 수명 주기가 백엔드의 진짜 일이며, 설명 페이지들이 건너뛰는 부분입니다. **등록:** 운영체제가 앱 설치마다 토큰을 발급합니다. **저장:** 앱은 *실행할 때마다* 토큰을 백엔드에 동기화합니다. 토큰은 재설치, 복원, 데이터 삭제 시 조용히 바뀝니다. **대상 지정:** 전송은 토큰을 개별로 또는 레지스트리를 통해 지정합니다. **무효화:** 죽은 토큰으로 보낸 전송은 특정 오류로 돌아옵니다. APNs는 `410 Gone`을, FCM은 등록되지 않은 토큰 오류를 반환하며, FCM은 약 9개월 동안 사용되지 않은 토큰을 만료시킵니다. **정리:** 그런 오류가 오면 즉시 삭제합니다. 마지막 단계를 건너뛰는 백엔드에는 썩어 가는 토큰 테이블이 쌓여 전송을 낭비하고, 전달 지표를 왜곡하고, 캠페인을 느리게 만듭니다. 오래된 토큰 문제는 지루하고 누적되며, 실제 환경에서 가장 흔한 푸시 버그입니다.

## APNs vs. FCM

| | APNs | FCM |
| --- | --- | --- |
| 도달 범위 | Apple 기기 – 유일한 길 | Android는 기본, Apple 기기는 APNs로 프록시 |
| 인증 | `.p8` 서명 키(키 ID + 팀 ID) | 플랫폼 콘솔에서 발급한 서버 자격 증명 |
| 페이로드 | ~4 KB · `aps` 딕셔너리 | ~4 KB · notification + data 메시지 |
| 오프라인 처리 | 병합 – 앱당 *최신* 알림 하나만 유지 | TTL이 있는 큐, 최대 약 4주 |
| 우선순위 | 10(즉시) vs. 5(전력 절약) | High vs. normal |
| 부가 기능 | Collapse ID, 백그라운드 푸시 | 토픽, 디바이스 그룹, collapse key |

첫 번째 행의 실질적인 결과는 이렇습니다. 모든 크로스 플랫폼 백엔드는 두 서비스를 모두 연동하거나, FCM을 단일 표면으로 쓰거나(APNs 자격 증명을 FCM에 업로드), 두 자격 증명 세트를 모두 보관하는 플랫폼에 문제 전체를 넘깁니다. 마지막 선택지가 BaaS 섹션의 결론입니다.

## 푸시가 약속하지 *않는* 것

솔직한 계약을 한곳에 모았습니다. 푸시 서비스의 200 응답은 **전달이 아니라 전달을 위한 접수**를 뜻합니다. 오프라인 디바이스에는 충실한 큐가 쌓이지 않습니다. APNs는 앱당 *가장 최근* 알림 하나만 보관하고, FCM의 큐는 TTL이 지나면 만료됩니다. Android의 배터리 최적화 기능은 통제할 수 없는 방식으로 전달을 미루며, 사용자는 운영체제 수준에서 채널이나 앱 전체를 무음으로 만들 수 있고 백엔드는 이를 알 수 없습니다. 보이는 알림 없이 백그라운드에서 앱을 깨우는 **사일런트 푸시**는 시간당 아주 작은 예산 안에서 실행되며, [Apple의 공식 안내](https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns)에 따르면 그 이상은 *오류 없이* 버려집니다. 새로 고침 힌트일 뿐 동기화 프로토콜이 아닙니다. 여기서 나오는 설계 규칙은 이렇습니다. 푸시는 *어깨를 두드리는 것*이고, 데이터 자체는 앱이 열릴 때 API를 통해 이동하며, 반드시 도착해야 하는 것에는 두 번째 채널을 마련해야 합니다.

## 웹 푸시, 간단히

브라우저는 자체 전달 경로를 표준화했습니다. 페이지가 **VAPID** 키 쌍([RFC 8292](https://datatracker.ietf.org/doc/html/rfc8292) – 서버가 서명된 토큰으로 자신을 식별하므로 벤더 등록이 필요 없습니다)을 사용해 [Push API](https://www.w3.org/TR/push-api/)로 구독하면, 브라우저의 푸시 서비스가 구독 정보(엔드포인트 URL + 암호화 키)를 반환하고, 서버는 [Web Push Protocol](https://datatracker.ietf.org/doc/html/rfc8030)에 따라 암호화된 페이로드를 그 엔드포인트로 POST합니다. 서비스 워커가 `push` 이벤트를 받아 알림을 표시하며, 사이트가 닫혀 있어도, 데스크톱에서는 브라우저가 닫혀 있어도 동작할 수 있습니다. 브라우저 벤더마다 자체 푸시 서비스를 운영하며, 엔드포인트 URL이 서버에 어디로 POST할지 알려 줍니다. 솔직한 단서가 하나 있습니다. iOS에서 웹 푸시는 홈 화면에 설치된 웹 앱에서만 작동합니다.

## 푸시 vs. 웹소켓 vs. 라이브 쿼리

| | 푸시 알림 | [웹소켓](/glossary/ko/websockets-real-time-sync/) / [라이브 쿼리](/glossary/ko/real-time-live-queries/) |
| --- | --- | --- |
| 앱 상태 | **닫혀 있거나 백그라운드** | 열려 있고 연결됨 |
| 방향 | 단방향, 서버 → 디바이스 | 전이중 / 구독 푸시 |
| 지연 시간과 신뢰성 | 수 초 수준, 최선 노력 | 밀리초, 연결로 보장 |
| 페이로드 | ~4 KB 요약 + 딥 링크 | 프로토콜이 싣는 것이면 무엇이든 |
| 하이브리드 | 프로덕션 패턴: 연결되어 있으면 소켓으로 전달하고, 아니면 몇 초 뒤 푸시로 폴백 | |

둘은 경계 하나를 사이에 둔 보완재입니다. 소켓은 *화면을 보고 있는* 사용자를 지원하고, 푸시는 휴대폰을 내려놓은 사용자에게 도달합니다. 채팅 앱은 매일 이 하이브리드를 보여 줍니다. 앱이 열려 있는 동안 메시지는 연결을 통해 스트리밍되고, 앱이 닫히는 순간 같은 메시지가 푸시가 됩니다.

## 일반적인 사용 사례

- **메시지와 멘션** – 전형적인 푸시입니다. 누군가 사용자를 찾는데 앱은 닫혀 있습니다.
- **트랜잭션 알림** – 주문 발송, 차량 도착, 결제 완료. 앱으로 딥 링크되는 한 줄 요약입니다.
- **시간에 민감한 트리거** – 점수 변동, 가격 알림, [프레즌스](/glossary/ko/presence-online-status/)와 맞닿은 "X님이 방송을 시작했습니다" 같은 순간.
- **재참여 유도** – 절제해서, 채널별 수신 거부와 함께 사용해야 합니다. 그렇지 않으면 사용자는 모든 알림을 끕니다.
- **배지와 상태 힌트** – 사용자가 앱을 열기 전에 새로 고침하도록 유도하는 데 사일런트 예산을 씁니다.

## 푸시를 사용해야 할까? 결정 매트릭스

| 상황 | 선택 |
| --- | --- |
| 앱이 닫혀 있어도 사용자가 알아야 함 | 푸시 – 푸시의 본래 역할 |
| 앱이 화면에 열려 있음 | [라이브 쿼리 / 소켓](/glossary/ko/real-time-live-queries/) – 더 빠르고 신뢰성 있음 |
| 데이터가 반드시 도착해야 함 | 자사 API + [백그라운드 동기화](/glossary/ko/background-jobs-task-schedulers/), 푸시는 어깨 두드리기로 |
| 웹 사용자 | VAPID + 서비스 워커를 이용한 웹 푸시 |
| 잦은 사일런트 동기화 | 피해야 함 – 예산 초과로 버려지므로 앱을 열 때 동기화 |
| 두 플랫폼, 팀 하나 | 두 서비스 위의 API 하나 – FCM의 프록시 모드 또는 BaaS |

## 한계와 트레이드오프

- **전달은 약속이 아니라 확률입니다.** 푸시를 놓쳐도 견디는 흐름을 설계하고, 앱을 열 때 대조해야 합니다.
- **권한은 한 번뿐인 자본입니다.** 운영체제의 권한 요청(iOS와 웹에서는 명시적, 최신 Android에서는 런타임 요청)은 맥락 속에서 물을 때 수락률이 가장 높으며, 한 번 거부되면 사실상 되돌릴 수 없습니다.
- **토큰 레지스트리는 살아 있는 데이터셋입니다.** 실행 시 동기화하고 오류 시 정리하지 않으면, 전달률이 조용히 떨어지는 것을 지켜보게 됩니다.
- **페이로드는 사실상 공개입니다.** 알림은 잠금 화면에 표시되고 제3자 인프라를 거쳐 이동하므로, 요약과 ID만 담고 비밀은 절대 담지 않아야 합니다.
- **기능 하나에 자격 증명 체계 두 개.** `.p8` 키, 콘솔 자격 증명, 만료와 회전. 설정의 고통은 실재하며 플랫폼당 한 번씩 겪어야 하고, 바로 그것이 관리형 계층이 흡수하는 부분입니다.

## Back4app에서의 푸시

Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. Back4app의 푸시 서비스는 전달 경로 전체를 데이터로 바꿉니다. 클라이언트 탭이 보여 주듯, 모든 설치는 디바이스 토큰, 플랫폼, 채널, 사용자 포인터가 자동으로 담긴 **Installation** 객체로 스스로 등록됩니다. 전송 호출 한 번으로 **채널**(토픽 방식)이나 임의의 Installation *쿼리*("`scores`를 구독한 Android 사용자 중 이번 주에 앱을 열지 않은 모든 사람")를 지정할 수 있으며, 플랫폼이 APNs와 FCM 자격 증명을 보관하고 디바이스별로 라우팅합니다. 전송은 [Cloud Code](/glossary/ko/cloud-code-serverless-functions/)(수신자에게 푸시하는 `Message`의 `afterSave`가 하이브리드 패턴의 폴백 절반입니다), 예약된 [작업](/glossary/ko/background-jobs-task-schedulers/), 또는 일회성 캠페인을 위한 대시보드 콘솔에서 실행됩니다. 토큰 관리, 이중 자격 증명, 플랫폼별 페이로드의 특이점은 플랫폼의 동작이 되고, 코드는 방법이 아니라 대상과 내용만 결정합니다.
