방문자 식별과 맥락 전달
ReactiveChat('identify')로 로그인 사용자를 알리고 서버의 HMAC-SHA256으로 userHash에 서명하며 시크릿을 교체하고 제품·장바구니 같은 구조화된 페이지 맥락을 전달하는 방법.
웹사이트가 방문자가 누구인지 안다면 채팅에도 알립니다. 그러면 팀은 상담원 화면에서 “방문자 4f2a” 대신 이름을 보며 고객 데이터와 고객 포털 같은 기능을 사용할 수 있습니다.
원칙: 브라우저의 정보는 증명이 되지 않습니다. 페이지가 identify로 주장하는 내용은 누구나 콘솔에서 위조할 수 있습니다. 브라우저에 전달되지 않는 시크릿으로 운영자의 서버가 계산한 서명이 있어야 확인된 사용자입니다.
브라우저 호출
ReactiveChat('identify', {
userId: '4711',
email: 'kim@example.com',
name: 'Kim Berger',
userHash: '9c1f…',
exp: 1792503600
});
- userId(문자열, 권장) – 사용자의 자체 영구 ID입니다. 이메일 주소나 변경될 수 있는 일련번호가 아닙니다.
- email(문자열, 선택 사항) – 상담원 화면에 표시합니다. 이메일에도 서명해야 확인됩니다(아래 참조).
- name(문자열, 선택 사항) – 상담원 화면의 표시 이름입니다.
- userHash(문자열, 확인에 필수) – 서버에서 계산한 64자리 16진수입니다.
- exp(정수, 권장) – Unix 초 단위 해시 만료 시각으로 최대 24시간 후입니다.
다른 필드는 폐기합니다. identify는 위젯 로드 전에 호출해도 큐에 들어가며 조기 로드를 유발하지 않습니다. 연결 시 값을 함께 전달하고 이미 연결되어 있으면 즉시 전달합니다.
시크릿
계정의 프로젝트 → 본인 확인에서 프로젝트 시크릿을 생성합니다(프로젝트 수정 권한 필요). 64자리 16진수이며 전체 값은 정확히 한 번만 표시하고 이후에는 가립니다. 서버 환경에 넣고 브라우저 스크립트, 앱이나 저장소에 넣어서는 안 됩니다.
계산 시 중요 사항: HMAC 키는 디코딩한 바이트가 아닌 표시된 그대로의 64자 문자열 자체입니다.
서명 계산
userHash = hex( HMAC_SHA256( secret, userId + "." + exp ) )
소문자 16진수 64자입니다. 확인된 이메일 주소가 있는 경우:
userHash = hex( HMAC_SHA256( secret, userId + "." + exp + "." + email ) )
두 번째 형식에서만 이메일 주소가 확인된 것으로 간주되며 exp가 필요합니다.
PHP:
<?php
$geheim = getenv('RC_IDENT_SECRET'); // 64 hex characters from the account
$userId = (string) $kunde->id;
$exp = time() + 3600; // at most 24 h
$userHash = hash_hmac('sha256', $userId . '.' . $exp, $geheim);
?>
<script nonce="<?= $cspNonce ?>">
ReactiveChat('identify', {
userId: <?= json_encode($userId) ?>,
userHash: <?= json_encode($userHash) ?>,
exp: <?= (int) $exp ?>
});
</script>
Node.js:
const crypto = require('crypto');
function rcIdentitaet(kunde) {
const userId = String(kunde.id);
const exp = Math.floor(Date.now() / 1000) + 3600;
const userHash = crypto.createHmac('sha256', process.env.RC_IDENT_SECRET)
.update(userId + '.' + exp)
.digest('hex');
return { userId, userHash, exp };
}
Python:
import hmac, hashlib, os, time
def rc_identitaet(kunde_id: str) -> dict:
exp = int(time.time()) + 3600
msg = f"{kunde_id}.{exp}".encode()
key = os.environ["RC_IDENT_SECRET"].encode() # the hex string as text
return {"userId": kunde_id, "exp": exp,
"userHash": hmac.new(key, msg, hashlib.sha256).hexdigest()}
이메일 주소 포함(PHP):
$userHash = hash_hmac('sha256', $userId . '.' . $exp . '.' . $email, $geheim);
주소는 identify에 전달하는 그대로 서명해야 합니다.
인라인 스크립트 없이 사용(엄격한 CSP)
Content Security Policy가 인라인 스크립트를 허용하지 않으면 값을 데이터로 출력하고 자체 스크립트 파일에서 identify를 호출합니다.
<meta name="rc-ident" content='{"userId":"4711","userHash":"9c1f…","exp":1792503600}'>
// /js/chat-ident.js
var m = document.querySelector('meta[name="rc-ident"]');
if (m) { ReactiveChat('identify', JSON.parse(m.content)); }
단일 페이지 앱에서는 로그인 후 fetch로 서버에서 세 값을 가져옵니다.
서버의 검사 항목
exp는 정수(Unix 초)이며 미래이고 최대 24시간 후입니다(추가 허용 오차 5분).- 해시가
userId + "." + exp또는 이메일 포함 형식과 일치합니다. 해시의 대문자도 허용합니다. - 비교는 상수 시간입니다.
유효한 해시와 exp가 모두 있는 사용자는 확인되고 만료되지 않은 사용자입니다. 이때만 고객 데이터와 고객 포털을 사용할 수 있습니다.
exp 없는 이전 형식: userHash = HMAC_SHA256(secret, userId)도 확인된 것으로 받지만 만료되지 않은 신원으로는 간주하지 않습니다. 대화 기록과 상담원 화면 표시에는 충분하지만 고객 데이터에는 부족합니다. 기존 연동을 exp 포함 형식으로 전환합니다.
검증 실패 시
채팅은 계속되지만 익명으로 작동합니다. 위젯은 auth_failed 오류와 detail.reason의 이유를 받습니다.
- no_hash –
userHash가 없습니다. - no_secret – 프로젝트 시크릿을 설정하지 않았습니다.
- bad_exp –
exp가 유효한 초 단위 정수가 아닙니다(흔한 경우: 밀리초 전달). - expired –
exp가 과거입니다(페이지를 오래 열어 둠, 잘못된 서버 시계). - exp_too_far –
exp가 24시간보다 먼 미래입니다. - bad_hash – 해시 불일치: 잘못된 시크릿이나 순서,
userId를 문자열 대신 숫자로 서명, 다른 이메일 표기입니다.
개발자 도구의 네트워크 탭에서 WebSocket 연결의 error 프레임에 이유가 있습니다. init_ok.identity에 verified와 reason이 표시됩니다.
“확인된 방문자만 허용” 설정은 없습니다. 확인되지 않은 방문자는 항상 익명으로 채팅합니다.
시크릿 교체
- 새 시크릿 생성: 이전 것은 추가로 24시간 유효합니다. 그 안에 서버에 새 값을 입력합니다.
- 제거: 둘 다 즉시 무효화합니다. 새 시크릿을 설정하기 전까지 모든 사용자가 미확인으로 간주됩니다.
사용자 전환과 로그아웃
- 다른 사용자로
identify를 호출하면(예: SPA 계정 전환) 이전 사용자의 입력 임시 저장본을 분리합니다. - 전용 로그아웃 명령은 없습니다. 신원은 현재 페이지 조회에만 적용됩니다. 로그아웃 후에는
identify없는 페이지를 제공합니다. 대화와 방문자 ID는 브라우저에 유지됩니다. 공용 기기에 아무것도 남기지 않아야 하면 방문자가 위젯에서 데이터를 삭제할 수 있습니다.
맥락 전달
신원 외에도 페이지에서 현재 표시된 제품이나 장바구니 내용처럼 지금 일어나는 일을 어시스턴트와 팀에 알릴 수 있습니다. track을 사용합니다.
ReactiveChat('track', 'warenkorb', {
page_type: 'cart',
cart_value: 129.80,
cart_items: 3,
currency: 'EUR'
});
다음 필드만 수집하고 나머지는 폐기합니다.
- 페이지:
url,path,title,referrer,page_type,landing_path - 제품:
product_id,variant_id,category,price,currency,in_stock - 장바구니:
cart_value,cart_items - 캠페인:
utm_source,utm_medium,utm_campaign,gclid,msclkid,fbclid - 신호:
intent,intent_confidence,rage_score,scroll_pct,dwell_ms
위젯은 주소, 제목, 유입 페이지와 캠페인 매개변수를 페이지가 변경될 때마다 자동 기록합니다. 맥락은 동의가 있을 때만 전송합니다(개인정보 보호 연동). 이 필드에 개인정보를 넣지 않습니다. 개인정보에는 identify를 사용합니다.
모바일 앱
앱에서는 WebView 로드 후 JavaScript로 신원을 전달하며 주소로 전달하지 않습니다. 여기서도 해시는 앱이 아닌 서버에서 계산합니다. 모바일 앱을 참조합니다.
다음: 고객 데이터 엔드포인트 · SPA와 페이지 변경
최종 업데이트: