Skip to content
Hearl
Esc
↑↓navigate↵open⌘Jpreview
On this page

Notifications

Email digests and webhooks for conversations that need attention, including signature verification.

Hearl can tell your team as soon as a conversation needs attention. Set it up on the Notifications page. Only owners and admins can change these settings.

When notifications are sent

Hearl checks every 15 minutes. A conversation is included when:

  • its platform status is No reply yet or User followed up
  • its progress is still Not started
  • Hearl’s AI judged it about your product, with no uncertain answers
  • its priority is at or above the minimum you set: everything, Medium and up, or High only

Each conversation is notified once per status. If it was notified as No reply yet and the user later follows up, it’s notified again as User followed up.

Email

Turn on Send email and add recipients. Leave the list empty to email every workspace member. Any address works, such as a shared support inbox, up to 20 recipients.

Each check sends one digest that lists up to 10 conversations, with a link to the rest in the inbox. Choose English or Chinese under Language.

Webhook

Enter an Endpoint. It must be a public https URL. Hearl recognizes common chat bots by their address and sends the message format they expect:

Address Sent as
hooks.slack.com Slack message
feishu.cn, larksuite.com Feishu text message
qyapi.weixin.qq.com WeCom Markdown message
oapi.dingtalk.com DingTalk Markdown message
discord.com, discordapp.com Discord message
Any other URL Generic JSON (below)

The page shows which kind it detected. Send a test sends a sample, and the page shows the result of the last push. A request that takes longer than 8 seconds counts as a timeout.

Generic 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://hearloop.zephyrwhimsy.workers.dev/app?c=c_8f2a"
    }
  ],
  "sent_at": "2026-11-08T09:15:00.000Z"
}
Field Description
event conversations.needs_attention, or test for Send a test.
workspace The workspace id and name.
text A ready-to-post summary. Slack reads only this field.
conversations Every conversation in this push.
sent_at When the push was sent, in ISO 8601.

platform is x, reddit or hn. status, priority and category are stable codes in Chinese, so your code can match on them whatever language your workspace uses:

Field Code Meaning
status 待回复 No reply yet
status 用户有新回复 User followed up
priority 高 / 中 / 低 High / Medium / Low
category 故障 Bug
category 求助 Question
category 功能建议 Feature request
category 价格 Pricing
category 负面反馈 Criticism
category 正面反馈 Praise
category 购买咨询 Buying
category 其他 Other

Verify the signature

When you save a webhook endpoint, Hearl creates a Signing secret for it. You can show or rotate it on the Notifications page. The request carries this header:

X-Hearloop-Signature: sha256=<hex>

<hex> is the HMAC-SHA256 of the raw request body, keyed with your signing secret, as lowercase hex. Compute it over the exact bytes you received, before parsing the JSON, and compare in constant time.

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);
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

Return any 2xx status to acknowledge the push.

Was this page helpful?