웹훅: 서버로 이벤트 전달
reactive.chat의 서명된 웹훅과 REST 훅: 이벤트, 헤더, 본문, HMAC-SHA256 서명 검증, 재시도, 시간 제한, 비활성화와 PHP·Node·Python 검증 예제.
웹훅은 새 대화, 새 메시지, 팀 인계, 평가 등 채팅에서 일어나는 일을 서버에 알립니다. 두 경로가 있습니다.
- 서명된 웹훅 – 계정에서 설정하는 워크스페이스당 대상 주소 하나이며 모든 전송에 HMAC-SHA256으로 서명합니다. 자체 시스템에 적합한 경로입니다.
- REST 훅 – 자동화 도구(Zapier, Make, n8n)가 서버 API로 직접 구독하거나 해제하는 대상입니다.
이벤트
- conversation.created – 대화가 생성되었습니다.
channel에 채널이 있습니다(web,whatsapp,email등). - message.created – 방문자, 어시스턴트, 상담원 또는 시스템의 메시지가 저장되었습니다.
- handoff.requested – 대화를 사람에게 인계해야 하며
reason과target이 포함됩니다. - csat.submitted – 방문자가 평가했습니다.
score(1또는-1)와comment(개인정보 가림 처리)가 포함됩니다. - fehlerfall.erstellt – 웹사이트에서 새 JavaScript 오류 사례가 감지되었습니다(매뉴얼 참조).
웹사이트 검색(Smart Answer)의 대화는 채팅으로 표시된 후에만 이벤트를 발생시킵니다.
서명된 웹훅 설정
계정의 설정 → 연동 → 웹훅에서 설정합니다(연동 관리 권한 필요).
- URL –
https://, 최대 1000자, 공개 접근 가능 주소입니다. 워크스페이스당 하나입니다. - 이벤트 – 선택 목록이며 선택하지 않으면 모두입니다.
- 서명용 시크릿 – 페이지에 표시되는 64자리 16진수입니다. 재생성은 이전 시크릿을 즉시 무효화합니다. 수신 측에서 잠시 두 시크릿을 모두 허용하도록 전환을 계획합니다.
저장 시 테스트 호출은 보내지 않습니다. 활성화한 뒤 이벤트가 전달되기까지 약 15초가 걸릴 수 있습니다. 활성화 전 이벤트를 소급 전송하지 않습니다.
전송
POST /rc-webhook HTTP/1.1
Host: hooks.your-company.com
Content-Type: application/json; charset=utf-8
User-Agent: reactive.chat-webhook/1.0
X-RC-Event: csat.submitted
X-RC-Delivery: 01JAB3C4D5E6F7G8H9JKMNPQRS
X-RC-Timestamp: 1792503600
X-RC-Signature: sha256=3f0a1c...
{"id":"01JAB3C4D5E6F7G8H9JKMNPQRS","event":"csat.submitted","ts":1792503600,"site_key":"ABCDEFGHJKLMNPQRSTUVWX12","conversation_id":"01JAB2...","session_id":"s_...","visitor_id":"v_...","data":{"score":5,"comment":"Very helpful"}}
헤더:
- X-RC-Event – 이벤트 이름입니다.
- X-RC-Delivery – 전송 ID이며 재시도 시에도 동일합니다. 중복 감지에 사용합니다.
- X-RC-Timestamp – Unix 초입니다.
- X-RC-Signature –
sha256=+hex(HMAC_SHA256(secret, X-RC-Timestamp + "." + raw_body))입니다. 표시된 16진수 문자열 자체가 키입니다.
본문:
- id(문자열) –
X-RC-Delivery와 같습니다. - event(문자열).
- ts(정수) – Unix 초입니다.
- site_key, conversation_id, session_id, visitor_id(문자열 또는
null). - data(객체) – 이벤트별 데이터입니다.
fehlerfall.erstellt에는fall_id,project_id,signatur,meldung,datei,zeile,spalte,seite,browser,os,stack,erste_ms,url(계정 내 사례 링크)이 포함됩니다.message.created에는message와 그 안의id,seq,kind,direction,author_type,author_name(상담원과 어시스턴트만),via,body(서버에서 가림 처리),redacted,ts(밀리초)가 있습니다.handoff.requested에는reason과target,csat.submitted에는score와comment,conversation.created에는channel이 있습니다.
서명 검증
모든 처리 전에 검증합니다. 타임스탬프가 최대 300초 전인지, 원시 본문에 대한 서명인지 확인하고 상수 시간으로 비교합니다.
PHP:
<?php
$geheim = getenv('RC_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_RC_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_RC_SIGNATURE'] ?? '';
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }
$soll = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $geheim);
if (!hash_equals($soll, $sig)) { http_response_code(401); exit; }
$ereignis = json_decode($body, true);
// detect duplicates: remember $ereignis['id']
http_response_code(204);
Node.js(Express):
const crypto = require('crypto');
app.post('/rc-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-RC-Timestamp') || '';
const sig = req.get('X-RC-Signature') || '';
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const soll = 'sha256=' + crypto.createHmac('sha256', process.env.RC_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8')).digest('hex');
if (sig.length !== soll.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(soll))) {
return res.sendStatus(401);
}
const ereignis = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204);
verarbeiteSpaeter(ereignis); // respond quickly, work afterwards
});
Python(Flask):
import hmac, hashlib, os, time
from flask import request, abort
@app.post("/rc-webhook")
def rc_webhook():
ts = request.headers.get("X-RC-Timestamp", "")
sig = request.headers.get("X-RC-Signature", "")
body = request.get_data() # raw bytes
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
soll = "sha256=" + hmac.new(os.environ["RC_WEBHOOK_SECRET"].encode(),
ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(soll, sig):
abort(401)
return "", 204
응답, 재시도, 비활성화
먼저 빠르게 2xx로 응답하고 이후 처리합니다. 시간 제한은 연결 설정 4초, 전체 10초입니다. 리디렉션은 따르지 않습니다.
- 2xx – 전송 완료.
- 408, 429, 5xx, 시간 초과, 네트워크 오류 – 1초, 5초, 25초, 125초, 625초 후 재시도합니다(총 최대 6회). 이후 폐기합니다.
- 410 Gone – 연동이 비활성화됩니다. 더 이상 웹훅을 받지 않을 때만 410을 사용합니다.
- 그 밖의 4xx – 재시도하지 않습니다.
1시간보다 오래된 이벤트는 더 이상 전송하지 않습니다. 오류가 누적되면(60초 안에 5회) 대상 전송을 30초 동안 일시 중지하고 이후 작업을 재시도합니다. 채팅은 웹훅을 기다리지 않습니다.
전송 순서는 보장되지 않습니다. 필요하면 ts 또는 API 메시지의 seq로 정렬합니다.
REST 훅(Zapier, Make, n8n)
자동화 도구는 서버 API로 대상을 직접 구독합니다. 직접 사용할 수도 있습니다.
curl -X POST https://reactive.chat/v1/hooks \
-H "Authorization: Bearer rc_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"message.created","target_url":"https://hooks.your-company.com/rc","source":"n8n"}'
- POST /v1/hooks – 쓰기 권한이 있는 권한 범위가 필요합니다.
event(위 이벤트 중 하나),target_url(https://, 최대 500자, 공개 주소),source(zapier,make,n8n중 하나, 기본값zapier)입니다.id와 함께 201로 응답합니다. 같은 이벤트와 대상의 조합은 중복 생성하지 않습니다. 워크스페이스당 최대 200개 구독입니다. - GET /v1/hooks –
id,event,target_url,source,failures,created_at,last_ok_at의 목록입니다. - DELETE /v1/hooks/{id} – 구독 해제.
REST 훅 전송:
POST https://hooks.your-company.com/rc
Content-Type: application/json
User-Agent: reactive.chat-n8n/1.0
X-RC-Event: message.created
X-RC-Delivery: 01JAB3...
{"event":"message.created","delivery_id":"01JAB3...","data":{"name":"message.created","ts":1792503600123,"site_key":"...","conversation_id":"...","session_id":"...","visitor_id":"...","message":{"id":"...","seq":7,"kind":"text","direction":"in","author_type":"bot","author_name":"Assistant","via":null,"body":"…","redacted":false,"ts":1792503600120}}}
- 서명되지 않습니다. 추측하기 어려운 대상 주소를 사용하고
X-RC-Delivery로 중복을 확인합니다. 자체 시스템에는 서명된 웹훅이 더 적합합니다. data는 전체 이벤트이며workspace_id,delivery_id,attempt(시도 횟수)도 포함합니다. 여기서data.ts는 밀리초입니다.message.created의data에는message와 그 안의id,seq,kind,direction,author_type,author_name(상담원과 어시스턴트만),via,body(서버 측 가림 처리),redacted,ts가 있습니다.handoff.requested는reason과target,csat.submitted는score와comment,conversation.created는channel을 포함합니다.- 410은 이 구독 하나만 해제합니다. 폐기된 API 키의 훅은 더 이상 전송하지 않습니다.
- 현재는 워크스페이스의 연동에서 해당 연동(Zapier, Make 또는 n8n)을 켜야 전송됩니다.
제공되지 않는 기능
- 서버가 방문자 메시지를 대화에 넣는 수신 웹훅은 없습니다. 팀을 대신한 답변은 서버 API 또는 MCP로 보냅니다.
- 서명용 시크릿 갱신 시 전환 유예 기간은 없습니다.
- 대화 삭제 이벤트는 없습니다.
다음: 서버 API
최종 업데이트: