2026 앱 다운로드 프로모션 유저 플로우차트
🌐 웹
🔀 에어브릿지
🏪 스토어
📤 앱→Braze SDK 이벤트
📡 Braze 인앱 메시지
📝 Braze Custom HTML IAM
🎯 Braze 세그먼트 분기
🔔 OS 권한
💾 DB
✅ 완료
❌ 미완료
flowchart TD
classDef web fill:#7c3aed,stroke:#6d28d9,color:#fff
classDef airbridge fill:#f59e0b,stroke:#d97706,color:#fff
classDef store fill:#3b82f6,stroke:#2563eb,color:#fff
classDef braze fill:#ec4899,stroke:#db2777,color:#fff
classDef brazehtml fill:#f43f5e,stroke:#e11d48,color:#fff
classDef brazelogic fill:#14b8a6,stroke:#0d9488,color:#fff
classDef os fill:#6b7280,stroke:#4b5563,color:#fff
classDef db fill:#10b981,stroke:#059669,color:#fff
classDef event fill:#8b5cf6,stroke:#7c3aed,color:#fff
classDef end_success fill:#22c55e,stroke:#16a34a,color:#fff
classDef end_fail fill:#ef4444,stroke:#dc2626,color:#fff
classDef decision fill:#fbbf24,stroke:#f59e0b,color:#000
classDef settings fill:#9ca3af,stroke:#6b7280,color:#fff
WEB["🌐 웹 이벤트 페이지
CTA: 앱 다운로드하고 혜택 받기
딥링크: smgo://event/{eventId}
?apply_event=true&term={term}"]:::web
WEB -->|CTA 클릭| AB["🔀 에어브릿지 라우팅"]:::airbridge
AB -->|미설치| STORE["🏪 App Store
숨고 앱"]:::store
AB -->|기설치| APP_EXIST["📱 앱 오픈
(Deep Link 콜백)"]
STORE -->|이탈| EXIT["❌ 이탈
플로우 종료"]:::end_fail
STORE -->|설치| APP_NEW["📱 앱 첫 실행
(Deferred Deep Link 콜백)"]
APP_NEW --> EVT_NEW["📤 앱 → Braze SDK
① setCustomAttribute
('push_authorization_status', ...)
② logCustomEvent
('deeplink_open_promotion',
{link_type: 'deferred', term, event_id})"]:::event
APP_EXIST --> EVT_EXIST["📤 앱 → Braze SDK
① setCustomAttribute
('push_authorization_status', ...)
② logCustomEvent
('deeplink_open_promotion',
{link_type: 'direct', term, event_id})"]:::event
EVT_NEW --> BRAZE_ROUTE
EVT_EXIST --> BRAZE_ROUTE{"🎯 Braze 대상자 선별
트리거: deeplink_open_promotion
WHERE term={동적} / event_id={동적}
─────────────────
세그먼트 필터:
① is_participated attr
② 약관 동의 2개 attr
③ push_authorization_status
④ link_type 프로퍼티"}:::brazelogic
BRAZE_ROUTE -->|"is_participated_promotion_{term} = true"| IAM_ALREADY["📡 분기 A
✅ 이미 이벤트 참여가 완료되었습니다"]:::braze
IAM_ALREADY -->|확인| HOME_DONE["🏠 숨고 홈"]:::end_success
BRAZE_ROUTE -->|"미참여 + 약관 미충족
+ link_type = direct"| IAM_EXIST_NOTICE["📡 분기 B
👋 기설치 안내 모달
마케팅 앱 알림을 켜주셔야
응모가 완료돼요"]:::braze
IAM_EXIST_NOTICE -->|"응모 완료하기"| TERMS
BRAZE_ROUTE -->|"미참여 + 약관 미충족
+ link_type = deferred"| TERMS["📝 약관 동의 모달
Custom HTML IAM
미동의 항목만 동적 노출
☐ 개인정보 마케팅 활용 동의
☐ 마케팅 알림 수신 동의"]:::brazehtml
BRAZE_ROUTE -->|"미참여 + 약관 충족
+ push_authorization_status = authorized"| IAM_COMPLETE["📡 분기 D
🎉 이벤트 참여 완료!
is_participated_{term} = true"]:::braze
IAM_COMPLETE -->|확인| HOME_DONE2["🏠 숨고 홈 ✅"]:::end_success
BRAZE_ROUTE -->|"미참여 + 약관 충족
+ push = not_determined"| IAM_PUSH_NEW["📡 분기 E-1
🔔 Push Primer
not_determined → 앱 내 프롬프트"]:::braze
BRAZE_ROUTE -->|"미참여 + 약관 충족
+ push = denied"| IAM_PUSH_DENIED["📡 분기 E-2
🔔 Push Primer
denied → 기기 설정 앱 이동"]:::braze
IAM_PUSH_NEW -->|"OS 권한 허용"| EVT_GRANTED["📤 setCustomAttribute
('push_authorization_status', 'authorized')
logCustomEvent('push_permission',
{status: 'granted', source: 'prompt'})"]:::event
IAM_PUSH_NEW -->|"OS 권한 거부/닫기"| EVT_DENIED["📤 logCustomEvent('push_permission',
{status: 'denied', source: 'prompt'})"]:::event
IAM_PUSH_DENIED -->|"설정에서 켜기"| SETTINGS["⚙️ 기기 설정 앱
iOS/Android 공통
숨고 > 알림"]:::settings
IAM_PUSH_DENIED -->|닫기| EVT_DENIED
SETTINGS -->|허용 후 앱 복귀| EVT_GRANTED_S["📤 setCustomAttribute
('push_authorization_status', 'authorized')
logCustomEvent('push_permission',
{status: 'granted', source: 'settings'})"]:::event
SETTINGS -->|거부 유지| EVT_DENIED_S["📤 logCustomEvent('push_permission',
{status: 'denied', source: 'settings'})"]:::event
EVT_GRANTED --> IAM_COMPLETE
EVT_GRANTED_S --> IAM_COMPLETE
EVT_DENIED --> IAM_FAIL
EVT_DENIED_S --> IAM_FAIL
IAM_FAIL["📡 이벤트 참여 불가 안내
트리거: push_permission WHERE status≠granted
알림을 켠 후 다시 시도해 주세요"]:::braze
IAM_FAIL -->|확인| HOME_FAIL["🏠 숨고 홈 ❌"]:::end_fail
TERMS -->|"[동의하기] 클릭"| TERMS_ACTION["📝 brazeBridge 실행
① logCustomEvent('click_terms_submit_button', ...)
② setCustomUserAttribute(is_marketing_info_opted_in, true)
③ setCustomUserAttribute(is_marketing_message_opted_in, true)
④ requestImmediateDataFlush()
⑤ goToPage('terms-notice') → 동의 고지"]:::brazehtml
TERMS_ACTION -->|"[확인] isPushOn()=true"| CHESS_DROP["📝 PAGE 3A: 체스 드래그앤드롭
나이트를 체스판으로 드래그
드롭 성공 → 경품 추첨 즉시 실행"]:::brazehtml
TERMS_ACTION -->|"isPushOn()=false"| PUSH_PRIMER_IAM["📝 PAGE 3B: 푸시 프라이머
알림이 꺼져 있어요 (체인 나이트)
CTA: 알림 키고 나이트 소환하기
→ smgo://braze/open-notification-settings"]:::brazehtml
PUSH_PRIMER_IAM -->|"설정 후 앱 재진입"| CHESS_DROP
CHESS_DROP -->|"드롭 성공 (onDropSuccess)"| PRIZE_DRAW["📝 경품 추첨 + 동시 처리
① 가중치 랜덤 추첨 (prize_tier: top/mid/base)
② claimPrize() → GAS POST (no-cors)
③ logParticipation() → brazeBridge attr 생성
is_participated_{term} = true"]:::brazehtml
PRIZE_DRAW -->|"GAS doPost"| GAS["💾 Google Apps Script
시트 재고 차감
prize 컬럼 -1, braze_id 로그"]:::db
PRIZE_DRAW --> PRIZE_ROUTE{"경품 결과 분기
gasKey 기준"}:::decision
PRIZE_ROUTE -->|"macbook/ps5/dinner"| TOP3["📝 top3 당첨 페이지
구글폼 직접 랜딩
(제세공과금 입력)"]:::brazehtml
PRIZE_ROUTE -->|"gas/dept/conv/coupon_*"| CONFIRM["📝 confirm 모달
당첨을 축하드려요!
이벤트 종료 후 일괄 지급"]:::brazehtml
TOP3 -->|closeModal| DONE["🏠 숨고 홈 ✅"]:::end_success
CONFIRM -->|"handleFinalClose()"| DONE
🔄 동적 프로모션 처리 메커니즘
프로모션 term 값을 2단계에 걸쳐 동적으로 전달합니다. IAM에서 event_properties로 직접 참조하므로 중간 attribute 저장이 불필요합니다. 새 프로모션 추가 시 앱 코드 변경 없이 딥링크 URL과 Canvas 설정만 변경하면 됩니다.
| # | 단계 | 처리 내용 | 코드/설정 |
| ① | 앱 → Braze | 딥링크의 term 파라미터를 동적 파싱하여 이벤트 프로퍼티로 전송 | logCustomEvent('deeplink_open_promotion', { term: getQueryParam('term') }) |
| ② | IAM ← Canvas Entry | IAM HTML에서 event_properties로 트리거 이벤트 프로퍼티 직접 참조. User Update 스텝 불필요 | var promoTerm = "{{event_properties.${term}}}"; setCustomUserAttribute('is_participated_promotion_' + promoTerm, true) |
📌 새 프로모션 추가 시: ① 딥링크 URL의 term 파라미터 변경 (앱 배포 불필요) ② Braze Canvas 복제 + term 필터 변경 ③ IAM HTML 변경 불필요 ④ 앱 코드 변경 불필요
🔗 딥링크 구조
| 딥링크 URL 포맷 |
smgo://event/{eventId}?apply_event=true&term={term} |
| 파라미터 | 설명 |
eventId (URL path) | 이벤트 페이지 식별 ID |
term (query string) | 프로모션 식별자. 이벤트 프로퍼티 + attribute 키 생성에 사용 |
apply_event (query string) | true일 때만 Braze 이벤트 발송. false이면 페이지만 표시 |
📌 consent deeplink 처리: smgo://braze/consent 단독 호출 폐기 → handleFinalClose()에서 300ms 후 brazeBridge.closeMessage() 호출로 통합. Android WebView에서 딥링크 2회 연속 호출 시 두 번째 미실행 이슈 대응.
📋 "모두 동의" 조건 정의
| # | Attribute | 타입 | 동의 값 | Braze 세그먼트 필터 |
| ① | is_marketing_info_opted_in | Custom Attr (Boolean) | true | Custom Attribute → is_marketing_info_opted_in = true |
| ② | is_marketing_message_opted_in | Custom Attr (Boolean) | true | Custom Attribute → is_marketing_message_opted_in = true |
⚠️ "약관 미충족" = 위 2개 조건 중 하나라도 미충족
📌 참고: email_subscribe, push_subscribe는 Braze Canvas HTML에서 Liquid로 불러올 수 없으므로 제외
📤 deeplink_open_promotion SDK
| 앱이 발송하는 진입 이벤트 (apply_event=true일 때만) |
| 순서 | 항목 | 설명 |
| ① | setCustomAttribute('push_authorization_status', ...) | not_determined / denied / authorized |
| ② | logCustomEvent('deeplink_open_promotion', {...}) | 딥링크 진입 이벤트 |
| 이벤트 프로퍼티 |
| Property | Type | 설명 |
link_type | String | deferred = 신규 | direct = 기설치 |
term | String | 프로모션 식별자 (딥링크에서 동적 추출) |
event_id | String | 이벤트 페이지 ID (딥링크 path에서 동적 추출) |
func handleDeepLink(url: URL, isDeferred: Bool) {
let term = params["term"]
guard apply_event == "true" else { return }
UNUserNotificationCenter.current().getNotificationSettings { settings in
Braze.setCustomAttribute("push_authorization_status", status)
Braze.logCustomEvent("deeplink_open_promotion", properties: [
"link_type": isDeferred ? "deferred" : "direct",
"term": term, "event_id": eventId
])
}
}
🎯 Braze 대상자 선별 조건 Braze
| 공통 트리거: deeplink_open_promotion WHERE term={동적} 또는 event_id={동적} |
| 분기 | 세그먼트 필터 조건 | 결과 IAM |
| A. 이미 참여 | is_participated_promotion_{term} = true | ✅ 이미 참여 완료 |
| B. 기설치 + 미충족 | 미참여 + 약관 미충족 + link_type=direct | 👋 기설치 안내 모달 |
| C. 신규 + 미충족 | 미참여 + 약관 미충족 + link_type=deferred | 📝 약관 동의 모달 |
| D. 모두 동의 + 푸시 OK | 미참여 + 약관 충족 + push_authorization_status=authorized | 📝 약관 동의 IAM → 동의 고지 → 체스 드롭 |
| E. 모두 동의 + 푸시 미허용 | 미참여 + 약관 충족 + push≠authorized 📌 Canvas E-1/E-2 폐기 → IAM 내부 통합 | 📝 약관 동의 IAM → 동의 고지 → 푸시 프라이머 |
💡 "약관 충족" = is_marketing_info_opted_in=true AND is_marketing_message_opted_in=true
💡 분기 D/E 푸시 판단: IAM 내 isPushOn()으로 통합 처리 (foreground_push_enabled 우선 → push_authorization_status → brazeBridge.isPushPermissionGranted() fallback)
📝 약관 동의 Custom HTML IAM — [동의하기] 클릭 시퀀스 Custom HTML
1
유효성 검증 — 2개 체크박스 모두 체크 확인 (is_all_checked = true)
2
이벤트 발송 — logCustomEvent('click_terms_submit_button', {is_all_checked: true, checked_items: [...], event_term, event_id})
3
Braze attribute 업데이트
setCustomUserAttribute('is_marketing_info_opted_in', true)
setCustomUserAttribute('is_marketing_message_opted_in', true)
4
전송 — requestImmediateDataFlush()
5
동의 고지 페이지(terms-notice) 표시 → 동의 일시, 동의 항목 안내
6
[확인] → handleConsentConfirm() → isPushOn() 실행
1순위: foreground_push_enabled = true → 3A(체스 드롭)
2순위: push_authorization_status = authorized 체크
fallback: brazeBridge.isPushPermissionGranted()
isPushOn() = false → 3B(푸시 프라이머)
📤 기타 Custom Event
| push_permission (SDK) |
| Property | Type | 설명 |
status | String | granted / denied / ''(닫기) |
event_term | String | 프로모션 식별자 |
source | String | prompt / settings / ''(닫기) |
| view_participated_promotion_modal (IAM) |
prize_name | String | 경품명 |
prize_detail | String | 경품 상세 |
prize_tier | String | top / mid / base |
coupon_amount | Number | 쿠폰 금액 (쿠폰 당첨 시만) |
| click_terms_submit_button (IAM) |
is_all_checked | Boolean | 모든 항목 체크 여부 |
checked_items | Array | 선택된 항목 목록 |
event_term | String | 프로모션 식별자 |
🔑 Braze Custom Attribute
| Attribute | 타입 | 설정 주체 |
push_authorization_status | String | 앱 SDK 딥링크 진입 시 값: not_determined / denied / authorized |
is_participated_promotion_{term} | Boolean | Braze IAM 참여 완료 모달 조회 시 ⚡
event_properties.${term}으로 동적 생성 |
is_marketing_info_opted_in | Boolean | brazeBridge 약관 동의 모달 ⚡ |
is_marketing_message_opted_in | Boolean | brazeBridge 약관 동의 모달 ⚡ |
promotion_prize_{term} | String | brazeBridge 경품 추첨 완료 시 예: "숨고 쿠폰 10,000원" |
promotion_prize_tier_{term} | String | brazeBridge top / mid / base |
promotion_coupon_amount_{term} | Number | brazeBridge 쿠폰 당첨 시만 1000 / 5000 / 10000 / 30000 / 50000 |
📋 유저 타입별 플로우 요약
| 유저 타입 | 플로우 | Braze 분기 조건 | 최종 결과 |
| 신규 + 모두 동의 + 푸시 허용 | 📤 (deferred) → 분기 D → 🎉 참여 완료 | 미참여 + 약관2개 충족 + Push authorized | 🎉 완료 |
| 신규 + 약관 미충족 | 📤 → 분기 C → 📝 약관 동의 → 🎉 | 미참여 + 미충족 + deferred | 🎉 완료 |
| 신규 + 모두 동의 + 푸시 미허용 | 📤 → 분기 E → 🔔 Push Primer → ... | 미참여 + 약관 충족 + push≠authorized | 허용→🎉 / 거부→❌ |
| 기설치 + 모두 동의 + 푸시 허용 | 📤 (direct) → 분기 D → 🎉 참여 완료 | 미참여 + 약관 충족 + Push authorized | 🎉 완료 |
| 기설치 + 약관 미충족 | 📤 → 분기 B → 👋 기설치 안내 → 📝 약관 동의 → 🎉 | 미참여 + 미충족 + direct | 🎉 완료 |
| 기설치 + 모두 동의 + 푸시 미허용 | 📤 → 분기 E → 🔔 Push Primer → ... | 미참여 + 약관 충족 + push≠authorized | 허용→🎉 / 거부→❌ |
| 중복 참여 (공통) | 📤 → 분기 A → ✅ 이미 참여 완료 | is_participated_{term}=true | ✅ 이미 참여 |
📌 참고 사항
| 항목 | 설명 |
| 경품 추첨 로직 | 드롭 성공 즉시 실행. prize_tier_appinstall_2603 (top/mid/base) 기반 가중치 랜덤 추첨. top: 맥북/PS5/파인다이닝 포함 / mid: 주유권까지 / base: 쿠폰만. claimPrize()는 fire-and-forget |
| GAS 재고 차감 | IAM → Google Apps Script POST (no-cors mode). action=claim, prize=gasKey, braze_id 전송. 재고 0 이하여도 클라이언트 추첨 진행 (서버에서 차감 거부) |
| 경품 결과 분기 | top3 (macbook/ps5/dinner): 구글폼 직접 랜딩 (제세공과금 입력) 일반 경품 (gas/dept/conv/coupon_*): confirm 모달 → 이벤트 종료 후 일괄 지급 |
| Push Primer 닫기 처리 | push_authorization_status: 업데이트 안 함 (진입 시 값 유지)
push_permission: {status: '', source: ''} — 아무 행동도 하지 않은 것으로 통일 |
| source 판단 기준 | 진입 시 push_authorization_status 값 기준:
not_determined → source: 'prompt' / denied → source: 'settings' / 닫기 → source: '' |
| IAM 우선순위 | 권장: 이미 참여(Highest) > 기설치 안내 > 약관 동의 > 참여 완료. triggerMinimumTimeInterval 5초 설정 |
| 분기 D/E 푸시 판단 | Canvas E-1/E-2 분기 폐기 → 약관 동의 IAM 내 isPushOn()으로 통합 처리 ① foreground_push_enabled=true → 체스 드롭 ② push_authorization_status=authorized ③ fallback: brazeBridge.isPushPermissionGranted() |
| 프로모션별 attribute | is_participated_promotion_{term}은 event_properties.${term}으로 동적 생성. 새 프로모션 시 IAM HTML 변경 불필요. Canvas 복제 + term 필터 변경만. |