Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card
Webhook
用于资源写入、同意更改和已完成文档的签名、重试的精简事件,支持 TypeScript、Dart 和 Python 中的签名验证。
当您的项目中发生某些事件时,Anpheros Platform 可以调用您的服务器:资源被写入、某人授予或撤销访问权限、文档已完成。事件是精简的:仅包含 ID、类型、版本 — 从不包含临床内容。如果需要,请通过 API 读取资源。每次传递都会进行签名;在信任之前请验证签名。
注册端点
curl -X POST $BASE/v1/webhooks -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url": "https://your.app/anpheros/hook", "events": ["resource.created", "consent.revoked"], "description": "prod"}'
响应包含 secret (whsec_…) 一次。请妥善存储,您需要它来验证签名。
events 接受 ["*"] 或以下任意一项:
| 类型 | 何时 | data |
|---|---|---|
resource.created / resource.updated / resource.deleted |
您的项目写入了 FHIR 资源(REST v1、FHIR API 或 bundle)—— 或另一个项目写入了您有权访问的记录(您创建了患者,或您持有有效的授权);然后 data.external 是 true,data.source 指明了写入者:author_type(patient / practitioner / device / import / ai)、organization(写入者代表的显示名称)、practitioner(已知时,来自目录的名称)、source_system |
resource_type、id、patient(您看到的 ID)、version、last_updated,以及对于外部写入 external、source |
consent.granted |
某人通过同意页面授予了您的应用访问权限(或受信任的应用链接了其记录) | grant_id、patient、application_id、scopes、purpose、expires_at |
consent.revoked |
某人(或平台)终止了授权 | 与上面相同,外加 revoked_at、reason |
document.finalized |
文档上传已完成并进行了校验和检查 | id、patient、content_type、size、sha256 |
proposal.created |
AI 代理建议写入您可以看到的记录;等待人员批准 | id、patient、action、resource_type |
ping |
您请求了测试交付 | endpoint |
端点是按项目和环境划分的:sk_test_ 密钥管理沙盒端点,
sk_live_ 密钥管理生产端点。URL 必须是公共 https:// 端点。注册(以及之后任何 url 的更改)会拒绝本地、私有、链路本地、元数据和其他保留地址——localhost、127.0.0.1、::1、10.0.0.0/8、192.168.0.0/16、fd00::/8 等——以及解析到这些地址的名称,在 400 invalid 字段 url 中;这在沙盒中也适用。每次交付都会再次检查地址,并且仅连接到它刚刚检查过的 IP 地址,因此一个后来指向私有网络的名称(DNS 重新绑定)将无法收到任何内容。要在开发过程中在您自己的机器上接收事件,请通过公共 HTTPS 隧道公开它,并注册隧道的地址。
每个项目和环境最多 10 个端点。
交付
POST https://your.app/anpheros/hook
Content-Type: application/json
User-Agent: Anpheros-Webhooks/1.0
Anpheros-Event: resource.created
Anpheros-Event-Id: evt_…
Anpheros-Delivery: whd_…
Anpheros-Signature: t=1789800000,v1=5f3a…
{"id":"evt_…","type":"resource.created","created_at":"2026-09-19T12:00:00.000Z","environment":"production",
"project":"proj_…","data":{"resource_type":"Observation","id":"…","patient":"…","version":1,"last_updated":"…"}}
在 10 秒内响应任何 2xx。其他任何情况(或超时)都视为失败,交付将以指数退避(backoff)重试,最多重试 24 小时(10 次),然后标记为 exhausted。
交付是至少一次(at-least-once)的,并且可能会乱序到达:使用 Anpheros-Event-Id 进行去重,使用 data.version 忽略过时的更新。
验证签名
v1 是使用您的密钥对 <t>.<raw body> 进行 HMAC-SHA256 计算的结果;如果 t 的时间戳早于 5 分钟,则拒绝。
import { verifyWebhookSignature } from '@anpheros/sdk';
app.post('/anpheros/hook', express.raw({ type: 'application/json' }), async (req, res) => {
const ok = await verifyWebhookSignature({ secret: process.env.ANPHEROS_WEBHOOK_SECRET!, header: req.header('anpheros-signature'), body: req.body });
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body.toString());
// … enqueue and return quickly
res.status(204).end();
});
import 'package:anpheros_sdk/anpheros_sdk.dart';
final ok = verifyWebhookSignature(secret: secret, header: request.headers['anpheros-signature'], body: rawBodyBytes);
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance=300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
操作
GET /v1/webhooks/{id}/deliveries?status=failed— 日志,最新在前,包含状态码和错误信息。POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver— 立即重新发送一次。POST /v1/webhooks/{id}/ping— 测试事件。POST /v1/webhooks/{id}/rotate— 新密钥(旧密钥立即失效;如果不能容忍中断,请先更新您的服务器:注册第二个端点,然后删除旧的)。PATCH /v1/webhooks/{id}和{"active": false}会暂停发送;当您删除端点时,待处理的发送将被丢弃。
两个 SDK 都在 webhooks 下暴露了这些功能(create、list、get、update、delete、rotateSecret、ping、deliveries、redeliver)。
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card