연동 문서
brownie 에 고객 데이터를 보내는 방법
서비스 서버와 앱이 고객의 행동(이벤트) · 속성 · 수신 동의를 brownie 로 보내면, 세그먼트와 캠페인이 그 데이터로 받을 사람과 보낼 때를 정해요. 이 문서는 개념과 규칙을 설명하고, 엔드포인트별 요청 · 응답 형식은 API 레퍼런스에 있어요.
시작하기
모든 요청은 HTTPS 로 아래 호스트에 보내요. 경로는 모두 /v1/ 로 시작하고, 본문은 JSON 이에요.
https://api.brownie.im
서버에서 이벤트 한 건을 보내는 가장 짧은 예예요.
$ curl -X POST https://api.brownie.im/v1/profiles/ext:1234567/events \
-H "Authorization: Bearer $BROWNIE_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "purchase", "properties": {"productId": "P-1", "price": 39000},
"occurredAt": "2026-10-23T10:00:00+09:00"}'
성공하면 201 과 {"success": true, "data": {"duplicatedEvent": false}} 가 돌아와요.
키
키는 두 종류이고, 어디서 부르느냐에 따라 골라 써요. 한 요청에 둘을 함께 실으면 400 EVENT_012 예요.
| 키 | 헤더 | 쓰는 곳 | 특징 |
|---|---|---|---|
| 서버 키 | Authorization: Bearer {키} | 서비스 서버 | 비공개. 이벤트 · 속성 · 연락처 · 동의 · 탈퇴 · 세그먼트 조회를 모두 할 수 있어요. 키마다 만료 시각과 허용 IP 목록(최대 20개)을 둘 수 있어요. |
| 앱 키 | X-Brownie-App-Key: {키} | 앱 · 웹 프런트엔드 | 앱 번들에 들어가도 되는 공개 키예요. 이벤트 · 속성 수집과 세션 · 팝업 기능만 쓸 수 있고, 연락처 · 동의 · 탈퇴는 보낼 수 없어요. 웹에서 부르면 허용한 Origin 만 받아요. |
키가 없거나, 폐기 · 만료됐거나, 틀리면 이유를 구분하지 않고 401 EVENT_005 를 돌려줘요. 허용 IP 밖이면 403 EVENT_009, 허용하지 않은 Origin 이면 403 EVENT_010 이에요.
고객 식별
brownie 에서 고객 한 명은 프로필이에요. 프로필은 두 가지 값으로 가리킬 수 있어요.
externalId— 서비스의 회원 번호예요. 서버에서 보낼 때는 대부분 이 값을 써요. 처음 보는 값이면 프로필을 새로 만들어요.profileId— brownie 가 매긴 UUID 예요. 앱에서 로그인 전 익명 고객을 가리킬 때 써요.
경로에 프로필을 넣는 엔드포인트는 UUID 를 그대로 쓰거나, 회원 번호 앞에 ext: 를 붙여요. 예: /v1/profiles/ext:1234567/events. 본문에 넣는 엔드포인트는 externalId 와 profileId 중 하나만 보내요. 둘 다 오면 externalId 를 먼저 봐요(배치) — 동의 · 탈퇴처럼 정확히 하나만 받는 곳은 400 이에요.
이벤트 보내기
이벤트는 고객이 한 행동이에요. 세그먼트 조건과 캠페인의 반응 조건이 이 이벤트를 써요. 보내는 길은 둘이에요.
- 한 건씩 —
POST /v1/profiles/{profileId}/events. 서버 키 전용이고, 거절되면 상태 코드로 알려 줘요. - 묶어서 —
POST /v1/batches. 서버 키와 앱 키 모두 써요. 이벤트 · 속성 · 푸시 토큰을 한 요청에 묶고,202응답의rejected목록이 항목별 거절 이유를 알려 줘요. 나머지 항목은 그대로 저장돼요.
발생 시각 occurredAt
ISO-8601 로 보내요. 오프셋이 없으면 한국 시간으로 읽어요. 받을 수 있는 범위는 전월 1일 00:00부터 지금 + 24시간까지예요. 더 오래됐거나 미래면 그 이벤트를 거절해요.
중복 제거 clientEventId
재전송에 대비해 이벤트마다 고유한 값(64자 이하)을 실어 주세요. clientEventId 와 occurredAt 이 둘 다 같은 이벤트가 다시 오면 저장하지 않고 중복으로 알려 줘요. 서버 키와 앱 키로 보낸 이벤트끼리는 서로 중복으로 보지 않아요. 비워 두면 중복 검사를 하지 않아요.
구매 purchase
이름이 purchase 인 이벤트를 서버 키로 보내면 구매 기록이 따로 남아요. properties 에 productId(문자열)와 price(숫자)가 있어야 하고, currency(기본 KRW)와 quantity(기본 1)는 선택이에요.
속성 · 연락처
배치의 attributes 항목으로 프로필 속성을 정해요. externalId · profileId · phone · email 을 뺀 나머지 키가 모두 속성이에요.
{"attributes": [
{"externalId": "1234567", "phone": "010-1234-5678", "grade": "VIP", "lastCourseName": null}
]}
- 값을
null로 보내면 그 속성을 지워요. - 값을 더하거나 빼는 연산은 지원하지 않아요. 계산한 값을 그대로 보내 주세요.
phone·email은 메시지를 보낼 연락처예요. 서버 키로만 보낼 수 있어요. 전화번호는010-1234-5678·+821012345678같은 형식을 받아 국제 형식으로 맞춰요.
수신 동의
광고성 메시지는 고객의 동의를 확인한 뒤에만 나가요. 동의가 바뀔 때마다 서버에서 POST /v1/consent-events 로 알려 주세요(서버 키 전용).
kind | 의미 |
|---|---|
ADVERTISING | 광고성 정보 수신. 광고 푸시는 이 동의만 봐요. |
MOBILE_MESSAGE | 문자 · 카카오 메시지 수신. 광고 문자 · 알림톡은 이 동의와 ADVERTISING 이 모두 있어야 해요. |
EMAIL_MESSAGE | 이메일 수신. 광고 이메일은 이 동의와 ADVERTISING 이 모두 있어야 해요. |
NIGHT_ADVERTISING | 야간(21:00~08:00) 광고 수신 |
MARKETING_PRIVACY | 마케팅 목적 개인정보 활용 |
value는true(동의) 또는false(거부 · 철회)예요. 받은 적 없는 동의는 거부로 봐요.changeId는 변경 한 건마다 고유한 값이에요. 같은 값을 다시 보내면 처음 결과를 그대로 돌려주고, 같은 값에 내용이 다르면409예요. 예:1234567:MOBILE_MESSAGE:20261015100000123- 이미 받은 변경보다 이른 시각의 변경은 이력에만 남고 현재 동의는 바뀌지 않아요(
applied: false). 순서가 뒤바뀌어 도착해도 안전해요.
앱 연동 흐름
앱은 앱 키로 아래 순서를 따라요.
- 세션 시작 — 앱을 열 때
POST /v1/sessions를 불러요. 처음이면 기기 ID 와 익명 프로필을 발급해 줘요. 응답의deviceId를 저장해 두고, 이후 모든 요청에X-Brownie-Device-Id로 실어요. 응답의config는 전송 주기 · 재시도 간격 같은 앱 설정이에요. - 이벤트 · 속성 수집 —
POST /v1/batches로 묶어 보내요. 대상을 적지 않으면 그 기기의 프로필에 쌓여요. - 로그인 — 고객이 로그인하면
PUT /v1/devices/{deviceId}/profile에 회원 번호를 보내요. 익명으로 쌓인 기록이 회원 프로필로 이어져요. 응답의result는PROMOTED·MERGED·RELINKED·UNCHANGED중 하나예요. - 팝업 — 세션 · 배치 응답의
bundle에 띄울 팝업이 들어 있어요. 띄우면POST /v1/in-app-messages로 노출을, 누르거나 닫으면POST /v1/in-app-messages/{messageSendId}/events로 반응(CLICK·DISMISS·SNOOZE)을 알려요.
X-Brownie-Identity 헤더에 실어야 할 수 있어요. 토큰이 없거나 다른 회원의 것이면 401 EVENT_011 이에요.회원 탈퇴
회원이 탈퇴하면 서버에서 POST /v1/profile-deletions 를 불러요. 부르는 즉시 발송 · 세그먼트 · 수집 대상에서 빠지고, 24시간 뒤 행동 기록 · 연락처 · 속성 · 동의가 지워져요. 되돌릴 수 없어요. 동의 철회를 먼저 보낸 뒤에 부르세요 — 지운 프로필의 동의는 바꿀 수 없어요.
한도
| 항목 | 한도 | 넘으면 |
|---|---|---|
| 요청 본문 | 1MB | 413 EVENT_006 |
| 배치의 이벤트 · 푸시 토큰 | 각 100건 | 400 GLOBAL_002 |
| 배치의 속성 항목 | 75건 | 400 GLOBAL_002 |
| 한 요청이 가리키는 서로 다른 프로필 | 75명 | 400 EVENT_004 |
| 이벤트 이름 · 속성 키 | 200자 | 그 이벤트 거절 |
| 이벤트 속성 | 최상위 키 100개, 중첩 3단계, 이름 포함 16KB | 그 이벤트 거절 |
같은 곳에서 짧은 시간에 아주 많은 요청을 보내면 차단될 수 있어요. 가능하면 배치로 묶어 보내 주세요.
응답과 오류
성공 응답은 data 를 감싼 형식이에요. 204 는 본문이 없어요.
{ "success": true, "data": { ... } }
오류는 감싸지 않고 이 형식이에요. code 로 분기하고, message 는 사람이 읽는 설명이에요.
{ "code": "EVENT_005", "message": "호출자를 확인할 수 없습니다.", "response": null }
| 코드 | HTTP | 뜻 | 할 일 |
|---|---|---|---|
GLOBAL_002 | 400 | 요청 값 · 형식 오류 | message 의 필드를 고쳐요 |
EVENT_005 | 401 | 쓸 수 없는 키 | 키 · 만료를 확인해요 |
EVENT_007 · EVENT_013 | 403 | 키 종류가 맞지 않는 경로 | 서버 키 · 앱 키를 바꿔요 |
EVENT_008 | 400 | 이벤트를 저장할 수 없음 | response.reason 을 봐요 |
EVENT_016 | 409 | 동시에 바뀜 | 다시 보내요 |
EVENT_001 · EVENT_017 | 503 | 일시적인 실패 | Retry-After 초 뒤 같은 요청을 다시 보내요 |
요청에 X-Request-Id(영숫자 · ._-, 64자 이하)를 실으면 응답에 같은 값이 돌아와요. 없으면 서버가 만들어 줘요. 문의할 때 이 값을 알려 주시면 빨리 찾을 수 있어요. 전체 오류 코드는 API 레퍼런스의 엔드포인트별 응답에 있어요.