---
title: 通知
description: 対応が必要な会話のメールダイジェストと Webhook。署名の検証についても説明します。
---

Hearl は、会話に対応が必要になった時点でチームに知らせることができます。設定は**通知**ページで行います。この設定を変更できるのは、オーナーと管理者のみです。

## 通知が送られるタイミング [#when]

Hearl は15分ごとにチェックします。会話は、次の条件をすべて満たすと対象になります。

- プラットフォームのステータスが**未返信**または**ユーザーが再返信**である
- 進捗が**未着手**のままである
- Hearl の AI が製品に関するものと判定していて、不確かな回答がない
- 優先度が、設定した最低ラインと同じかそれ以上である。選べるのは、すべて、中以上、高のみのいずれかです

通知はステータスごとに、会話1件につき1回送られます。**未返信**として通知された会話で、その後ユーザーが再返信した場合は、**ユーザーが再返信**として改めて通知されます。

## メール [#email]

**メールを送信**をオンにして、受信者を追加します。リストを空にすると、ワークスペースの全メンバーにメールが届きます。共有のサポート用アドレスなど、どのアドレスでも指定でき、最大20件までです。

チェックのたびに、最大10件の会話を載せたダイジェストを1通送ります。残りの会話は、受信トレイへのリンクから確認できます。**言語**で、英語か中国語を選べます。

## Webhook [#webhook]

**エンドポイント**を入力します。公開された `https` の URL である必要があります。Hearl は、アドレスから一般的なチャットボットを判別し、それぞれが求める形式でメッセージを送ります。

| アドレス | 送信形式 |
| --- | --- |
| `hooks.slack.com` | Slack メッセージ |
| `feishu.cn`, `larksuite.com` | Feishu テキストメッセージ |
| `qyapi.weixin.qq.com` | WeCom Markdown メッセージ |
| `oapi.dingtalk.com` | DingTalk Markdown メッセージ |
| `discord.com`, `discordapp.com` | Discord メッセージ |
| その他の URL | 汎用 JSON（下記） |

ページには、検出された種類が表示されます。**テスト送信**でサンプルを送信でき、直近のプッシュの結果もページに表示されます。リクエストに8秒を超える時間がかかると、タイムアウトとして扱われます。

### 汎用 JSON [#json]

```json
{
  "event": "conversations.needs_attention",
  "workspace": { "id": "org_123", "name": "Acme" },
  "text": "*2 conversations need attention* · Acme\n• [reddit] Sync stopped working after 2.3 — No reply yet <https://…>",
  "conversations": [
    {
      "id": "c_8f2a",
      "platform": "reddit",
      "product": "Acme",
      "title": "Sync stopped working after 2.3",
      "author": "some_user",
      "url": "https://www.reddit.com/r/…",
      "status": "待回复",
      "priority": "高",
      "category": "故障",
      "inbox_url": "https://hearl.org/app?c=c_8f2a"
    }
  ],
  "sent_at": "2026-11-08T09:15:00.000Z"
}
```

| フィールド | 説明 |
| --- | --- |
| `event` | `conversations.needs_attention`。**テスト送信**の場合は `test`。 |
| `workspace` | ワークスペースの `id` と `name`。 |
| `text` | そのまま投稿できる要約。Slack はこのフィールドだけを読み取ります。 |
| `conversations` | このプッシュに含まれるすべての会話。 |
| `sent_at` | プッシュを送信した日時（ISO 8601 形式）。 |

`platform` は `x`、`reddit`、`hn` のいずれかです。`status`、`priority`、`category` は中国語の固定コードです。ワークスペースの言語にかかわらず、コード側でこの値を使って判別できます。

| フィールド | コード | 意味 |
| --- | --- | --- |
| `status` | `待回复` | 未返信 |
| `status` | `用户有新回复` | ユーザーが再返信 |
| `priority` | `高` / `中` / `低` | 高 / 中 / 低 |
| `category` | `故障` | 不具合 |
| `category` | `求助` | 質問 |
| `category` | `功能建议` | 機能要望 |
| `category` | `价格` | 料金 |
| `category` | `负面反馈` | 批判 |
| `category` | `正面反馈` | 称賛 |
| `category` | `购买咨询` | 購入検討 |
| `category` | `其他` | その他 |

### 署名を検証する [#signature]

Webhook のエンドポイントを保存すると、Hearl はそのエンドポイント用の**署名シークレット**を作成します。通知ページで、表示またはローテーションできます。リクエストには、次のヘッダーが付きます。

```txt
X-Hearloop-Signature: sha256=<hex>
```

`<hex>` は、リクエストボディ（生データ）の HMAC-SHA256 を、署名シークレットをキーとして計算し、小文字の16進数で表したものです。受信したバイト列をそのまま使い、JSON を解析する前に計算してください。比較は定数時間で行います。

```js Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.HEARL_WEBHOOK_SECRET;

app.post("/hearl", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + createHmac("sha256", secret).update(req.body).digest("hex");
  const received = req.get("x-hearloop-signature") ?? "";
  const ok =
    received.length === expected.length &&
    timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const payload = JSON.parse(req.body.toString("utf8"));
  console.log(payload.event, payload.conversations.length);
  res.status(200).end();
});

app.listen(3000);
```

```python Python
import hashlib
import hmac
import json
import os

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["HEARL_WEBHOOK_SECRET"].encode()


@app.post("/hearl")
def hearl():
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    received = request.headers.get("X-Hearloop-Signature", "")
    if not hmac.compare_digest(received, expected):
        abort(401)

    payload = json.loads(body)
    print(payload["event"], len(payload["conversations"]))
    return "", 200
```

プッシュを受け取ったことを知らせるには、任意の `2xx` ステータスを返してください。
