Webhook
Potatoから7種類のアノテーションイベントについてHMAC署名付きのHTTP webhook通知を送ります。指数バックオフによる再送、管理画面での監視、PythonとNodeでの検証に対応しています。
v2.4.0の新機能
webhookを使うと、アノテーションのイベントが起きたときに、ポーリングなしでPotatoから外部システムへ通知できます。データパイプラインとの接続、アラートの発火、ダッシュボードの更新、後続処理の自動起動などに使えます。
概要
対応しているイベントが発生するたびに、Potatoは設定したエンドポイントへHTTP POSTリクエストを送ります。ペイロードはJSONで、HMAC-SHA256で署名されるため、自分のPotatoインスタンスから送られたものかを検証できます。
webhookの配信は完全に非ブロッキングで、webhookの送信中にアノテーションのリクエストが待たされることはありません。配信に失敗した場合は指数バックオフで再送されます。
設定
YAMLの設定にwebhooksセクションを追加します。
webhooks:
enabled: true
endpoints:
- name: "my-pipeline"
url: "https://your-system.example.com/potato-events"
secret: "your-signing-secret" # optional but recommended
events:
- annotation.created
- item.fully_annotated
- task.completed
active: true
timeout: 10複数のエンドポイント
エンドポイントは複数設定でき、それぞれ購読するイベントを変えられます。
webhooks:
enabled: true
endpoints:
- name: "data-pipeline"
url: "https://pipeline.example.com/annotations"
secret: ${WEBHOOK_SECRET_1}
events:
- annotation.created
- item.fully_annotated
- name: "slack-alerts"
url: "https://hooks.slack.com/services/..."
events:
- task.completed
- quality.attention_check_failed
- name: "catch-all"
url: "https://logging.example.com/potato"
events:
- "*" # subscribe to all eventsイベントの種類
| イベント | 発生するタイミング |
|---|---|
annotation.created | アノテーターがインスタンスにラベルを送信したとき |
annotation.updated | アノテーターが送信済みのラベルを修正したとき |
item.fully_annotated | インスタンスが必要な重複アノテーション数に達したとき |
task.completed | タスク内のすべてのインスタンスのアノテーションが完了したとき |
user.phase_completed | アノテーターがフェーズを完了したとき(ソロモードのワークフロー) |
quality.attention_check_failed | アノテーターが注意チェックに失敗したとき |
webhook.test | テスト用に管理APIから手動で発火させたとき |
"*"を指定すると、現在および将来のすべてのイベント種別を購読します。
ペイロードの形式
すべてのイベントは共通のエンベロープを持ちます。
{
"event_id": "evt_01HXYZ...",
"event_type": "annotation.created",
"timestamp": "2026-03-17T14:23:01Z",
"task_name": "sentiment-study",
"data": {
...
}
}annotation.createdのペイロード
{
"event_type": "annotation.created",
"data": {
"annotator_id": "user123",
"instance_id": "doc_042",
"annotation": {
"sentiment": "positive",
"confidence": "high"
},
"submitted_at": "2026-03-17T14:23:01Z"
}
}item.fully_annotatedのペイロード
{
"event_type": "item.fully_annotated",
"data": {
"instance_id": "doc_042",
"annotator_count": 3,
"annotations": [
{"annotator_id": "user1", "sentiment": "positive"},
{"annotator_id": "user2", "sentiment": "positive"},
{"annotator_id": "user3", "sentiment": "neutral"}
]
}
}task.completedのペイロード
{
"event_type": "task.completed",
"data": {
"task_name": "sentiment-study",
"total_instances": 500,
"total_annotations": 1500,
"completed_at": "2026-03-17T15:00:00Z"
}
}署名の検証
secretを設定すると、PotatoはStandard Webhooks(HMAC-SHA256)を使って各リクエストに署名します。3つのヘッダーが付きます。
| ヘッダー | 値 |
|---|---|
webhook-id | 配信ごとの一意なID |
webhook-timestamp | 配信時のUnixタイムスタンプ |
webhook-signature | HMAC-SHA256の署名 |
Pythonでの検証
import hmac
import hashlib
import time
def verify_webhook(payload_bytes: bytes, headers: dict, secret: str) -> bool:
webhook_id = headers.get("webhook-id", "")
timestamp = headers.get("webhook-timestamp", "")
signature = headers.get("webhook-signature", "")
# Reject stale requests (older than 5 minutes)
if abs(time.time() - int(timestamp)) > 300:
return False
signed_content = f"{webhook_id}.{timestamp}.{payload_bytes.decode()}"
expected = hmac.new(
secret.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"v1,{expected}", signature)Node.jsでの検証
const crypto = require('crypto');
function verifyWebhook(payload, headers, secret) {
const webhookId = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signature = headers['webhook-signature'];
// Reject stale requests
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false;
}
const signedContent = `${webhookId}.${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedContent)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(`v1,${expected}`),
Buffer.from(signature)
);
}再送の挙動
配信に失敗した場合(2xx以外の応答またはタイムアウト)は自動的に再送されます。
| 試行 | 待ち時間 |
|---|---|
| 1(初回) | 即時 |
| 2 | 5秒 |
| 3 | 30秒 |
| 4 | 5分 |
| 5 | 30分 |
| 6 | 1時間 |
6回失敗すると、その配信は恒久的な失敗として記録されます。再送キューは{output_dir}/.webhooks/webhook_retries.dbのSQLiteに永続化されるため、サーバーを再起動しても保留中の再送は残ります。
監視とテスト
管理API
webhookの状態と統計を確認します。
# List all webhooks and their delivery statistics
curl -H "X-API-Key: $ADMIN_API_KEY" \
http://localhost:8000/admin/api/webhooks応答:
{
"endpoints": [
{
"name": "my-pipeline",
"url": "https://...",
"events": ["annotation.created"],
"active": true,
"stats": {
"total_emitted": 1240,
"total_failed": 3,
"pending_retries": 0,
"last_success": "2026-03-17T14:23:01Z"
}
}
]
}テスト用webhookの送信
curl -X POST -H "X-API-Key: $ADMIN_API_KEY" \
http://localhost:8000/admin/api/webhooks/test \
-H "Content-Type: application/json" \
-d '{"endpoint_name": "my-pipeline"}'これで、指定した名前のエンドポイントへwebhook.testイベントが即座に送られます。
設定の完全リファレンス
webhooks:
enabled: true
endpoints:
- name: string # unique name for this endpoint
url: string # HTTPS URL to POST to
secret: string # optional HMAC secret for signature verification
events: # list of event types, or ["*"] for all
- annotation.created
active: true # set false to disable without removing
timeout: 10 # request timeout in seconds (default: 10)
max_retries: 6 # max retry attempts (default: 6)参考資料
- 管理ダッシュボード — アノテーションの進捗の監視とユーザー管理
- 品質管理 —
quality.attention_check_failedを発火させる注意チェックの設定 - ソロモード —
user.phase_completedを発火させるフェーズ単位のワークフロー - エクスポート形式 — Potatoからアノテーションを取り出す別の方法
実装の詳細については、ソースドキュメントを参照してください。