Webhook:サーバーへのイベント通知
reactive.chatの署名付きWebhookとREST hooks:イベント、ヘッダー、本文、HMAC-SHA256による署名検証、再試行、タイムアウト、無効化、PHP・Node・Pythonの検証例。
Webhookは、新しい会話、メッセージ、チームへの引き継ぎ、評価など、チャットで起きたことをサーバーに通知します。2つの経路があります。
- 署名付きWebhook – ワークスペースごとに1つの送信先アドレスをアカウントで設定し、各配信にHMAC-SHA256で署名します。独自システムに適した経路です。
- REST hooks – 自動化ツール(Zapier、Make、n8n)がサーバーAPI経由で、自分で送信先を登録・解除します。
イベント
- conversation.created – 会話が作成されました。
channelにチャネルを含みます(web、whatsapp、emailなど)。 - message.created – 訪問者、アシスタント、オペレーター、システムのいずれかのメッセージが保存されました。
- handoff.requested – 人間への引き継ぎが必要です。
reasonとtargetを含みます。 - csat.submitted – 訪問者が評価しました。
score(1または-1)とcomment(個人データはマスク済み)を含みます。 - fehlerfall.erstellt – Webサイトで新しいJavaScriptエラー事例が検出されました(マニュアル参照)。
サイト内検索(Smart Answer)の会話は、チャットとして見えるようになってからイベントを発生させます。
署名付きWebhookを設定する
アカウントの設定 → 連携 → Webhookで設定します(必要な権限:連携の管理)。
- URL –
https://, 最大1,000文字で、公開アクセスできるもの。ワークスペースごとに1アドレスです。 - イベント – 選択します。何も選ばなければすべてです。
- 署名用シークレット – ページに表示します(16進数64文字)。再生成すると、以前のものは即座に無効になります。受信側が短時間だけ両方を受け付けるよう、切り替えを計画してください。
保存時にテスト呼び出しは送りません。有効化してからイベントが流れ始めるまで、最大約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では、id、seq、kind、direction、author_type、author_name(オペレーターとアシスタントのみ)、via、body(サーバーでマスク)、redacted、ts(ミリ秒)を持つmessage。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 – 連携を無効化します。以降のWebhookを本当に不要とする場合だけ、410を使ってください。
- その他の4xx – 再試行しません。
1時間を超えて古いイベントは配信しません。エラーが集中した場合(60秒で5回)、送信先への配信を30秒停止し、その後ジョブを再試行します。チャットがWebhookを待つことはありません。
配信順序は保証しません。必要に応じて、tsまたはAPIのメッセージのseqで並べ替えてください。
REST hooks(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 hookの配信:
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で重複を確認してください。独自システムには署名付きWebhookの方が適しています。 dataは完全なイベントで、さらにworkspace_id、delivery_id、attempt(試行回数)を含みます。ここでのdata.tsはミリ秒です。message.createdのdataには、id、seq、kind、direction、author_type、author_name(オペレーターとアシスタントのみ)、via、body(サーバー側でマスク)、redacted、tsを持つmessageを含みます。handoff.requestedにはreasonとtarget、csat.submittedにはscoreとcomment、conversation.createdにはchannelを含みます。- 410は、その1件の登録だけを解除します。失効したAPIキーのフックには配信しません。
- 現時点では、ワークスペースの連携で該当する連携(Zapier、Make、n8n)を有効にしていることが、配信の条件です。
提供していないもの
- サーバーから訪問者のメッセージを会話に入れる、受信用Webhookはありません。チームの代理での返信には、サーバーAPIまたはMCPを使います。
- 署名用シークレットの更新時に、移行期間はありません。
- 会話を削除したときのイベントはありません。
次:サーバーAPI
最終更新: