PUSH API · v1a

iOS SDK

기준 확인: 2026-09-11 · 외부 콘솔의 메뉴 이름은 변경될 수 있어요. 아래 공식 가이드 링크와 실제 SDK 버전도 함께 확인해 주세요.

시작 전에: 세 가지 연결을 구분해요

SDK 설치, 푸시 서비스 연결, 기기 등록은 서로 다른 단계예요. 라이브러리 빌드만 성공했다고 알림이 전달되지는 않아요. 아래 순서대로 본인 기기 하나에서 확인해 주세요.

  1. 대시보드에서 사용할 조직·프로젝트·앱 환경을 선택해요. Android와 iOS는 각각의 앱과 푸시 서비스 설정을 사용해요.
  2. 설정 → 푸시 서비스 연결에서 FCM 또는 APNs를 등록하고 검증 상태를 확인해요. /app-environments/{appEnvironmentId}/providers
  3. 설정 → SDK 연결에서 같은 앱 환경의 공개 키를 준비해요. 기존 앱은 이전 storageNamespace를 유지해요. 고객 SDK 주소는 고정이며 서버 API 키는 모바일 앱에 넣지 않아요. /app-environments/{appEnvironmentId}/settings/sdk

운영 대시보드 로그인 · 키 종류와 보관 위치

0. Apple APNs와 앱 서명 준비하기

  1. Apple Developer Account에서 앱 Bundle ID와 팀을 확인해요. Certificates, Identifiers & Profiles → Keys에서 APNs용 승인된 .p8 키를 준비해요.
  2. 새 키가 필요하고 생성 권한이 있다면 Keys → 새 키 추가 → Apple Push Notifications service (APNs) 선택 → Configure로 이동해요. Environment는 Sandbox, Production, Sandbox & Production 중 실제 서명된 aps-environment를 허용하는 값으로 선택해요. Key Restriction은 Team Scoped (All Topics) 또는 Topic Specific 중 필요한 범위를 검토하고, 앱의 Bundle ID/topic이 허용되는지 확인해 주세요.
  3. Kits 푸시 서비스 연결에서 .p8 파일과 Key ID, Team ID, 앱 Bundle ID/topic, APNs 환경을 확인해요. Key ID와 Team ID는 서로 다른 값이에요. 파일명에서 읽힌 Key ID도 Apple 계정의 값과 비교해 주세요.
  4. Xcode에서 앱 target → Signing & Capabilities → + Capability → Push Notifications를 추가하고 올바른 Team을 선택해요. 일반 표시 알림에 Background Modes를 무조건 추가할 필요는 없어요. 백그라운드 처리 기능이 필요할 때만 해당 계약과 실행 제한을 따로 확인해요.
  5. 최종 서명 결과의 aps-environment와 SDK의 KitsAPNsEnvironment(.sandbox 또는 .production), 푸시 서비스 연결의 APNs 환경을 맞춰요. Debug/Release라는 빌드 이름이나 Kits 앱 환경 종류만으로 결정하지 마세요.
Apple Developer의 실제 영어 Configure Key 화면. 저장되지 않은 새 APNs 설정에 Environment는 Sandbox, Key Restriction은 Team Scoped (All Topics)로 표시되며 저장 후 변경 제한 안내가 있어요.
2026-09-11 Chrome에서 촬영한 실제 영어 화면이에요. 새 키 폼에서 APNs를 선택해 Configure를 열었으며, 보이는 Sandbox·Team Scoped 값은 저장 전 기본 표시이지 운영 권장값이 아니에요. Save·Continue·Register를 누르지 않았고 키 생성·변경·다운로드도 하지 않았어요. 원본 이미지 보기

Apple 키 관리 안내 · APNs 등록과 서명 안내

공통 푸시 서비스 설정 화면 보기 · 화면 예시는 Android FCM이에요. iOS에서는 APNs .p8와 Key ID·Team ID를 사용하며 JSON 파일을 선택하지 않아요.

새 관리형 SDK 간편 연동

import KitsPush

// Once on MainActor, after wiring the AppDelegate bridge:
try Kits.initialize(publicKey: publicKey)
// Development-signed app instead:
// try Kits.initialize(publicKey: publicKey, options: KitsOptions(ios: KitsIOSOptions(apnsEnvironment: .sandbox)))

운영 주소와 기본 저장소는 SDK가 제공하고 인증 모드는 대시보드에서 읽어요. 새 SDK의 사용자 관측·이벤트·활동 집계는 기본으로 켜지며 저장된 수집 거부를 유지해요. 동의 전 수집은 collectionEnabled=false로 보류할 수 있어요. 기존 앱은 이전 namespace를 유지해야 하며 키 회전 때 바꾸지 마세요. iOS APNs 기본값은 production이며 실제 개발용 서명은 sandbox를 지정해요.

APNs 토큰 전달에는 KitsPushAppDelegate 상속 또는 SDK README의 AppDelegate 전달 브리지가 필요해요. SwiftUI는 UIApplicationDelegateAdaptor로 연결하고, Scene을 쓰면 클릭 전달도 연결해요. 초기화만으로 앱 delegate를 바꾸지는 않아요.

공통 선택 옵션의미 · 기본값
collectionEnabled통합 수집 허용 · true. false로 시작하면 수집 보류. 저장된 거부는 true 기본값으로 해제되지 않아요
foregroundPresentationPolicy포그라운드 표시 정책 · PRESENT / .present
shouldDisplay선택 표시 콜백 · 생략 시 허용, 오류 시 표시 안 함
identityAuthProviderVERIFIED 계정 증명 공급자 · 생략
audienceCollectionEnabled기본 사용자 정보·세션 수집 · true. 명시적 false는 유지
notificationEventsEnabled실제 수신·클릭 보고 · true. 메시지 track_events도 필요
observationOptionspersistNotificationEvents · true / activityDistributionEnabled · true

동의 전 수집을 보류하려면 초기화 옵션에 collectionEnabled=false를 지정해요. 동의 후 Kits.setCollectionEnabled(true)로 시작하고 false로 중단해요. 알림 수신 거부나 서버에 이미 저장한 기록의 삭제와는 별개예요. 각 기능 옵션을 명시적으로 false로 둔 경우 통합 허용으로 다시 켜지지 않아요. iOS 확장의 수신 보고는 별도 설정이에요.

Android 전용 설정은 android: KitsAndroidOptions(FCM 모드·알림 리소스), iOS 전용 설정은 ios: KitsIOSOptions(APNs 환경·delegate 연동)로 묶어요. shouldDisplay는 포그라운드 표시와 새 Android SDK 직접 표시를 제어해요. iOS와 구형 Android에서 OS가 직접 표시하는 백그라운드 알림은 차단하지 못해요. 초기화만으로 권한을 요청하지 않아요.

아래 core 전용 설정·OS 콜백 예제는 수동 저수준 연동을 유지할 때만 사용하세요. 위 관리형 초기화에서는 SDK가 그 연결을 맡아요.

지원 기준: iOS 15 이상, Swift 6, Xcode 16.4 이상. macOS 테스트 가능 여부를 macOS 앱 제품 지원으로 해석하지 마세요.

1. Swift Package 연결하기

운영자에게 검증된 kits-ios 소스나 승인된 패키지 위치를 받으세요. 이 문서는 확인되지 않은 공개 저장소나 릴리스 URL을 제시하지 않습니다. Xcode의 Add Package Dependencies → Add Local에서 /path/to/kits-ios를 선택하고 앱 타깃에 KitsPush 제품을 연결합니다.

2. 구성과 APNs 환경 확인하기

import KitsPush
import UIKit

// Legacy core integration only: keep this app's existing namespace unchanged.
// New integrations use Kits.initialize(publicKey:); the SDK manages storage.
let configuration = try KitsPushConfiguration(
    publicKey: configuredPublicKey,
    storageNamespace: configuredStorageNamespace,
    apnsEnvironment: configuredAPNsEnvironment
)
let kitsPush = try await KitsPushClient.configure(configuration)
await MainActor.run {
    UIApplication.shared.registerForRemoteNotifications()
}

configuredPublicKey·configuredStorageNamespace는 해당 앱 환경의 설정값입니다. configuredAPNsEnvironment는 앱 서명의 aps-environment에 맞춰 KitsAPNsEnvironment.sandbox 또는 .production으로 정합니다. 앱에 Push Notifications capability와 올바른 서명 설정이 필요합니다.

3. 이번 실행의 APNs 토큰 전달하기

실행할 때마다 registerForRemoteNotifications를 호출하고, 그 실행의 delegate 콜백 토큰을 전달합니다. 저장해 둔 옛 토큰으로 대체하지 마세요. 아래는 기존 delegate 안에 넣는 부분 예제이며 client 수명은 앱이 관리합니다.

// In your existing UIApplicationDelegate; kitsPush is the configured client.
func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
    Task { _ = await kitsPush.registerAPNsToken(deviceToken) }
}

// Observe acknowledgement separately from the local .queued result.
for await state in kitsPush.syncStates() {
    if case .synced = state {
        // Latest desired device state acknowledged by Kits.
    }
}

권한 요청 예제: 앱의 실제 선택을 전달해요

import KitsPush
import UserNotifications

// In a host-owned async action after explaining why notifications are useful.
// kitsPush is the same retained client configured at app startup.
let center = UNUserNotificationCenter.current()
let before = await center.notificationSettings()
if before.authorizationStatus == .notDetermined {
    do { _ = try await center.requestAuthorization(options: [.alert, .badge, .sound]) }
    catch { /* Show a safe error; do not pretend permission was granted. */ }
}
let settings = await center.notificationSettings()
let permission: KitsPushPermission
switch settings.authorizationStatus {
case .notDetermined: permission = .notDetermined
case .denied: permission = .denied
case .authorized: permission = .authorized
case .provisional: permission = .provisional
case .ephemeral: permission = .ephemeral
@unknown default: permission = .notDetermined
}
_ = await kitsPush.reportPermission(permission)
// Independently forward the user's real in-app choice:
// _ = await kitsPush.setPushPreference(.optedIn) or .optedOut
// Re-read settings after returning from iOS Settings, without prompting again.

Apple 알림 권한 안내

이미지 알림: Xcode 확장 target 추가하기

SDK는 이미지 다운로드·검증 도우미를 제공하지만 서명된 앱 확장을 대신 만들 수는 없어요. 이미지 알림을 사용하려면 호스트 앱에 Notification Service Extension을 포함해야 해요. 텍스트 알림만 쓰면 이 단계는 건너뛸 수 있어요.

  1. Xcode → File → New → Target에서 Notification Service Extension을 추가해요. 앱과 올바른 Team으로 서명하고 앱에 확장이 포함되었는지 확인해요.
  2. 확장 target에 KitsPushNotificationService 제품을 연결해요. 기본 앱의 KitsPush 제품과는 별개예요. Xcode가 만든 NotificationService 클래스는 아래처럼 교체할 수 있어요.
import KitsPushNotificationService

// Image-only default; no APNs private key or device bearer belongs here.
final class NotificationService: KitsNotificationServiceExtension {}

NSExtensionPrincipalClass가 이 클래스와 맞고 APPLICATION_EXTENSION_API_ONLY=YES인지 확인해요. 이미지 URL은 리다이렉트 없는 공개 HTTPS PNG/JPEG를 사용하고 실패 시 텍스트 알림으로 돌아가는지도 검사해요. 자세한 크기 제한과 수동 전달 예제는 아래 선택 기능 절에 있어요.

Apple Notification Service Extension 안내

5. 대시보드와 기기에서 끝까지 확인하기

  1. SDK 동기화 완료를 확인한 뒤 같은 앱 환경의 수신 대상 → 푸시 구독에서 앱 설치를 찾아요. 앱 설치 ID·푸시 구독 ID, 플랫폼, 권한, 수신 설정을 확인하고 토큰 원문은 공유하지 않아요.
  2. 본인 앱 설치만 테스트 기기로 지정하고 직접 대상 발송에서 선택해요. 테스트 발송으로 기록을 켜도 실제 알림이 가며 권한·동의·푸시 서비스 검증을 우회하지 않아요. 처음에는 이미지 없는 SERVICE 텍스트 한 건으로 확인해요.
  3. 발송 관리 → 발송한 메시지에서 요청과 개별 발송 결과를 확인해요. 대시보드/API 출처와 일반/테스트 목적은 별개예요. 접수 또는 푸시 서비스의 수락은 단말 표시·클릭 성공이 아니에요.
  4. 앱 foreground, background, 알림 탭 후 cold start, 거부 후 재허용을 각각 검사해요. 이미지·태그·다국어·수신 관측은 기본 발송이 확인된 뒤 하나씩 추가해요.

서버 API로 한 건 보내는 예제

막히는 단계부터 확인해요

증상먼저 확인할 내용
앱 설치가 안 보여요앱 환경과 공개 키, API origin/IP 접근, SDK 상태 오류를 확인해요. configure/Queued는 서버 성공이 아니에요. DB나 앱 데이터를 지워서 해결하려 하지 마세요.
APNs token / BadDeviceToken이번 실행의 APNs delegate 토큰, 서명 entitlement, Bundle ID/topic, APNs 환경 일치를 확인해요. 오래된 토큰 재사용이나 임의 토큰 생성으로 우회하지 않아요.
접수됐지만 알림이 없어요개별 발송 결과 → 푸시 서비스 오류 → OS 권한과 앱 수신 설정 → 채널/집중 모드 → foreground 표시 처리 순서로 확인해요. 조회 결과 없이 반복 발송하지 마세요.
알림이 두 번 와요기존 OneSignal과 Kits 양쪽에서 발송했는지, OS 표시 알림을 앱이 다시 표시했는지 확인해요. 서버 이벤트마다 기존 플랫폼과 Kits 중 어느 쪽이 담당하는지 명시해요.

4. 앱의 알림 delegate 유지하기

  • UNUserNotificationCenterDelegate는 앱이 소유합니다. 포그라운드 콜백에서 handleWillPresent, 클릭 콜백에서 handleNotificationResponse를 호출합니다.
  • 반환값이 nil이면 Kits 알림이 아니므로 기존 처리기로 넘깁니다. OS completion handler는 모든 경로에서 정확히 한 번 호출하고, 화면 이동 대기 때문에 붙잡아 두지 마세요.
  • 실제 OS 권한을 reportPermission에, 앱 안의 수신 선택을 setPushPreference에 전달합니다. SDK는 동의를 대신 받거나 링크를 자동으로 열지 않습니다.
  • queued는 로컬 저장 완료입니다. syncStates의 synced로 서버 반영을 확인하고, 필요하면 synchronize를 요청하세요.

기본 세그먼트와 SDK 태그

이 기능이 포함된 최신 SDK와 서버를 함께 사용하세요. SDK는 앱 버전·기기 언어·첫 세션·최근 세션·세션 수·누적 foreground 시간을 보고해요. 초기화만으로 세션을 만들지 않고, 실제 앱 foreground 진입을 관찰해요.

등록과 현재 사용자 연결이 완료된 다음 태그를 설정하세요. SDK 태그에는 서버 속성 정의 API가 필요 없어요. 키 목록이 자동으로 나타나며, 로그아웃 등으로 연결이 바뀌면 이전 태그는 지워져요.

let result = await kitsPush.setTags(["interest": "travel"])
let removal = await kitsPush.removeTags(["interest"])

위 두 줄은 설정과 삭제를 각각 보여주는 별도 예제예요.

대시보드의 세그먼트에서 기본 필터 또는 SDK 태그를 고르고 미리보기를 확인하세요. 새 세그먼트는 익명 기기를 포함할 수 있으며 조건에 맞는 푸시 구독에만 보내요. 테스트 기기 지정은 관리자만 대시보드에서 변경해요.

선택: 실제 수신·클릭 관측

수집 기본값 개편이 포함된 SDK 소스에서는 notificationEventsEnabled와 기록 재시도(persistNotificationEvents)가 기본 true예요. 기존 게시 버전은 다를 수 있으므로 사용하는 릴리스를 확인하세요. 메시지의 track_events=true는 별도로 필요하며, 생략한 서버 API 요청이나 저장된 초안의 false를 자동으로 바꾸지 않아요. 저장된 수집 거부와 collectionEnabled=false가 우선해요. 앱의 동의·개인정보 고지도 검토하세요.

// Optional argument in KitsPushConfiguration, after host consent:
notificationEventsEnabled: true

// Forward actual OS callbacks:
kitsPush.handleWillPresent(notification)
kitsPush.handleNotificationResponse(response)

메시지의 추가 필드는 {"track_events": true}입니다. 서버가 넣은 kits_delivery_id와 정확한 버전 marker가 있어야 보고합니다. 이 예약 ID를 직접 만들지 마세요. CLICKED가 RECEIVED를 만들지 않으며 푸시 서비스의 수락이나 표시를 수신으로 추정하지 않습니다.

직접 연결하는 호스트는 await reportNotificationReceived(payload) 또는 reportNotificationClicked(payload)를 사용할 수 있어요. willPresent는 RECEIVED, dismissal을 제외한 실제 response는 CLICKED예요. 백그라운드에서는 수신 보고를 설정한 Notification Service Extension이 필요하며 이미지 없는 일반 알림도 지원해요. OS 완료 콜백을 직접 지연하지 말고 SDK의 제한된 처리 시간을 사용하세요.

인증이 거부되는 401 같은 영구 실패는 무제한 재시도하지 않아요. 수집 기록은 알림 표시나 분석 완료 여부를 바꾸지 않아요.

메시지 상세의 SDK 수신·클릭 기록은 자동으로 불러오고, 화면을 보는 동안 최대 5분간 5초마다 갱신해요. 차트는 서버에 기록이 도착한 시각 기준이며 앱의 실제 클릭 시각과 다를 수 있어요. 새로고침하거나 나중에 다시 열어도 보관된 시간별 기록을 볼 수 있어요.

이미지 없는 알림의 백그라운드 수신

  1. 앱에 서명된 Notification Service Extension 타깃을 포함하고 KitsPushNotificationService를 연결하세요. SDK 라이브러리가 앱 타깃을 자동으로 추가하지는 못해요.
  2. 대시보드에서 수신·클릭 수집과 iOS 백그라운드 수신 수집을 모두 켜세요. API에서는 track_events=true와 track_receipts=true예요. 서버가 일반 APNs alert에 mutable-content와 해당 전달 전용 보고 토큰을 넣어요.
  3. KitsNotificationServiceExtension의 receiptConfiguration에서 확장이 직접 읽을 수 있는 현재 동의 설정을 확인해요. 허용이면 KitsNotificationReceiptConfiguration(reportingEnabled: true), 거부나 미확인이면 nil을 반환하세요. 앱 SDK의 수집 거부는 별도 프로세스인 확장에 자동 공유되지 않으므로 App Group 같은 공유 설정이 필요해요.

선택: 이미지 확장 설치

  1. Xcode에서 호스트 앱에 Notification Service Extension 타깃을 추가합니다. 그 타깃에는 KitsPushNotificationService 제품만 연결하고 KitsPush 앱 제품은 연결하지 않습니다. APPLICATION_EXTENSION_API_ONLY=YES와 올바른 signing/provisioning을 유지하세요.
  2. 아래 helper를 보존하고 수신·만료 두 메서드를 전달합니다. 메시지 image_url은 정상 API 필드로 보내며 Kits가 mutable-content를 설정합니다. 실제 기기에서 이미지와 실패 시 텍스트를 확인하세요.
import KitsPushNotificationService
import UserNotifications

final class NotificationService: UNNotificationServiceExtension {
    private let images = KitsNotificationService()
    override func didReceive(_ request: UNNotificationRequest,
        withContentHandler handler: @escaping (UNNotificationContent) -> Void) {
        images.didReceive(request, withContentHandler: handler)
    }
    override func serviceExtensionTimeWillExpire() {
        images.serviceExtensionTimeWillExpire()
    }
}

