用 Webhook 接收事件
在 Comu APP 中配置一个 HTTPS endpoint,接收转录完成通知, Comu 会在事件发生时往这个 URL POST 一份 JSON。 本页覆盖 App 内配置、事件清单、payload 结构、验签与投递行为。
在 Comu APP 配置 Webhook
配置路径:Comu APP → 我的页面 → Developers → Webhook。 进入 Webhook 页面后,按表单完成配置即可。
登录 Comu APP,进入我的页面,点击 Developers。
在 Developers 页面选择 Webhook,
新建或编辑 endpoint,填写 HTTPS 接收 URL。App 自动订阅 content.completed。
将页面展示的 signingSecret 写入你的密钥管理工具,
接收端用它验证 X-Kirby-Signature。
接收:事件发生时,Comu 异步往你这个 URL POST 一份 JSON envelope,
带上 X-Kirby-Signature / X-Kirby-Event-Type 等 header。
验签 + 处理:用 signing secret 重算 HMAC-SHA256 比对, 确认是 Comu 发的且未被篡改;拒绝可疑请求,正常请求按业务消费即可。
Webhook 配置项
| 配置项 | 说明 |
|---|---|
| Endpoint URL | 你的 HTTPS 接收地址。Comu 会向这个 URL POST JSON payload。 |
| 订阅事件 | App 自动订阅 content.completed,无需选择其他事件。 |
| 描述 | 用于区分多个 endpoint,例如生产环境、测试环境或某个集成系统。 |
signingSecret |
页面展示的签名密钥,接收端验签时使用;请按密钥级别保管,不要写入代码仓库或日志。 |
事件清单(1 类)
当前仅提供「转录完成」通知,事件码为 content.completed。最新转录与总结任务都成功后推送,不等待洞察或待办生成。
content.completed转录完成
{
"id": "evt_example_001",
"type": "content.completed",
"api_version": "2026-04-24",
"created": 1789380000,
"payload_style": "fat",
"user_id": "u_123",
"data": {
"object": {
"object": "note",
"note_id": "note_123",
"transcript_task_id": "task_transcript_123",
"summary_task_id": "task_summary_123",
"completed_at": "2026-09-14T10:00:00Z"
}
}
}
事件码会出现在 payload 的 type 和请求头 X-Kirby-Event-Type 中。收到通知后,可用 note_id 通过查询接口读取转录与总结正文。
签名验证(防伪造 + 防重放)
Comu 在每次推送的请求头里写入 X-Kirby-Signature,
格式与 Stripe 一致:t={unix秒},v1={hex(HMAC-SHA256)}。
接收端用同一把 signing secret 重算并比对即可。
X-Kirby-Signature 解出 t(时间戳)和 v1(hex 签名)。|now - t| ≤ 300s,超出直接拒绝(防重放)。t + "." + rawBody 用 signing secret 算 HMAC-SHA256 hex,
用 常量时间比对,防时序侧信道。
展开 Java 实现(零第三方依赖,可直接拷到你的服务里)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Locale;
public final class ComuWebhookVerifier {
private static final long TOLERANCE_SECONDS = 300L;
private static final String ALGO = "HmacSHA256";
/**
* 验证 Comu webhook 请求。
*
* @param secret 在 Comu APP 的 Webhook 页面拿到的 whsec_xxx 明文
* @param header 请求头 X-Kirby-Signature 的完整值,形如 t=...,v1=...
* @param rawBody 原始请求体字符串(必须是未反序列化的 raw bytes 字符串)
*/
public static boolean verify(String secret, String header, String rawBody) {
if (secret == null || header == null || rawBody == null) return false;
long t = -1L;
String v1 = null;
for (String part : header.split(",")) {
int eq = part.indexOf('=');
if (eq <= 0) continue;
String k = part.substring(0, eq).trim();
String v = part.substring(eq + 1).trim();
if ("t".equals(k)) {
try { t = Long.parseLong(v); } catch (NumberFormatException ignore) { return false; }
} else if ("v1".equals(k)) {
v1 = v;
}
}
if (t < 0 || v1 == null || v1.isEmpty()) return false;
long now = System.currentTimeMillis() / 1000L;
if (Math.abs(now - t) > TOLERANCE_SECONDS) return false;
String expected = hmacSha256Hex(secret, t + "." + rawBody);
byte[] a = expected.getBytes(StandardCharsets.US_ASCII);
byte[] b = v1.getBytes(StandardCharsets.US_ASCII);
return MessageDigest.isEqual(a, b);
}
private static String hmacSha256Hex(String secret, String payload) {
try {
Mac mac = Mac.getInstance(ALGO);
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), ALGO));
byte[] raw = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(raw.length * 2);
for (byte by : raw) sb.append(String.format(Locale.ROOT, "%02x", by));
return sb.toString();
} catch (Exception e) {
throw new IllegalStateException("HMAC sign failed", e);
}
}
}
关键点:
① 一定用原始字节算签名,不要先 JSON 反序列化再 toString,否则空白字符差异会导致签名永远不匹配;
② 用 MessageDigest.isEqual 而不是 String.equals 做比对,避免时序侧信道;
③ 任意语言只要有 HMAC-SHA256 + 常量时间比对即可,不限于 Java。
投递行为
事件不会阻塞业务请求 —— 业务侧产生事件后丢消息队列,由独立 worker 异步推送到 endpoint。
非 2xx / 超时会按指数退避重试若干次;持续失败会进入 DLQ,可在管理后台查看投递历史。
重试可能导致同一事件投递多次,接收端按 payload 中的事件 ID 做幂等去重, 避免一个事件被业务消费两次。
上线前自检
endpoint URL 必须是 HTTPS;纯 HTTP 在公网上既会泄漏 payload 也会让中间人替换响应。
没有验签的 webhook 接收端等于一个公网开放的 RPC,任何人都能伪造事件。没有例外。
只订阅业务真正需要的事件类型,减少不必要的 QPS 与噪声日志,方便定位异常。
和 API Key 同等敏感。从 Comu APP 获取后立刻写进密钥管理工具,不要写入代码仓库或日志。
用 payload 内的事件 ID 在你侧建唯一索引; 重试 / 网络抖动导致的重复投递就不会触发二次副作用。
收到 webhook 后立即响应 200,长任务异步处理;超时会被判失败并触发重试。