Webhook
概要
Webhookを使用すると、抽出が完了または失敗した際にリアルタイムの通知を受け取ることができます。APIをポーリングする代わりに、DocMapがステータスの変更後すぐにエンドポイントにPOSTリクエストを送信し、抽出結果を含めます。
基本情報:
- アカウントあたり最大 10個のアクティブなWebhook
- ペイロードはHMAC-SHA256で署名されるため、真正性を検証できます
- 配信タイムアウトはWebhookあたり 10秒
- 配信はファイアアンドフォーゲット方式 -- 1つのWebhookの失敗が他のWebhookをブロックすることはありません
Webhookの作成
ダッシュボードから
- DocMapダッシュボードの Webhook に移動します
- Webhookを作成 をクリックします
- エンドポイントURLを入力します(本番環境ではHTTPSが必須)
- 購読するイベントを選択します
- 署名シークレットをすぐにコピーしてください -- 再度表示されることはありません
APIから
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"]
}'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();
// すぐに保存してください -- 再度表示されることはありません
console.log("Signing Secret:", data.secret);
console.log("Webhook ID:", data.webhook.id);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"]
# すぐに保存してください -- 再度表示されることはありません
print("Signing Secret:", data["secret"])
print("Webhook ID:", data["webhook"]["id"])WARNING
署名シークレットはWebhookの作成時に 一度だけ 返されます。環境変数またはシークレットマネージャーに安全に保存してください。
ペイロード形式
イベントが発生すると、DocMapは以下の構造でWebhook URLにPOSTリクエストを送信します:
{
"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) |
署名の検証
リクエストがDocMapからのものであることを確認するために、X-DocMap-Signature ヘッダーを必ず検証してください。署名は、署名シークレットをキーとして使用した、生のリクエストボディのHMAC-SHA256ハッシュです。
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の例
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}`)
// Webhookを処理...
res.status(200).send('OK')
})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の例
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']}")
# 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は配信失敗をログに記録しますが、リトライは行いません。
