Braze 입문 2편. SDK 통합 + 첫 캠페인 end-to-end 흐름. Web · iOS · Android SDK 5분 설치, REST API (POST /users/track) 의 batch 패턴, External ID 매핑의 의미, User Alias 의 anonymous 단계 처리, Custom Event · Purchase · Attribute 3 모델, console 에서 Segment + Campaign 만들기 흐름, REST API key vs SDK key 보안, baseUrl 지역별 endpoint, rate limit 까지 풀어쓴 학습 노트.
이 글은 Braze 입문에서 운영까지 시리즈 2편이에요. 1편 의 큰 그림 다음 — 실제로 코드에 박는 첫 30분. 통합부터 console 에서 첫 메시지 보내기 까지.
이번 글의 목표
이 글을 끝까지 따라하면 Braze workspace → SDK 설치 → 첫 사용자 추적 → 첫 캠페인 발송 까지 end-to-end 가 됩니다.
목표 = 30분 안 첫 메시지 도달. 우리 앱·웹에서 직접 만든 사용자 에게 console 에서 보낸 첫 email · push.
통합의 두 갈래
Braze 와 데이터를 주고받는 방법:
| 방식 | 사용 |
|---|---|
| SDK (Software Development Kit, Web · iOS · Android · ...) | 사용자 디바이스에서 자동 추적 + 메시지 수신 |
| REST API (HTTP 기반 서버 통신 규약) | 서버에서 명시 추적 + 메시지 전송 |
대부분 운영 = 둘 다 함께. Client SDK = 사용자 행동 자동, Server REST API = 백엔드 이벤트 + 메시지 발송.
Web SDK — 5분 통합
설치
npm install @braze/web-sdk
# 또는
yarn add @braze/web-sdk
초기화
import * as braze from "@braze/web-sdk";
braze.initialize("YOUR_SDK_API_KEY", {
baseUrl: "sdk.iad-01.braze.com", // ★ 지역별 endpoint
enableLogging: false, // production = false
minimumIntervalBetweenTriggerActionsInSeconds: 30
});
여기서 시험 함정이 하나 있어요 — baseUrl 의 지역별 endpoint. Braze 는 workspace 가 어느 region 에 있는지 에 따라 다른 endpoint:
| Region | baseUrl |
|---|---|
| US-01 | sdk.iad-01.braze.com |
| US-02 | sdk.iad-02.braze.com |
| US-03 ~ US-08 | sdk.iad-03.braze.com ~ sdk.iad-08.braze.com |
| EU-01 | sdk.fra-01.braze.eu |
| EU-02 | sdk.fra-02.braze.eu |
| AU-01 | sdk.au-01.braze.com |
console → Settings → APIs and Identifiers 에서 확인.
→ 잘못된 baseUrl = 연결 안 됨. 가장 흔한 초기 실수.
External ID 설정
// 사용자 로그인 시점
braze.changeUser(user.id); // External ID
braze.getUser().setEmail(user.email);
braze.getUser().setCountry("KR");
braze.getUser().setLanguage("ko");
braze.getUser().setCustomAttribute("tier", "premium");
braze.openSession();
changeUser(externalId) = 가장 중요한 호출. Braze user 와 우리 DB user 의 연결.
Custom Event 박기
braze.logCustomEvent("product_viewed", {
product_id: "P-123",
category: "shoes",
price: 50000
});
braze.logCustomEvent("checkout_completed", {
cart_value: 150000,
items_count: 3,
payment_method: "card"
});
1편의 5 핵심 개념 의 Event 데이터 모델. 동사_명사 + snake_case 권장 (산업 공통).
Purchase 박기 (별도 모델)
braze.logPurchase(
"product_id", // 상품 ID
50000, // 금액
"KRW", // 통화
1, // 수량
{ // 추가 속성
category: "shoes",
discount_applied: true
}
);
Purchase 는 Custom Event 와 다른 모델. LTV(고객 생애 가치) · ARPU(사용자당 평균 매출) 분석 자동 + 결제 기반 segment. 결제는 항상 logPurchase, 일반 event 아님.
iOS SDK — 5분 통합
설치 (CocoaPods)
CocoaPods(iOS 의존성 매니저)로 라이브러리를 박는 흐름.
# Podfile
pod 'BrazeKit'
pod 'BrazeUI' # in-app message UI
pod 'BrazeNotificationService'
pod install
초기화 (Swift)
import BrazeKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
static var braze: Braze?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: ...) -> Bool {
let configuration = Braze.Configuration(
apiKey: "YOUR_IOS_API_KEY",
endpoint: "sdk.iad-01.braze.com"
)
AppDelegate.braze = Braze(configuration: configuration)
return true
}
}
External ID 설정
AppDelegate.braze?.changeUser(userId: user.id)
AppDelegate.braze?.user.setEmail("user@example.com")
AppDelegate.braze?.user.setCustomAttribute(key: "tier", value: "premium")
Event · Purchase
AppDelegate.braze?.logCustomEvent(
name: "product_viewed",
properties: ["product_id": "P-123"]
)
AppDelegate.braze?.logPurchase(
productId: "product_id",
currency: "KRW",
price: 50000.0,
quantity: 1
)
Push 권한 + 토큰 등록
import UserNotifications
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .badge, .sound]
) { granted, _ in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
// AppDelegate 의 didRegisterForRemoteNotificationsWithDeviceToken
AppDelegate.braze?.notifications.register(deviceToken: deviceToken)
iOS Push = 권한 요청 + APNs(애플 푸시 알림 서비스) token Braze 등록. 둘 다 필수.
Android SDK — 5분 통합
설치 (Gradle)
// app/build.gradle
dependencies {
implementation "com.braze:android-sdk-ui:LATEST_VERSION"
}
초기화 (Kotlin)
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val config = BrazeConfig.Builder()
.setApiKey("YOUR_ANDROID_API_KEY")
.setCustomEndpoint("sdk.iad-01.braze.com")
.build()
Braze.configure(this, config)
}
}
AndroidManifest.xml:
<application android:name=".MyApp">
...
</application>
External ID · Event · Purchase
// 로그인 시점
Braze.getInstance(this).changeUser(user.id)
Braze.getInstance(this).currentUser?.setEmail(user.email)
Braze.getInstance(this).currentUser?.setCustomAttribute("tier", "premium")
// Event
val properties = BrazeProperties()
.add("product_id", "P-123")
.add("category", "shoes")
Braze.getInstance(this).logCustomEvent("product_viewed", properties)
// Purchase
Braze.getInstance(this).logPurchase("product_id", "KRW", BigDecimal("50000"), 1)
FCM Push 토큰 등록
FCM(Firebase Cloud Messaging, 구글의 안드로이드 푸시 채널) 토큰을 Braze 에 넘기는 자리.
// FirebaseMessagingService
override fun onNewToken(token: String) {
Braze.getInstance(applicationContext).registeredPushToken = token
}
REST API — 백엔드 통합
인증 — API Key
Authorization: Bearer YOUR_REST_API_KEY. — 공식 docs
curl -X POST https://rest.iad-01.braze.com/users/track \
-H "Authorization: Bearer YOUR_REST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"attributes": [...],
"events": [...],
"purchases": [...]
}'
여기서 정말 중요한 시험 함정 — REST API Key 와 SDK Key 는 다름:
| 항목 | SDK Key | REST API Key |
|---|---|---|
| 공개 가능? | ✓ (Client SDK 박힘) | ✗ (절대 외부 X) |
| 권한 | event 로깅 + 메시지 수신 | 전체 운영 (user 관리 + 메시지 발송 + 분석 등) |
| 위치 | Web · iOS · Android | Server only |
| 유출 시 | 비교적 안전 | 즉시 rotation 필수 |
| prefix | (workspace 별) | 보통 별도 발급 |
REST API Key 는 권한별 발급 — user track 용 · messaging 용 · analytics 용 분리 가능. 최소 권한 원칙.
Endpoint 의 지역별 분리
US-01: https://rest.iad-01.braze.com
US-02: https://rest.iad-02.braze.com
EU-01: https://rest.fra-01.braze.eu
AU-01: https://rest.au-01.braze.com
SDK 의 baseUrl 과 같은 지역 기반. console 에서 확인.
가장 중요한 endpoint — POST /users/track
/users/track = User 프로필 생성/업데이트 + 이벤트 + 결제 기록. — 공식 docs
Body 구조
{
"attributes": [
{
"external_id": "user-123",
"email": "user@example.com",
"country": "KR",
"tier": "premium",
"last_login_date": "2026-05-17T09:00:00Z"
}
],
"events": [
{
"external_id": "user-123",
"name": "checkout_completed",
"time": "2026-05-17T09:15:00Z",
"properties": {
"cart_value": 50000,
"items_count": 3
}
}
],
"purchases": [
{
"external_id": "user-123",
"product_id": "P-123",
"currency": "KRW",
"price": 50000,
"quantity": 1,
"time": "2026-05-17T09:15:00Z"
}
]
}
한 요청에 attributes · events · purchases 동시 batch — 효율적.
Identifier 우선순위
Primary = external_id · user_alias · braze_id. Secondary = email · phone. — 공식 docs
primary 가 있으면 email/phone 은 attribute 로 처리되고, primary 가 없으면 email/phone 으로 사용자 lookup 이 돌아갑니다.
External ID 가 표준. 우리 DB user.id = external_id 매핑.
Batch 한도
Standard customers: 한 요청에 최대 75 objects total (attributes + events + purchases 합산). — 공식 docs
Request 1: attributes 25 + events 30 + purchases 20 = 75 ✓
Request 2: attributes 80 = 80 ✗ 거부
→ 대량 sync = 75 단위 split. 일반 운영은 보통 한 사용자 1~5 object 라 한도 안 닿음.
Rate Limit
Base: 3,000 requests / 3 seconds
시간당: 250,000 requests
대량 import = exponential backoff(재시도 간격을 지수로 늘리는 방식) + retry 필수.
Python 예제
import requests
from datetime import datetime, timezone
BRAZE_REST_URL = "https://rest.iad-01.braze.com"
BRAZE_API_KEY = os.environ["BRAZE_REST_API_KEY"]
def track_purchase(external_id: str, product_id: str, price: int):
response = requests.post(
f"{BRAZE_REST_URL}/users/track",
headers={
"Authorization": f"Bearer {BRAZE_API_KEY}",
"Content-Type": "application/json"
},
json={
"purchases": [{
"external_id": external_id,
"product_id": product_id,
"currency": "KRW",
"price": price,
"quantity": 1,
"time": datetime.now(timezone.utc).isoformat()
}]
}
)
response.raise_for_status()
return response.json()
# 사용
track_purchase("user-123", "P-456", 50000)
Node.js 예제
import axios from "axios";
const braze = axios.create({
baseURL: "https://rest.iad-01.braze.com",
headers: {
Authorization: `Bearer ${process.env.BRAZE_REST_API_KEY}`,
"Content-Type": "application/json"
}
});
async function trackUser(userId, attributes, events) {
const response = await braze.post("/users/track", {
attributes: [{
external_id: userId,
...attributes
}],
events: events.map(e => ({
external_id: userId,
name: e.name,
time: new Date().toISOString(),
properties: e.properties
}))
});
return response.data;
}
External ID 설계 — 처음부터 잘 잡기
여기가 운영의 처음이자 끝. External ID 결정 이 6개월 후의 모든 분석 의 기반.
External ID 선택 원칙
| 좋은 후보 | 나쁜 후보 |
|---|---|
| DB의 user.id (BIGINT) | email (변경 가능) |
| UUID (불변) | session_id (매번 다름) |
| 외부 SSO subject | phone (변경 가능) |
핵심 — 불변 + unique + 우리 시스템 내 표준. email · phone 은 변경 가능 해서 부적합. 대부분 = DB user.id 또는 UUID(범용 고유 식별자).
Anonymous 사용자 (로그인 전)
// 가입 전 사용자
braze.changeUser(undefined); // External ID 없음
// → Braze 가 자동으로 braze_id (anonymous) 생성
// 사용자 행동 추적 (anonymous 상태)
braze.logCustomEvent("landing_viewed", { ... });
// 로그인 시점 — External ID 연결
braze.changeUser(user.id);
// → 이전 anonymous 활동이 user.id 로 merge
Anonymous → 로그인 transition 의 자연스러운 merge. 기존 행동 손실 X.
User Alias — 추가 매핑
braze.getUser().addAlias("legacy_user_id", "legacy");
braze.getUser().addAlias("crm_contact_id", "salesforce");
Alias = External ID 외 부가 식별자. 여러 시스템 통합 시 유용 — 마이그레이션 중인 legacy 시스템 ID, CRM(Customer Relationship Management) 의 Salesforce contact ID, 외부 마케팅 도구 ID 같은 자리에서 씁니다.
Event · Attribute · Purchase 의 차이
여기서 자주 헷갈리는 자리 — 언제 무엇으로 박나.
정성 (Attribute) vs 정량 (Event)
User Attribute:
- 사용자 *상태* (현재 값)
- update 가능, 누적 X
- 예: tier, country, email, signup_year
Custom Event:
- 사용자 *행동* (timestamp 있음)
- append-only, 누적
- 예: product_viewed, login, search_performed
결제 = Purchase 별도
Custom Event "purchase" 로 박는 것 = 나쁜 패턴
Purchase 별도 모델로 박기 = 올바름
이유 — Purchase 가 LTV · ARPU · 결제 기반 segment 의 표준 자리. Custom Event 로 박으면 이 자동 분석 안 됨.
Subscription State 도 별도
// 이메일 옵트인
braze.getUser().setEmailNotificationSubscriptionType("opted_in");
// 푸시 거부
braze.getUser().setPushNotificationSubscriptionType("unsubscribed");
// SMS 동의
braze.getUser().setSubscriptionGroupSubscriptionState("group-id", "subscribed");
법적 안전 + 발송 자동 필터. 동의·거부 항상 기록.
Console 에서 첫 캠페인 — 5 단계
SDK + REST API 로 데이터 흐름 잡았으면, 이제 console 에서 메시지 보내기.
Step 1: Segment 만들기
Audience > Segments > Create Segment:
Segment 이름: "Active iOS 사용자"
Filter:
- Most Recent App is "iOS"
- Last Used App is within 7 days
- Custom Attribute "tier" is "premium" OR "enterprise"
→ 재사용 가능한 사용자 그룹. 이 segment 를 여러 캠페인/Canvas 에서 사용.
Step 2: Campaign 시작
Engagement > Campaigns > Create Campaign:
Campaign 종류: Push Notification (iOS · Android)
Campaign 이름: "여름 세일 알림"
Step 3: Audience 선택
Audience: Segment "Active iOS 사용자" (Step 1)
Estimated reach: 12,500 사용자
Step 4: Content 작성
Title: "여름 세일 시작!"
Body: "{{ ${first_name} | default: '회원님' }}님, 좋아하시는 상품 30% 할인 중이에요"
Deep link: myapp://sale
Image: (선택)
1편의 Liquid templating — {{ first_name }} 으로 개인화. default 필수.
Step 5: Schedule + 발송
Send: One-time
Time: 2026-07-01 09:00 KST
또는: "Send as soon as possible" (즉시 발송)
→ Launch 클릭 → 발송 시작 → console 에서 delivery · open · click 실시간 추적.
첫 발송 후 확인
Dashboard > Campaign Report:
- Sent: 12,453
- Delivered: 12,401 (99.5%)
- Opened: 4,326 (34.9%)
- Clicked: 1,234 (28.5% CTR)
- Conversions: 87 (결제 event)
5편 Product Analytics 와 비슷한 통합 dashboard — Braze 도 발송 → 행동 → 결제 자동 funnel.
End-to-End 30분 예시 — Web 앱
// 1. SDK 초기화 (앱 시작 시 한 번)
import * as braze from "@braze/web-sdk";
braze.initialize(process.env.BRAZE_SDK_KEY, {
baseUrl: "sdk.iad-01.braze.com",
enableLogging: process.env.NODE_ENV === "development"
});
// 2. 사용자 로그인 시
async function onLogin(user) {
braze.changeUser(user.id);
braze.getUser().setEmail(user.email);
braze.getUser().setCountry(user.country);
braze.getUser().setLanguage(user.language);
braze.getUser().setCustomAttribute("tier", user.tier);
braze.getUser().setCustomAttribute("signup_year", user.signupYear);
braze.openSession();
}
// 3. 주요 행동 추적
function trackProductView(product) {
braze.logCustomEvent("product_viewed", {
product_id: product.id,
category: product.category,
price: product.price
});
}
function trackCheckoutStart(cart) {
braze.logCustomEvent("checkout_started", {
cart_value: cart.total,
items_count: cart.items.length
});
}
function trackPurchase(order) {
for (const item of order.items) {
braze.logPurchase(
item.productId,
item.price,
"KRW",
item.quantity,
{ category: item.category }
);
}
}
// 4. 로그아웃
function onLogout() {
braze.changeUser(undefined); // Anonymous 로 전환
}
서버 측 (Node.js):
// 결제 완료 webhook 에서 다시 한 번 (확실한 추적)
async function onPurchaseWebhook(order) {
await braze.post("/users/track", {
purchases: order.items.map(item => ({
external_id: order.userId,
product_id: item.productId,
currency: "KRW",
price: item.price,
quantity: item.quantity,
time: order.completedAt
}))
});
}
Client + Server 동시 추적. Client 가 driver + Server 가 안전장치.
자주 만나는 사고 — 통합 단계
사고 1: baseUrl 잘못
원인 — 기본 iad-01 사용 → workspace 가 iad-03 → 연결 X.
해결 — console 에서 정확한 endpoint 확인 + 환경 변수.
사고 2: REST API Key 노출
원인 — Server Key 를 Client 또는 git public 에 박음.
해결 — Server only + secrets manager + 즉시 rotation if 유출.
사고 3: External ID 불일치
원인 — Client 는 user.id 박는데 Server 는 user.email → 두 entity 로.
해결 — user factory 함수 로 일관. 항상 DB user.id 통일.
사고 4: Anonymous → 로그인 transition 안 됨
원인 — changeUser(externalId) 호출 시점 잘못 (login 전 또는 너무 늦게).
해결 — 로그인 직후 즉시 changeUser. anonymous 행동 자동 merge.
사고 5: Purchase 를 Custom Event 로
원인 — logCustomEvent("purchase", { ... }) 로 박음.
해결 — Purchase 는 항상 logPurchase. LTV 자동 분석.
사고 6: Subscription state 무시
원인 — opt-out 한 사용자 에게 캠페인 발송 → 법 위반.
해결 — channel 별 subscription 추적 + 발송 자동 필터.
사고 7: Liquid default 누락
원인 — {{ first_name }} → 누락된 사용자 = "안녕하세요, !".
해결 — 항상 | default: '회원님'.
사고 8: Rate limit hit
원인 — 대량 user import (10만 명) 가 분당 너무 많은 요청.
해결 — batch 75 / request + exponential backoff + retry.
사고 9: External ID 너무 큼
원인 — 987 bytes 초과 → EXTERNAL_USER_ID_TOO_LARGE 에러.
해결 — 짧고 안정 ID (DB BIGINT 또는 UUID). 긴 hash 사용 X.
운영 권장 패턴
Pattern 1: User Factory 함수
// utils/braze-user.ts
export function syncUserToBraze(user: User) {
braze.changeUser(user.id);
braze.getUser().setEmail(user.email);
braze.getUser().setFirstName(user.firstName);
braze.getUser().setCountry(user.country);
braze.getUser().setLanguage(user.language);
braze.getUser().setCustomAttribute("tier", user.tier);
braze.getUser().setCustomAttribute("signup_year", user.signupYear);
braze.openSession();
}
모든 sync 가 같은 함수 — 일관성.
Pattern 2: Environment 별 SDK Key
# .env.production
BRAZE_SDK_KEY=xxx-prod
BRAZE_REST_API_KEY=yyy-prod
BRAZE_BASE_URL=sdk.iad-01.braze.com
# .env.development
BRAZE_SDK_KEY=xxx-dev
BRAZE_REST_API_KEY=yyy-dev
BRAZE_BASE_URL=sdk.iad-01.braze.com
workspace 별도 (dev · prod 분리) + env var.
Pattern 3: Event Name 상수화
// types/braze-events.ts
export const BRAZE_EVENTS = {
PRODUCT_VIEWED: "product_viewed",
CART_ADDED: "cart_added",
CHECKOUT_STARTED: "checkout_started",
CHECKOUT_COMPLETED: "checkout_completed", // Purchase 도 동시
SIGNUP_COMPLETED: "signup_completed"
} as const;
// 사용
braze.logCustomEvent(BRAZE_EVENTS.PRODUCT_VIEWED, properties);
오타 = compile error.
Pattern 4: Server-side 안전망
// 결제 완료 webhook 에서 다시 추적
// → Client 추적 누락 (앱 끄거나 네트워크 끊긴 경우) 대비
async function onOrderCompleted(order) {
await brazeApi.post("/users/track", {
purchases: [...]
});
}
Client + Server 동시 추적 — Client 가 빠른 UX, Server 가 정확성 보장.
Pattern 5: 배치 import (legacy 데이터)
async def bulk_import_users(users: list):
"""기존 사용자 10만 명을 Braze 로 import"""
for batch in chunks(users, 75):
payload = {
"attributes": [
{
"external_id": u.id,
"email": u.email,
"country": u.country,
"tier": u.tier,
"signup_year": u.signup_year
}
for u in batch
]
}
for attempt in range(3):
try:
resp = await braze_client.post("/users/track", json=payload)
resp.raise_for_status()
break
except RateLimitError:
await asyncio.sleep(2 ** attempt) # exponential backoff
await asyncio.sleep(0.1) # rate limit 안전선
대량 import 의 표준 패턴.
시험 직전 한 번 더 — SDK 통합 + 첫 캠페인 함정 압축 노트
- 통합 두 갈래 = SDK (Client · 자동) + REST API (Server · 명시)
- 대부분 운영 = 둘 다 함께
- Web SDK =
npm install @braze/web-sdk+initialize(key, options)+changeUser+setEmail+logCustomEvent - iOS SDK = CocoaPods BrazeKit + Swift Configuration · APNs token 등록
- Android SDK = Gradle braze-android-sdk-ui + Kotlin BrazeConfig + FCM token
baseUrl지역별 endpoint = US-01~08 · EU-01~02 · AU-01 (workspace 별 확인)- 잘못된 baseUrl = 가장 흔한 초기 실수
- External ID = 가장 중요한 호출 (
changeUser) - 불변 + unique + 우리 시스템 표준 (DB user.id 또는 UUID)
- email/phone 은 변경 가능 — 부적합
- External ID < 987 bytes
- Anonymous → 로그인 transition =
changeUser직후 자동 merge - User Alias = External ID 외 부가 식별자 (legacy ID · CRM · 외부 도구)
- REST API Key vs SDK Key — Server only vs Client 안전
- REST API Key 권한별 발급 (user track · messaging · analytics 분리)
- REST API Key 유출 = 즉시 rotation
- POST /users/track = 가장 중요한 endpoint
- body =
attributes+events+purchases(한 요청 batch) - Primary identifier = external_id · user_alias · braze_id
- Secondary = email · phone (primary 있으면 attribute 처리)
- Batch 한도 = 75 objects total / request (Standard)
- Rate limit = 3,000 req / 3 sec · 시간당 250,000
- 대량 import = batch 75 + exponential backoff
- Attribute vs Event vs Purchase:
- Attribute = 상태 (update 가능)
- Event = 행동 (timestamp, append-only)
- Purchase = 결제 (LTV · ARPU 자동 분석 위해 별도)
- Subscription State = channel 별 동의 (email/push/SMS)
- Console 5 단계 = Segment 만들기 → Campaign 시작 → Audience → Content (Liquid) → Schedule + Launch
- Liquid default 필수 —
{{ first_name | default: '회원님' }} - 함정 — baseUrl 잘못
- 함정 — REST API Key 노출 (즉시 rotation)
- 함정 — External ID 불일치 (user factory)
- 함정 — Anonymous transition 시점 잘못
- 함정 — Purchase 를 Custom Event 로 (LTV 분석 누락)
- 함정 — Subscription state 무시 (법 위반)
- 함정 — Liquid default 누락 ("안녕하세요, !")
- 함정 — Rate limit hit (batch + backoff)
- 함정 — External ID 너무 큼 (987 bytes 초과)
- 패턴 — User Factory 함수
- 패턴 — Environment 별 SDK Key (dev/prod workspace 분리)
- 패턴 — Event Name TypeScript 상수
- 패턴 — Server-side 안전망 (Client + Server 동시 추적)
- 패턴 — 배치 import (75 chunk + exponential backoff)
- 결과 — SDK + REST API + Console 한 흐름으로 첫 메시지 도달
공식 문서: Braze API Basics · POST /users/track 에서 원문을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
다음 글: