Webhooks
概述
Webhook 允许您在提取完成或失败时接收实时通知。DocMap 会在状态变化时立即向您的端点发送 POST 请求,而无需轮询 API。
关键信息:
- 每个账户最多 10 个活跃 Webhook
- 载荷使用 HMAC-SHA256 签名,确保您可以验证真实性
- 每个 Webhook 的投递超时为 10 秒
- 投递采用即发即弃模式 -- 一个 Webhook 的失败不会阻塞其他 Webhook
创建 Webhook
通过控制面板
- 在 DocMap 控制面板中导航到 Webhooks
- 点击 创建 Webhook
- 输入您的端点 URL(生产环境必须使用 HTTPS)
- 选择要订阅的事件
- 立即复制签名密钥 -- 之后不会再次显示
通过 API
bash
curl -X POST https://api.docmap.io/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/docmap",
"events": ["extraction.completed", "extraction.failed"]
}'typescript
const response = await fetch("https://api.docmap.io/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-app.com/webhooks/docmap",
events: ["extraction.completed", "extraction.failed"],
}),
});
const { data } = await response.json();
// Store this immediately -- it will never be shown again
console.log("Signing Secret:", data.secret);
console.log("Webhook ID:", data.webhook.id);python
import requests
response = requests.post(
"https://api.docmap.io/v1/webhooks",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"url": "https://your-app.com/webhooks/docmap",
"events": ["extraction.completed", "extraction.failed"],
},
)
data = response.json()["data"]
# Store this immediately -- it will never be shown again
print("Signing Secret:", data["secret"])
print("Webhook ID:", data["webhook"]["id"])WARNING
签名密钥仅在创建 Webhook 时返回一次。请将其安全存储在环境变量或密钥管理器中。
载荷格式
当事件发生时,DocMap 会向您的 Webhook URL 发送 POST 请求,包含以下结构:
json
{
"event": "extraction.completed",
"data": {
"id": "extract-abc123",
"userId": "uid_xyz789",
"templateId": "tpl_inv001",
"templateName": "Standard Invoice",
"fileName": "invoice-march.pdf",
"status": "completed",
"extractedData": {
"vendor_name": "Acme Corp",
"invoice_number": "INV-2024-003",
"total_amount": 4750.00
},
"error": null,
"variables": [...],
"source": "api",
"runId": null,
"processingTimeMs": 3842,
"createdAt": "2024-11-20T14:30:00.000Z"
},
"timestamp": "2024-11-20T14:30:04.000Z"
}请求头
每个 Webhook 请求包含以下头:
| 头 | 描述 |
|---|---|
Content-Type | application/json |
X-DocMap-Signature | HMAC-SHA256 签名:sha256={hex_digest} |
X-DocMap-Event | 事件名称(例如 extraction.completed) |
验证签名
始终验证 X-DocMap-Signature 头以确认请求来自 DocMap。签名是使用您的签名密钥作为密钥对原始请求体进行 HMAC-SHA256 哈希计算的结果。
typescript
import { createHmac, timingSafeEqual } from 'node:crypto'
function verifyWebhookSignature(
body: string,
signature: string,
secret: string,
): boolean {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex')
const received = signature.replace('sha256=', '')
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex'),
)
}
// Express example
app.post('/webhooks/docmap', (req, res) => {
const signature = req.headers['x-docmap-signature'] as string
const body = JSON.stringify(req.body)
if (!verifyWebhookSignature(body, signature, process.env.DOCMAP_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature')
}
const { event, data } = req.body
console.log(`Received ${event} for extraction ${data.id}`)
// Process the webhook...
res.status(200).send('OK')
})python
import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256,
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
# Flask example
from flask import Flask, request
app = Flask(__name__)
@app.post('/webhooks/docmap')
def handle_webhook():
signature = request.headers.get('X-DocMap-Signature', '')
body = request.get_data()
if not verify_webhook_signature(body, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
payload = request.get_json()
event = payload['event']
data = payload['data']
print(f"Received {event} for extraction {data['id']}")
# Process the webhook...
return 'OK', 200TIP
始终使用常量时间比较(如 timingSafeEqual 或 hmac.compare_digest)来防止计时攻击。
事件参考
| 事件 | 触发条件 |
|---|---|
extraction.completed | 提取成功完成。data.status 为 "completed",data.extractedData 包含结果。 |
extraction.failed | 提取失败。data.status 为 "failed",data.error 包含错误信息。 |
最佳实践
使用 HTTPS。 始终为 Webhook URL 使用 HTTPS 端点。HTTP 端点在生产环境中可能被拒绝。
验证签名。 在处理 Webhook 之前,始终验证
X-DocMap-Signature头。这可以确认请求来自 DocMap 且未被篡改。快速响应。 在几秒内返回 2xx 状态码。DocMap 最多等待 10 秒的响应。如果您的处理时间更长,请立即确认 Webhook 然后异步处理。
处理重复。 在极少数情况下,Webhook 可能会被投递多次。如有需要,请使用提取
id进行去重。安全存储密钥。 将签名密钥保存在环境变量或密钥管理器中。切勿在源代码中硬编码或提交到版本控制。
监控故障。 如果您的端点持续出现故障,请考虑检查服务器日志。DocMap 会记录投递失败但不会重试。
