Webhooks
GET /batches/{batch_name}をポーリングして完了を待つ代わりに、コールバックURLを一度登録すれば、バッチが完了した瞬間にHTTP POSTが届きます。3つの署名ヘッダーとid.timestamp.bodyの署名コンテンツ構造は、Standard Webhooksオープン仕様に従っています — OpenAIのwebhooksでも採用されているのと同じアプローチです。仕様の慣例との違いが1つあります:ここでのwebhook_secretはプレーンな16進文字列であり、whsec_プレフィックス付きのbase64値ではありません。既製のStandard Webhooks/Svix検証ライブラリはbase64デコードを試みて失敗します。シークレットは返されたまま、生のHMACキーバイトとして、以下の検証コード(または同等の独自実装)で使用してください。プレフィックスを認識するライブラリは使わないでください。
Webhookの登録
バッチのコールバックをPUT /api/v1/batches/{batch_name}/webhookで登録(または更新)します:
curl -X PUT https://imagetotable.ai/api/v1/batches/260716-4K9P/webhook \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"callback_url": "https://example.com/hooks/imagetotable"}'レスポンスには、あなたのために生成されたwebhookごとのwebhook_secretが含まれます — アカウント全体で共有されるキーではなく、登録ごとに1つのシークレットです。シークレットが漏洩しても影響を受けるのはその1つのコールバックだけです:
{
"batch_name": "260716-4K9P",
"callback_url": "https://example.com/hooks/imagetotable",
"webhook_secret": "9f2a3b7c1d8e4f5061728394a5b6c7d81a2b3c4d5e6f7089",
"created_at": "2026-07-16T09:12:03Z",
"fired_at": null
}webhook_secretは保存しておいてください — 配信の検証に必要になります。「シークレットを表示」する専用エンドポイントはありませんが、同じcallback_urlを再PUTすれば、紛失した場合に安全に再取得できます(シークレットのローテーションやリセットは行われません)。
イベントエンベロープ
各配信はエンベロープに包まれたJSONオブジェクトです — 生のバッチデータではありません。これにより、既存の統合を壊すことなく、将来のイベントタイプに対応するために形状を拡張できます。typeを確認してイベントを区別してください。バッチに登録されたコールバックURLは、以下の両方の種類を混在して受け取ることができます。
{
"type": "batch.completed",
"created_at": "2026-07-16T09:14:31Z",
"data": {
"batch_name": "260716-4K9P",
"status": "succeeded",
"document_count": 3
}
}data.statusはバッチの最終結果を反映します(succeededまたはfailed — 完全なステータス語彙についてはAsync Task Modelを参照)。ペイロードは完了通知であり、結果そのものではありません — 受信後、GET /batches/{batch_name}/resultsを呼び出して実際の抽出データを取得してください。
ドキュメントでbboxアノテーションジョブをトリガーすると、そのドキュメントのバッチに既に登録されているWebhookが再利用され、別のbatch.completedではなく、別のイベントが配信されます:
{
"type": "document.bbox_completed",
"created_at": "2026-07-16T09:20:07Z",
"data": {
"document_id": "3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f",
"group_batch_id": "bx_8a3c2e1f",
"status": "succeeded"
}
}batch.completedと同様に、このイベントの配信はアトミックに要求されるため、複数の行グループが異なるスレッドでほぼ同時に終了状態に達しても、1つのbboxジョブに対して2つの別々のdocument.bbox_completed配信を受け取ることはありません。通常のwebhook-idベースの重複排除(下記参照)は、失敗した配信が再試行される通常のケースに加えて適用されます。
ポーリングトリガー用のイベントフィード
バッチコールバックWebhookは、既知の1つのbatch_nameにスコープされます。Zapierの「New Completed Batch」や「Failed Extraction」などのポーリングトリガーには、アカウント全体にスコープされたフィードが必要です:GET /api/v1/events。これは永続化されたイベントを新しい順に返し、Zapierが重複排除に使用できる安定したidフィールドを含みます。
curl "https://imagetotable.ai/api/v1/events?type=batch.completed&limit=20" \
-H "Authorization: Bearer $API_KEY"{
"events": [
{
"id": "evt_3f9c9e2a1b7d4e2b9a3e2f5b6c7d8e9f",
"type": "batch.completed",
"created_at": "2026-08-04T08:31:22+00:00",
"batch_name": "260804-4K9P",
"document_id": null,
"status": "succeeded",
"document_count": 3,
"filename": null,
"error": null,
"batch_url": "https://imagetotable.ai/api/v1/batches/260804-4K9P",
"batch_results_url": "https://imagetotable.ai/api/v1/batches/260804-4K9P/results"
}
],
"has_more": false,
"next_page_token": null
}サポートされているイベントタイプは、batch.completed、document.completed、document.failedです。イベントはワーカーが最終的なドキュメント状態をコミットした後にのみ書き込まれるため、コンシューマーはdocument_urlまたはbatch_results_urlをすぐに安全にフォローできます。失敗したドキュメントの再処理や、同じバッチ内の後続ウェーブの処理では、新しいイベントidが生成されます。batch_nameやdocument_idだけで重複排除しないでください。
バッチが既にわかっていて、待機中の非同期アクションを再開したい場合はコールバックを使用してください。トリガーがアカウント全体で新しい作業を発見する必要がある場合は/eventsを使用してください。ユーザーにbatch_nameを手入力させるトリガーは、通常は間違ったモデルです。
署名の検証
Standard Webhooks 仕様に従い、各配信には次の3つのヘッダーが含まれます。
| ヘッダー | 目的 |
|---|---|
webhook-id | この配信試行の一意のID。重複排除に使用します。再試行された配信は同じIDを再利用します。 |
webhook-timestamp | 配信が送信されたUnixタイムスタンプ。リプレイ攻撃を防ぐために使用します(タイムスタンプが古すぎる配信は拒否します)。 |
webhook-signature | 署名そのもので、v1,<base64 signature> の形式で表されます。 |
署名は、webhook-id、webhook-timestamp、生のリクエストボディをこの順序で . で連結したものに対して、Webhookの webhook_secret をキーとしてHMAC-SHA256を適用したものです。
signed_content = "{webhook_id}.{webhook_timestamp}.{raw_request_body}"
signature = base64(hmac_sha256(webhook_secret, signed_content))ご自身の環境でこれを再計算し、webhook-signature ヘッダーの v1, 以降の値と(定数時間比較を使用して)比較してください。必ず生のリクエストボディのバイト列に対して検証を行ってください。パース済みJSONを再シリアライズしたものではありません。再シリアライズすると、空白やキーの順序が変わり、正規の配信でも署名が一致しなくなる可能性があります。
# 署名検証はHTTP呼び出しではないため、保存済みの配信データ(headers.txt /
# body.json)に対してopensslで再現しています。実際のWebhookハンドラーに検証コードを
# 組み込む前に、スキームの理解を手動で確認するのに役立ちます。
WEBHOOK_ID=$(grep -i '^webhook-id:' headers.txt | cut -d' ' -f2 | tr -d '\r')
WEBHOOK_TS=$(grep -i '^webhook-timestamp:' headers.txt | cut -d' ' -f2 | tr -d '\r')
SIGNED_CONTENT="${WEBHOOK_ID}.${WEBHOOK_TS}.$(cat body.json)"
echo -n "$SIGNED_CONTENT" \
| openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary \
| base64import base64
import hashlib
import hmac
import os
headers = dict(line.split(": ", 1) for line in open("headers.txt") if ": " in line)
webhook_id = headers["webhook-id"].strip()
webhook_timestamp = headers["webhook-timestamp"].strip()
body = open("body.json", "rb").read()
signed_content = f"{webhook_id}.{webhook_timestamp}.".encode() + body
signature = base64.b64encode(
hmac.new(os.environ["WEBHOOK_SECRET"].encode(), signed_content, hashlib.sha256).digest()
)
print(signature.decode())import { createHmac } from "node:crypto";
import { readFileSync } from "node:fs";
const headers = Object.fromEntries(
readFileSync("headers.txt", "utf8")
.split("\n")
.filter((line) => line.includes(": "))
.map((line) => line.split(": "))
);
const webhookId = headers["webhook-id"].trim();
const webhookTimestamp = headers["webhook-timestamp"].trim();
const body = readFileSync("body.json", "utf8");
const signedContent = `${webhookId}.${webhookTimestamp}.${body}`;
const signature = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(signedContent)
.digest("base64");
console.log(signature);手動でテストしている場合(配信のボディを body.json に保存してから上記のスニペットを実行する場合)は、エディタや取得方法によって末尾に改行が追加されていないか注意してください。上記のcurl/openssl版は偶然これに影響を受けません(bashの $(cat ...) が末尾の改行を削除するため)が、Python版とJavaScript版はファイルの正確なバイトを読み取るため、余分な \n が混入すると一致しない署名を静かに生成します。典型的な症状は「curlでは有効なのにPythonの検証では無効になる」というもので、コード自体にバグがないために混乱します。実際のWebhookハンドラでは、コピーペーストや手動保存ではなく、Webフレームワークの生のリクエストボディ取得機能(Flaskの request.get_data()、Expressのraw-bodyミドルウェアなど)を使ってボディを取得してください。そうすれば本番環境ではこの問題は発生しません。これは手動テストだけの落とし穴です。
再試行
配信は、バッチが完了するとすぐに1回試行されます。その試行が失敗した場合(タイムアウト、接続エラー、2xx以外のレスポンス)、指数バックオフで再試行されます: 1分、5分、30分、2時間、6時間、24時間 — 合計7回まで(初回+再試行6回)。最初の試行から24時間以内にどれも成功しない場合、配信は失敗とマークされ、それ以上の再試行は行われません。イベントを永続的に記録したらすぐにエンドポイントが2xxステータスを返すようにしてください。遅い処理(バッチ結果の取得や解析など)はレスポンスの後に行い、前には行わないでください。下流の遅いステップ自体が再試行の原因にならないようにするためです。
バッチの再処理: ウェーブごとに1つの通知
特定の batch.completed 通知は、バッチ内のすべてのドキュメントが終了ステータスに達した瞬間に1回だけ発生します。その後、同じ batch_name にドキュメントを追加して process を再度呼び出すと(「新しいウェーブ」)、そのウェーブが完了したときにも自動的に通知されます。process_batch は、前のウェーブですでに通知済みのバッチ内に新しい対象ドキュメントを見つけると、Webhookを自動的に再設定します。ウェーブの合間にWebhookを再 PUT したり、何か他の操作をする必要はありません。
2つのウェーブが重なる場合(前のウェーブがまだ実行中に、さらにドキュメントを追加して処理する場合、前のウェーブが終了した後ではなく)、ちょうど1回の通知が届き、両方のウェーブのすべてのドキュメントが対象となり、それらの最後(どちらかのウェーブの)が完了したときに配信されます。2つの別々の通知を受け取るのは、2つの process 呼び出しの間にバッチが実際にアイドル状態になった(すべてのドキュメントが少なくとも1回は終了ステータスに達した)場合だけです。
このドキュメントの以前のバージョンでは、各ウェーブの前にWebhookを再 PUT することで回避しなければならない恒久的な制限として、一度だけの発火が説明されていました。しかし、そのアドバイスは実際には機能しませんでした。登録エンドポイントは、fired_at について意図的に干渉しない方針だった(そして今もそうです)ためです(Webhooksリファレンスを参照)。そして再設定は現在 process によって自動的に処理されます。もしあなたが