선택: 앱에 등록된 iOS 버튼

호스트에서 제목을 번역하고 category/action을 정의합니다. 다른 SDK와 등록을 경합하지 않도록 전체 집합을 앱 시작 시 한 번 등록하세요. ios_category는 배포된 identifier와 일치해야 합니다. 서버의 동적 버튼 배열은 없습니다.

let category = UNNotificationCategory(identifier: "ORDER_V1", actions: [
    UNNotificationAction(identifier: "VIEW_ORDER", title: "View order", options: .foreground)
], intentIdentifiers: [], options: [])
let actions = try KitsNotificationActions(categories: [category])
let all = try actions.categoriesForRegistration(preserving: otherHostCategories)
UNUserNotificationCenter.current().setNotificationCategories(all)

// In the real response delegate, after forwarding to kitsPush once:
if let event = actions.action(for: response) {
    // Authorize current user and business data before handling event.actionIdentifier.
}

최대 32 category·각 1–3 plain action이며 ID 충돌은 오류입니다. 미등록 버튼·다른 payload·기본 탭·dismissal은 nil입니다. 기본 탭은 별도 처리하고 completion handler는 정확히 한 번 호출하세요. 기존 SDK나 미설정 앱의 버튼 표시를 보장하지 않습니다.

SDK 로그인과 로그아웃

  • 인증 모드는 프로젝트 환경마다 하나이고 그 환경의 모든 앱에 적용돼요. 프로젝트 환경의 첫 앱은 BASIC으로 시작하고, 나중에 추가한 앱은 그 환경의 모드를 따라요. 새 SDK의 간편 초기화는 대시보드 정책을 자동 조회하므로 앱에 모드를 중복 지정하지 않아요. BASIC은 고객 인증 callback·JWT 발급 API·서명 키가 필요 없어요. BASIC은 external ID 소유권을 검증하지 않아서 같은 프로젝트 환경의 클라이언트가 다른 UID를 주장하고 그 알림을 받을 수 있어요. 이 제약을 받아들이는 환경에서 사용하세요. VERIFIED 인증 실패는 BASIC으로 자동 전환되지 않아요.
  • VERIFIED는 신뢰할 수 있는 고객 백엔드의 JWT가 필요해요. 앱 시작 시 SDK의 인증 callback을 연결해요. 고객 서버는 세션·활성 계정을 검증하고 요청한 external_id가 세션 UID와 같은지 확인한 뒤 createIdentityTokenSigner로 JWT를 로컬 서명해요. Kits 토큰 발급 HTTP 요청은 없고 기본 TTL은 300초예요. 앱이 보낸 사용자 ID만 믿으면 안 돼요.
  • generateIdentityKeyPair로 고객 환경에서 키쌍을 한 번 생성해요. keyId와 publicKeyPem만 Dashboard의 프로젝트 설정 → SDK 연결에서 프로젝트 환경에 등록하고 privateKeyPem은 고객 비밀 저장소에 보관해요. 등록한 공개 키는 그 프로젝트 환경의 모든 앱이 함께 써요. 개인 키는 Kits에 업로드하지 않아요. 새 공개 키 등록 → 고객 signer 키 교체 → 이전 키 폐기 순서로 회전해요. JWT 서명·검증 실패를 구형 발급이나 검증 없는 로그인으로 자동 우회하지 마세요.
  • 두 모드 모두 자기 앱 설치의 deviceToken으로 GET /v1/installations/{id}/identity를 조회하고 POST /v1/installations/{id}/identity로 연결해요. VERIFIED LOGIN은 token만, BASIC LOGIN은 external_id만 보내요. LOGOUT은 token과 external_id를 모두 생략해요. SDK가 expected_binding_id·operation_id·보안 저장·직렬화·재시도를 관리해요. 증명과 기기 토큰을 로그에 남기지 마세요.
  • SDK는 작업의 모드를 저장하며 구형 journal은 VERIFIED로 읽어요. 모드 변경 후 재시작하면 먼저 완료 상태를 조회하고, 다른 모드의 미완료 LOGIN은 IDENTITY_AUTH_MODE_CHANGED로 중단해요. 현재 세션의 login을 다시 호출하면 로그아웃 barrier를 거쳐 새 모드 작업을 만들어요.
  • 새 AUTO 흐름은 GET /v1/installations/{id}/identity-policy에서 현재 binding과 정책 revision을 함께 읽어요. 새 LOGIN의 expected_policy_revision이 바뀌면 409 IDENTITY_REBIND_CONFLICT로 다시 확인해요. 조회 실패·잘못된 응답을 BASIC으로 추정하지 않아요. 이미 보낸 작업은 먼저 결과를 확인하고 VERIFIED 증명은 임의로 제거하지 않아요. LOGOUT은 정책 조회 없이 기존 identity 경로를 사용해요. 위 명시적 모드 방식과 구형 journal은 계속 호환돼요.
  • JWT는 ES256, aud=kits:identity, iss=기기가 속한 앱 환경, identity.external_id와 정수 초 iat/exp를 사용해요. TTL은 1–3600초이며 기기·binding·operation에 묶이지 않아요. 탈취 시 유효 기간 내 다른 인증된 설치에서 재사용될 수 있고 내용은 암호화되지 않아요. HTTPS와 Cache-Control: no-store를 사용하고 개인 키·JWT·요청 본문을 로그에 남기지 마세요. 메시지마다 JWT를 발급하지 않아요.
  • 구형 호환: identity.createToken과 POST /v1/identity-tokens는 기존 kitid1 증명을 위해 유지하지만 새 연동에서는 deprecated예요. 이 구형 HTTP 경로만 identity:write 서버 키와 설치·binding·operation 문맥을 요구해요. 로컬 JWT 서명은 Kits API 키가 필요 없고 messages.create 발송은 별도의 messages:send 서버 키를 사용해요.
  • 로컬 큐에 저장됐다는 사실은 서버에서 로그아웃이 완료됐다는 뜻이 아니에요. identityState로 원격 확인 결과를 확인하세요. 이 흐름과 기존 서버 identify/logout 호출을 동시에 실행하지 마세요.

아래 키 설정과 서명 예시는 VERIFIED에만 필요해요.

import { createIdentityTokenSigner } from '@marco-inc/kits-node';

// VERIFIED backend only: load the private key from your secret store.
const signer = createIdentityTokenSigner({
  appEnvironmentId, keyId, privateKeyPem, ttlSeconds: 300,
});

// In an authenticated handler, after checking the active account:
if (request.external_id !== verifiedUid) throw new Error('Session changed');
const result = signer.sign(verifiedUid); // {token, expires_at}; no HTTP call
// Return only to the authenticated app with Cache-Control: no-store.

아래 호출은 각각 로그인 이벤트, 로그아웃 이벤트, 상태 조회에 사용하는 별도 예시예요. 순서대로 한 번에 실행하지 마세요.

// Once, on MainActor (APNs production by default):
try Kits.initialize(publicKey: publicKey)

// On authenticated login:
Kits.login(userID)

// On logout (a separate event):
Kits.logout()

// Local submission state is not remote confirmation:
Kits.identitySubmissionState

정확한 인증·요청·응답 계약 보기

앱 안의 알림 배너 클릭 기록

Kits.addForegroundListener { event in
    event.preventDefault()
    showInAppBanner(event.title, event.body, event.payload.data) {
        event.recordClick() // Call on MainActor.
        // Keep your existing navigation here.
    }
}

showInAppBanner는 앱에서 만든 표시 함수의 예시예요. 실제 클릭할 때만 event.recordClick()을 호출하세요. CLICKED만 기록하며 화면 이동이나 SDK 클릭 리스너를 다시 실행하지 않아요. 배너 표시만으로 클릭을 기록하거나 수신을 추정하지 않아요. 메시지의 track_events, SDK의 notificationEventsEnabled와 수집 동의가 필요하고, 중복 클릭·로그아웃·계정 변경·동의 철회는 기존 기록 정책을 따라요.