Documentation Index
Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
Use this file to discover all available pages before exploring further.
プッシュ通知により、パブリッシャーはポーリングを必要とせず、タスクのステータス更新を直接配信できます。タスクリクエストに Webhook URL を指定すると、パブリッシャーはタスクの進行に応じてその URL にステータス変更を POST します。
仕組み
- タスク呼び出しごとに一意のオペレーション ID が生成されます
- そのID(および他のルーティングパラメーター)を URL テンプレートに代入して Webhook URL が構築されます
push_notification_config がその URL と HMAC 認証情報とともにタスクリクエストボディに注入されます
- タスクのステータスが変化するたびに、パブリッシャーが Webhook 通知を URL に POST します
- 各通知はペイロード内に
operation_id をエコーバックするため、URL を解析せずに相関付けできます
create_media_buy request
└── push_notification_config
├── url: "https://you.com/adcp/webhook/create_media_buy/agent_123/cd51e063-2b79-4a6d-afac-ed7789c3a443"
└── authentication: { schemes: ["HMAC-SHA256"], credentials: "..." }
↓ publisher processes task ↓
POST https://you.com/adcp/webhook/create_media_buy/agent_123/cd51e063-2b79-4a6d-afac-ed7789c3a443
{
"task_id": "task_456",
"operation_id": "cd51e063-2b79-4a6d-afac-ed7789c3a443", ← echoed from your URL
"status": "completed",
"result": { ... }
}
@adcp/client ライブラリを使用している場合、このフロー全体は自動的に処理される — クライアントに webhookUrlTemplate と webhookSecret を一度設定すると、push_notification_config がすべての送信タスク呼び出しに注入されます。
命名規則: snake_case vs camelCase
混乱しやすい点があります。2つの命名規則が存在する:
| コンテキスト | フィールド名 | 例 |
|---|
| MCP タスク引数(AdCP JSON) | push_notification_config | { push_notification_config: { url: ... } } |
| A2A 設定オブジェクト | pushNotificationConfig | configuration: { pushNotificationConfig: { url: ... } } |
AdCP のフィールド名は常に push_notification_config(snake_case)だ。他のタスクパラメーターと並んでタスクリクエストボディに含めます。
A2A の場合、A2A プロトコルが camelCase を使用して configuration エンベロープにラップするが、オブジェクトの内容は同一です。
リクエストへの push_notification_config の追加
MCP
push_notification_config をタスク引数として、他のタスクパラメーターとマージして含める:
{
"brand": { "brand_id": "acme" },
"start_time": { "type": "date", "date": "2025-03-01" },
"end_time": "2025-06-30T23:59:59Z",
"packages": [...],
"push_notification_config": {
"url": "https://you.com/webhooks/adcp/create_media_buy/op_abc123",
"authentication": {
"schemes": ["HMAC-SHA256"],
"credentials": "your_shared_secret_min_32_chars"
}
}
}
A2A
A2A の場合、スキルパラメーターは message.parts[].data.parameters に格納します。プッシュ通知設定はトップレベルの configuration オブジェクトに入れる:
{
"message": {
"parts": [{
"kind": "data",
"data": {
"skill": "create_media_buy",
"parameters": {
"packages": [...]
}
}
}]
},
"configuration": {
"pushNotificationConfig": {
"url": "https://you.com/webhooks/adcp/create_media_buy/op_abc123",
"authentication": {
"schemes": ["HMAC-SHA256"],
"credentials": "your_shared_secret_min_32_chars"
}
}
}
}
オペレーション ID と URL テンプレート
オペレーション ID により、受信した Webhook を適切なハンドラーにルーティングできます。典型的なパターン:
- タスク呼び出しごとに一意の ID を生成します
- Webhook URL パスに埋め込む
- パブリッシャーがペイロードで
operation_id をエコーする — URL 解析は不要
URL テンプレートパターン:
https://you.com/webhooks/{task_type}/{agent_id}/{operation_id}
例(クライアントライブラリが自動処理):
import { randomUUID } from 'crypto';
const operationId = randomUUID(); // e.g. "cd51e063-2b79-4a6d-afac-ed7789c3a443"
const webhookUrl = `https://you.com/adcp/webhook/create_media_buy/${agentId}/${operationId}`;
// pass webhookUrl in push_notification_config.url
パブリッシャーの Webhook ペイロードには "operation_id": "cd51e063-2b79-4a6d-afac-ed7789c3a443" が含まれるため、URL を解析せずに正しい保留オペレーションにルーティングできます。
Webhook が送信される条件
Webhook は、リクエストに push_notification_config が含まれている限り、最初のレスポンス以降の各ステータス変化に対して送信されます。
タスクが同期的に完了した場合(最初のレスポンスがすでに completed または failed)、Webhook は送信されない — すでに結果が手元にあるためです。
Webhook をトリガーするステータス変化:
| Status | 意味 |
|---|
working | タスクが処理中 — 進捗情報が含まれることがある |
input-required | 人の承認または確認待ち |
completed | 最終結果が利用可能 |
failed | タスクがエラー詳細とともに失敗 |
canceled | タスクがキャンセルされた |
Webhook ペイロード形式
MCP
{
"task_id": "task_456",
"operation_id": "cd51e063-2b79-4a6d-afac-ed7789c3a443",
"task_type": "create_media_buy",
"domain": "media-buy",
"status": "completed",
"timestamp": "2025-01-22T10:30:00Z",
"message": "Media buy created successfully",
"result": {
"media_buy_id": "mb_12345",
"packages": [
{ "package_id": "pkg_001", "context": { "line_item": "li_ctv_sports" } }
]
}
}
A2A
A2A は最終状態に対して Task オブジェクト、進捗には TaskStatusUpdateEvent を送信します。AdCP の結果データは status.message.parts[].data に含まれます:
{
"id": "task_456",
"contextId": "ctx_123",
"status": {
"state": "completed",
"message": {
"role": "agent",
"parts": [
{ "kind": "text", "text": "Media buy created successfully" },
{
"kind": "data",
"data": {
"media_buy_id": "mb_12345",
"packages": [
{ "package_id": "pkg_001", "context": { "line_item": "li_ctv_sports" } }
]
}
}
]
},
"timestamp": "2025-01-22T10:30:00Z"
}
}
プロトコル比較
| MCP | A2A |
|---|
| 設定フィールド | push_notification_config(タスク引数内) | configuration.pushNotificationConfig(スキルパラメーターとは別) |
| エンベロープ | mcp-webhook-payload.json | ネイティブの Task / TaskStatusUpdateEvent |
| 結果の場所 | result フィールド | status.message.parts[].data |
| データスキーマ | 同一の AdCP スキーマ | 同一の AdCP スキーマ |
ステータス別結果データ
| Status | result / data の内容 |
|---|
completed / failed | 完全なタスクレスポンス |
working | 進捗: percentage、current_step、total_steps |
input-required | 理由と任意のバリデーションエラー |
submitted | 最小限の受付確認 |
Bearer トークン
シンプルなトークン認証 — パブリッシャーが Authorization ヘッダーでトークンを送信します。
設定:
{
"authentication": {
"schemes": ["Bearer"],
"credentials": "your_bearer_token_min_32_chars"
}
}
サーバーサイドの検証:
app.post('/webhooks/adcp', (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (token !== process.env.ADCP_WEBHOOK_TOKEN) {
return res.status(401).end();
}
processWebhook(req.body);
res.status(200).end();
});
HMAC-SHA256(本番環境推奨)
パブリッシャーが共有シークレットで各リクエストに署名し、リプレイ保護のためのタイムスタンプを含めます。受信側は署名とタイムスタンプの両方を検証します。
設定:
{
"authentication": {
"schemes": ["HMAC-SHA256"],
"credentials": "your_shared_secret_min_32_chars"
}
}
パブリッシャーが送信する2つのヘッダー:
X-ADCP-Signature: sha256=<hex digest>
X-ADCP-Timestamp: <unix timestamp in seconds>
署名アルゴリズム:
署名対象のメッセージは {unix_timestamp}.{raw_json_body} だ — Unix タイムスタンプ(秒)、ドット、HTTP ボディとして送信される JSON バイトそのもの。
Signature = sha256= + hex( HMAC-SHA256( secret, "{timestamp}.{rawBody}" ) )
rawBody はワイヤー上で送信される正確なバイトでなければなりません。実装では HTTP ボディと同じシリアライゼーションで署名しなければなりません — 再シリアライズまたは再フォーマットされたバージョンへの署名は検証失敗を引き起こす。
パブリッシャー実装(署名):
import { createHmac } from 'crypto';
function signWebhook(rawBody: string, secret: string): { signature: string; timestamp: string } {
const timestamp = Math.floor(Date.now() / 1000).toString();
const message = `${timestamp}.${rawBody}`;
const hex = createHmac('sha256', secret).update(message).digest('hex');
return {
signature: `sha256=${hex}`,
timestamp,
};
}
受信側実装(検証):
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(
rawBody: string,
signature: string,
timestamp: string,
secret: string,
): boolean {
const ts = parseInt(timestamp, 10);
if (isNaN(ts)) return false;
// Reject requests older than 5 minutes (replay attack prevention)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - ts) > 300) return false;
const message = `${ts}.${rawBody}`;
const expected = `sha256=${createHmac('sha256', secret).update(message).digest('hex')}`;
if (signature.length !== expected.length) return false;
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
app.post('/webhooks/adcp', (req, res) => {
const sig = req.headers['x-adcp-signature'] as string;
const timestamp = req.headers['x-adcp-timestamp'] as string;
if (!sig || !timestamp || !verifyWebhook(req.rawBody, sig, timestamp, process.env.ADCP_WEBHOOK_SECRET)) {
return res.status(401).end();
}
processWebhook(req.body);
res.status(200).end();
});
:::caution 重要: 生ボディによる検証
署名は常に生の HTTP ボディバイトに対して検証し、パース済み JSON を再シリアライズしたバージョンに対して行ってはなりません。言語やライブラリによって JSON シリアライゼーション(キーの順序、空白、数値フォーマット)が異なる場合があります。JSON パース前に生ボディをキャプチャし、HMAC 検証にはその正確なバイトを使用すること。
Express では express.json() の verify コールバックを使用して生ボディをキャプチャできる:
app.use(express.json({
verify: (req, _res, buf) => {
(req as any).rawBody = buf.toString('utf-8');
},
}));
:::
:::note リプレイ保護
5 分のタイムスタンプウィンドウにより、リプレイ攻撃を防ぐ。パブリッシャーは X-ADCP-Timestamp ヘッダーに Unix タイムスタンプ(秒単位、ISO 8601 ではない)を使用しなければなりません。受信側は |current_time - timestamp| > 300 秒のリクエストを拒否すべきです。
:::
信頼性
Webhook は少なくとも1回の配信を使用する — 同じイベントが複数回受信される場合があり、イベントが順序通りに届かない場合もあります。
これに対処するために task_id + status + timestamp を使用します:
async function processWebhook(payload) {
const { task_id, status, timestamp, result } = payload;
const task = await db.getTask(task_id);
// 既に新しいステータスを処理済みの場合はスキップ
if (task?.updated_at >= timestamp) return;
await db.updateTask(task_id, { status, updated_at: timestamp, result });
await triggerBusinessLogic(task_id, status);
}
常にポーリングをバックアップとして実装すること。 Webhook はネットワーク障害やサーバーダウンで失敗することがあります。Webhook が設定されている場合は遅いポーリング間隔(例: 30 秒ではなく 2 分ごと)を使用し、Webhook で終端ステータスを受信したらポーリングを停止します。
ベストプラクティス
- 常にポーリングをバックアップとして実装する — Webhook は失敗することがあります。Webhook が設定されている場合は間隔を減らして(例: 2 分ごと)ポーリングし、終端ステータスを受信したら停止します
- 重複を処理する —
task_id + timestamp を使用して、既に処理済みまたは順序外のイベントをスキップします
- 署名を検証する — 処理前に常に
X-ADCP-Signature を検証します
- すぐに応答を返す — パブリッシャーのタイムアウトや不要なリトライを避けるため、重い処理の前に
200 を返す
- URL 構造に依存しない — ルーティングには URL 解析ではなくペイロードの
operation_id を使用します
- 本番環境では HMAC-SHA256 を使用する — Bearer トークンはシンプルだがペイロードの改ざんを防げない
レポート Webhook
レポート Webhook はタスクステータス Webhook とは別物です。アクティブなメディアバイの定期的なパフォーマンスデータを配信し、push_notification_config ではなく create_media_buy の reporting_webhook で設定します。
詳細についてはタスクリファレンスの reporting_webhook を参照。
次のステップ