/v1/account/webhook-endpoints

account/webhook-endpoints

Webhook:监控将新的 Signals 发送到哪里,以及如何验证。

费用和限制

费用和限制
项目 积分
管理 Webhook 端点和投递 免费

限制:每分钟 30 次请求。 创建、更新和删除:每分钟 10 次。 检查、测试和新密钥:每分钟 6 次。

管理员令牌或 API 密钥

端点

方法
方法 路径 用途 访问
GET /v1/account/webhook-endpoints 所有 Webhook 端点,不含已归档的。 API 密钥或管理员令牌
POST /v1/account/webhook-endpoints 创建 Webhook 端点。 管理员令牌
POST /v1/account/webhook-endpoints/{id}/verify 验证。 API 密钥或管理员令牌
POST /v1/account/webhook-endpoints/{id}/test 发送测试消息。 API 密钥或管理员令牌
POST /v1/account/webhook-endpoints/{id}/rotate-secret 新的签名密钥。 管理员令牌
POST /v1/account/webhook-endpoints/{id}/enable 启用。 管理员令牌
POST /v1/account/webhook-endpoints/{id}/disable 停用。 管理员令牌
DELETE /v1/account/webhook-endpoints/{id} 归档。 管理员令牌
GET /v1/account/webhook-deliveries 最近 50 次投递。 API 密钥或管理员令牌
POST /v1/account/webhook-deliveries/{id}/retry 重新发送失败的投递。 API 密钥或管理员令牌
GET /v1/account/webhook-events 最近 50 个事件。 API 密钥或管理员令牌

创建 Webhook 端点

curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CRM-Sync",
    "url": "https://hooks.example.com/handelsregister",
    "headers": {
        "X-Receiver-Token": "YOUR_RECEIVER_TOKEN"
    }
}'
参数
名称 类型 说明 示例
Idempotency-KeyHeader 字符串必填 每个操作一个唯一值,最好是 UUID。见 Idempotency-Key。限制: 1 至 128 个字符 req-20260802-01
nameBody 字符串必填 供你自己参考的名称。限制: 1 至 120 个字符 CRM-Sync
urlBody 字符串 (uri)必填 你服务器的地址:可公开访问,端口 443 上的 https,使用主机名而非 IP 地址。限制: 1 至 2,048 个字符 https://hooks.example.com/handelsregister
headersBody 对象可选 随每条消息发送的自定义请求头,例如供你服务器使用的令牌。限制: 最多 10 项 {"X-Receiver-Token":"YOUR_RECEIVER_TOKEN"}
1{2  "endpoint": {3    "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",4    "name": "CRM-Sync",5    "url": "hooks.example.com/…",6    "status": "pending_verification",7    "verified_at": null,8    "header_names": [9      "X-Receiver-Token"10    ],11    "consecutive_failures": 0,12    "created_at": "2026-11-01T08:28:01+00:00"13  },14  "signing_secret": "whsec_…",15  "meta": {16    "message": "Endpoint created. Store the signing secret now — it is shown only once.",17    "request_credit_cost": 0,18    "credits_remaining": 14519  }20}
201 · 示例响应:一个新的 Webhook 端点,此处密钥已缩短

验证

1{2  "id": "msg_01m6b3jsqq7ntwzkdj099ayraz",3  "type": "endpoint.verification",4  "timestamp": "2026-11-01T08:28:26.743Z",5  "schema_version": 1,6  "data": {7    "challenge": "189fb34b2af819bb13cb47d9806dbf30"8  }9}
验证时你的服务器收到的消息
curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/verify" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
1{2  "verified": true,3  "endpoint": {4    "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",5    "name": "CRM-Sync",6    "url": "hooks.example.com/…",7    "status": "active",8    "verified_at": "2026-11-01T08:28:26+00:00",9    "header_names": [10      "X-Receiver-Token"11    ],12    "consecutive_failures": 0,13    "created_at": "2026-11-01T08:28:01+00:00"14  },15  "meta": {16    "request_credit_cost": 0,17    "credits_remaining": 14518  }19}
200 · 验证成功时的示例响应
1{2  "delivery": {3    "id": "del_01m6b3jsyrnc3sz1mt31dq6107",4    "message_id": "msg_01m6b3jsynxzhvagm7n2nq7mpv",5    "event_id": "evt_01m6b3jsynxzhvagm7n2nq7mpt",6    "event_type": "endpoint.test",7    "endpoint_id": "wep_01m6b3j1g0cdz98hw8zdefbf52",8    "status": "succeeded",9    "attempts": 1,10    "last_status_code": 200,11    "last_error_category": null,12    "next_attempt_at": "2026-11-01T08:28:26+00:00",13    "succeeded_at": "2026-11-01T08:28:26+00:00",14    "created_at": "2026-11-01T08:28:26+00:00"15  },16  "meta": {17    "request_credit_cost": 0,18    "credits_remaining": 14519  }20}
200 · 测试消息的示例响应

验证签名

请求头
请求头 含义
webhook-id 消息的 ID(msg_…)。重试时保持不变。用它识别重复。
webhook-timestamp 本次投递尝试的时间,Unix 秒。
webhook-signature 一个或多个 v1,<Base64> 形式的签名,以空格分隔。
import base64, hashlib, hmac, time

def verify_webhook(secret: str, headers: dict, body: bytes) -> bool:
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    # 拒绝超过五分钟的消息
    if abs(time.time() - int(timestamp)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    # 密钥轮换期间,请求头带有两个签名
    for part in headers["webhook-signature"].split(" "):
        version, _, signature = part.partition(",")
        if version == "v1" and hmac.compare_digest(signature, expected):
            return True
    return False

负载

字段
字段 含义
id 消息的 ID,与请求头 webhook-id 相同。
event_id 事件的 ID(evt_…)。
type 事件类型,见下文。
timestamp 我们检测到事件的时间,UTC。
schema_version 结构的版本,目前为 1。
data 事件数据。
事件类型
事件类型 含义
organization.signal.detected 某个监控发现了新信号。data.signal 的结构与 signals 中的信号相同,另有 data.monitor,以及 data.links.signal 下指向 API 中该信号的链接。
organization.signal.sample 来自公司真实历史的示例,在“账户 › 监控”下发送。结构与上述相同,另加 data.sample: true。
endpoint.test 通过 /test 发送的测试消息。
endpoint.verification 验证时的质询。
1{2  "id": "msg_01m65pqg9x0176cteqvtxw68sa",3  "event_id": "evt_01m65pqg9sx7mwmw0edc1mfbbe",4  "type": "organization.signal.sample",5  "timestamp": "2026-10-30T06:07:37.000Z",6  "schema_version": 1,7  "data": {8    "monitor": {9      "id": "mon_01m65pn3vm11sjjp1j076cbvec",10      "type": "organization.signals"11    },12    "sample": true,13    "signal": {14      "event": {15        "id": "36c7c8fddf99bb0299d6815bcaa01013",16        "topic": "TRANSFORMATIONS",17        "topic_name": {18          "de": "Umwandlungen & M&A",19          "en": "Transformations & M&A"20        },21        "occurred_on": "2022-12-22",22        "announced_on": "2023-08-10",23        "date_basis": "SOURCE_EVENT"24      },25      "organization": {26        "type": "ORGANIZATION",27        "entity_id": "94eb910fef13f94585c48c263fa446d4",28        "current_profile": {29          "name": "Hetzner Online GmbH",30          "registration": {31            "register_type": "HRB",32            "register_number": "6089",33            "court": "Ansbach",34            "register_locality": "Ansbach",35            "registration_authority_id": "D3201",36            "register_entity_name": "Ansbach HRB 6089"37          },38          "legal_form": {39            "name": {40              "de": "Gesellschaft mit beschränkter Haftung"41            },42            "short": {43              "de": "GmbH"44            }45          },46          "seat": {47            "city": "Gunzenhausen"48          },49          "status": {50            "code": "ACTIVE"51          }52        }53      },54      "parties": {55        "counterparty": {56          "entity": {57            "type": "ORGANIZATION",58            "entity_id": "dd4d2d6854b2dbd1ffce74e669f3852e",59            "current_profile": {60              "name": "Headtrip GmbH",61              "registration": {62                "register_type": "HRB",63                "register_number": "33432",64                "court": "Nürnberg",65                "register_locality": "Nuremberg",66                "registration_authority_id": "D3310",67                "register_entity_name": "Nürnberg HRB 33432"68              },69              "legal_form": {70                "name": {71                  "de": "Gesellschaft mit beschränkter Haftung"72                },73                "short": {74                  "de": "GmbH"75                }76              },77              "seat": {78                "city": "Nürnberg"79              },80              "status": {81                "code": "INACTIVE"82              }83            }84          },85          "relationship": "TRANSFERRING_COMPANY"86        }87      },88      "register_entry": {89        "entry_number": "3",90        "entry_date": "2023-08-10",91        "description_text": "Verschmelzung zur Aufnahme der Headtrip GmbH",92        "context": "PRIMARY",93        "flags": []94      },95      "source": {96        "kind": "COMMERCIAL_REGISTER"97      },98      "details": {99        "schema": "TRANSFORMATION",100        "kind": "MERGER",101        "specifics": {102          "merger_target": "ABSORPTION"103        },104        "direction": "INCOMING",105        "is_cross_border": false,106        "resolution_date": "2022-12-22",107        "agreement_date": "2022-12-22"108      }109    },110    "links": {111      "signal": "https://handelsregister.ai/api/v1/signals/36c7c8fddf99bb0299d6815bcaa01013"112    }113  }114}
Hetzner Online GmbH(Ansbach HRB 6089)的示例消息:Headtrip GmbH(Nürnberg HRB 33432)的合并,作为历史示例发送

投递和重试

我们保证每条消息至少投递一次,但不保证固定顺序。请按 webhook-id 识别重复。请快速响应,之后再处理消息。
投递的状态
状态 含义
withheld 等待该次检查完成计费。
pending 等待发送。
in_flight 正在发送。
retry_wait 等待下一次尝试。
succeeded 已投递。
exhausted 所有尝试均失败。
blocked_endpoint 该 Webhook 端点目前无法接收任何内容。
cancelled 已取消,例如因为消息已过期。

启用、停用、归档

curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/disable" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

错误

错误
状态 原因 你可以怎么做
400 api_key 位于 URL 中。 在请求头 x-api-key 中发送密钥。
400 该 URL 不允许作为目标、某个请求头不被允许、已达到最大数量,或 Webhook 端点未处于有效状态。meta.message 给出原因。 修正该值,或先进行验证。
400 Idempotency-Key 格式无效。 修正格式,例如使用 UUID。
401 没有 API 密钥或令牌,或其无效。 在请求头 x-api-key 中发送密钥。
403 Bearer 令牌缺少权限 account:read。 使用 API 密钥或带 account:read 的令牌。
403 你账户的电子邮件地址尚未确认,或账户已被锁定。 打开确认邮件中的链接。如果账户被锁定,客服可以提供帮助。
403 此调用需要管理员令牌。API 密钥不够,其他令牌也不行。 用 Authorization: Bearer 加管理员令牌发送。
403 Bearer 令牌缺少权限 monitoring:manage。 使用 API 密钥或管理员令牌。
404 没有此 ID 的 Webhook 端点或投递。 检查 ID。
409 该 Idempotency-Key 已用于不同的数据、已超过 24 小时,或仍在处理中。 使用新键或稍等片刻。
422 某个值缺失或无效,例如 name 或 url。errors 指出该字段。 修正该值,并用新键重试。
422 验证失败。 检查质询请求头和 2xx 状态,然后用新键重试。
428 缺少请求头 Idempotency-Key。 发送该请求头。
429 请求过多。 按 Retry-After 等待后重试。
503 验证、测试和重新发送暂时不可用。 稍后重试。
1{2  "error": "invalid_request",3  "meta": {4    "message": "The endpoint is not active.",5    "request_credit_cost": 0,6    "credits_remaining": 1457  }8}
400 · 尚未验证的 Webhook 端点无法接收测试消息