From 19a5f963b335f69c5b2c56f9bc8f1c61c944b462 Mon Sep 17 00:00:00 2001 From: Yusufbek Alimatov Date: Mon, 15 Dec 2025 19:47:19 +0500 Subject: [PATCH 1/3] docs: update push notification authentication flow --- .../media-buys/optimization-reporting.mdx | 29 +--- .../task-reference/create_media_buy.mdx | 8 +- .../media-buy/task-reference/get_products.mdx | 4 +- docs/protocols/a2a-guide.mdx | 11 +- docs/protocols/core-concepts.mdx | 124 ++++++++---------- docs/protocols/mcp-guide.mdx | 10 +- docs/protocols/protocol-comparison.mdx | 14 +- .../source/core/push-notification-config.json | 15 +-- static/schemas/source/enums/auth-scheme.json | 4 +- 9 files changed, 95 insertions(+), 124 deletions(-) diff --git a/docs/media-buy/media-buys/optimization-reporting.mdx b/docs/media-buy/media-buys/optimization-reporting.mdx index 0204a1a4..2f1180a0 100644 --- a/docs/media-buy/media-buys/optimization-reporting.mdx +++ b/docs/media-buy/media-buys/optimization-reporting.mdx @@ -74,22 +74,6 @@ Publishers can proactively push reporting data to buyers through webhook notific Configure reporting webhooks when creating a media buy using the `reporting_webhook` parameter: -```json -{ - "buyer_ref": "campaign_2024", - "packages": [...], - "reporting_webhook": { - "url": "https://buyer.example.com/webhooks/reporting", - "authentication": { - "schemes": ["Bearer"], - "credentials": "secret_token_min_32_chars" - }, - "reporting_frequency": "daily" - } -} -``` - -**Or with HMAC signature (recommended for production):** ```json { "buyer_ref": "campaign_2024", @@ -98,19 +82,18 @@ Configure reporting webhooks when creating a media buy using the `reporting_webh "url": "https://buyer.example.com/webhooks/reporting", "authentication": { "schemes": ["HMAC-SHA256"], - "credentials": "shared_secret_min_32_chars" + "credentials": generateUniqueSecret('campaign_2024_reporting') }, "reporting_frequency": "daily" } } ``` -**Security is Required:** -- `authentication` configuration is mandatory (minimum 32 characters) -- **Bearer tokens**: Simple, good for development (Authorization header) -- **HMAC-SHA256**: Production-recommended, prevents replay attacks (signature headers) -- Credentials exchanged out-of-band during publisher onboarding -- See [Webhook Security](/docs/protocols/core-concepts.mdx#security) for implementation details +**Authentication:** + +If provided, AdCP enforces HMAC-SHA256 signature-based authentication. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. + +For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). #### Supported Frequencies diff --git a/docs/media-buy/task-reference/create_media_buy.mdx b/docs/media-buy/task-reference/create_media_buy.mdx index f5dfdeae..2424526c 100644 --- a/docs/media-buy/task-reference/create_media_buy.mdx +++ b/docs/media-buy/task-reference/create_media_buy.mdx @@ -464,8 +464,8 @@ const result = await testAgent.createMediaBuy({ reporting_webhook: { url: 'https://buyer.example.com/webhooks/reporting', authentication: { - schemes: ['Bearer'], - credentials: 'secret_token_xyz_minimum_32_chars' + schemes: ['HMAC-SHA256'], + credentials: generateUniqueSecret('nike_q1_reporting') }, reporting_frequency: 'daily', requested_metrics: ['impressions', 'spend', 'video_completions'] @@ -518,8 +518,8 @@ async def create_with_reporting(): reporting_webhook={ 'url': 'https://buyer.example.com/webhooks/reporting', 'authentication': { - 'schemes': ['Bearer'], - 'credentials': 'secret_token_xyz_minimum_32_chars' + 'schemes': ['HMAC-SHA256'], + 'credentials': generate_unique_secret('nike_q1_reporting') }, 'reporting_frequency': 'daily', 'requested_metrics': ['impressions', 'spend', 'video_completions'] diff --git a/docs/media-buy/task-reference/get_products.mdx b/docs/media-buy/task-reference/get_products.mdx index 9a877d14..b865b3af 100644 --- a/docs/media-buy/task-reference/get_products.mdx +++ b/docs/media-buy/task-reference/get_products.mdx @@ -667,8 +667,8 @@ POST /api/mcp/call_tool "pushNotificationConfig": { "url": "https://buyer.com/webhooks/adcp/get_products", "authentication": { - "schemes": ["Bearer"], - "credentials": "secret_token_32_chars" + "schemes": ["HMAC-SHA256"], + "credentials": generateUniqueSecret('product_search_123') } } } diff --git a/docs/protocols/a2a-guide.mdx b/docs/protocols/a2a-guide.mdx index dafbc278..385b1315 100644 --- a/docs/protocols/a2a-guide.mdx +++ b/docs/protocols/a2a-guide.mdx @@ -259,15 +259,20 @@ await a2a.send({ }, pushNotificationConfig: { url: `https://buyer.com/webhooks/a2a/${taskType}/${operationId}`, - token: "client-validation-token", // Optional: for client-side validation authentication: { - schemes: ["bearer"], - credentials: "shared_secret_32_chars" + schemes: ["HMAC-SHA256"], + credentials: generateUniqueSecret(operationId) // Unique secret per operation } } }); ``` +### Webhook Authentication + +If authentication is provided, AdCP enforces HMAC-SHA256. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. + +For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). + For webhook payload formats, protocol comparison, and detailed handling examples, see [Task Management - Push Notification Integration](/docs/protocols/task-management#push-notification-integration). ## SSE Streaming (A2A-Specific) diff --git a/docs/protocols/core-concepts.mdx b/docs/protocols/core-concepts.mdx index 9ff1e9fa..da97cf6c 100644 --- a/docs/protocols/core-concepts.mdx +++ b/docs/protocols/core-concepts.mdx @@ -516,63 +516,55 @@ AdCP webhooks use **at-least-once delivery** semantics with the following charac ### Security -#### Webhook Authentication (Required) +#### Webhook Authentication -**AdCP adopts A2A's PushNotificationConfig structure** for webhook configuration. This provides a standard, flexible authentication model that supports multiple security schemes. +If authentication is provided, AdCP enforces HMAC-SHA256 signature-based authentication. -**Configuration Structure (A2A-Compatible):** +**Configuration:** ```json { "push_notification_config": { - "url": "https://buyer.example.com/webhooks/adcp", + "url": "https://buyer.example.com/webhooks/adcp/nike_q1_2025", "authentication": { - "schemes": ["Bearer"], - "credentials": "secret_token_min_32_chars" + "schemes": ["HMAC-SHA256"], + "credentials": "unique-secret-for-this-operation-min-32-chars" } } } ``` -**Supported Authentication Schemes:** - -1. **Bearer Token (Simple, Recommended for Development)** - ```json - { - "authentication": { - "schemes": ["Bearer"], - "credentials": "secret_token_32_chars" - } - } - ``` - -2. **HMAC Signature (Enterprise, Recommended for Production)** - ```json - { - "authentication": { - "schemes": ["HMAC-SHA256"], - "credentials": "shared_secret_32_chars" - } - } - ``` - -**Publisher Implementation (Bearer):** +#### HMAC Signature Flow + +**1. Client generates unique secret:** ```javascript -const config = pushNotificationConfig; -const scheme = config.authentication.schemes[0]; +import crypto from 'crypto'; -if (scheme === 'Bearer') { - await axios.post(config.url, payload, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.authentication.credentials}` +function generateUniqueSecret(operationId) { + // Generate cryptographically secure random secret + return crypto.randomBytes(32).toString('base64url'); +} + +const secret = generateUniqueSecret('nike_q1_2025'); +``` + +**2. Client provides secret in push_notification_config:** +```json +{ + "push_notification_config": { + "url": "https://buyer.com/webhooks/adcp/nike_q1_2025", + "authentication": { + "schemes": ["HMAC-SHA256"], + "credentials": "Xk7fB9mPq2vZwC8LtN3jR5sY1fKdHg6A4uW0eT9iOp" } - }); + } } ``` -**Publisher Implementation (HMAC-SHA256):** +**3. Server signs payload:** ```javascript -if (scheme === 'HMAC-SHA256') { +const crypto = require('crypto'); + +function sendWebhook(config, payload) { const timestamp = new Date().toISOString(); const signature = crypto .createHmac('sha256', config.authentication.credentials) @@ -589,27 +581,9 @@ if (scheme === 'HMAC-SHA256') { } ``` -**Buyer Implementation (Bearer):** +**4. Client verifies signature:** ```javascript -app.post('/webhooks/adcp', async (req, res) => { - const authHeader = req.headers.authorization; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ error: 'Missing Authorization header' }); - } - - const token = authHeader.substring(7); - if (token !== process.env.ADCP_WEBHOOK_TOKEN) { - return res.status(401).json({ error: 'Invalid token' }); - } - - await processWebhook(req.body); - res.status(200).json({ status: 'processed' }); -}); -``` - -**Buyer Implementation (HMAC-SHA256):** -```javascript -app.post('/webhooks/adcp', async (req, res) => { +app.post('/webhooks/adcp/:operation_id', async (req, res) => { const signature = req.headers['x-adcp-signature']; const timestamp = req.headers['x-adcp-timestamp']; @@ -617,15 +591,15 @@ app.post('/webhooks/adcp', async (req, res) => { return res.status(401).json({ error: 'Missing signature headers' }); } - // Reject old webhooks (prevent replay attacks) const eventTime = new Date(timestamp); - if (Date.now() - eventTime > 5 * 60 * 1000) { + const age = Date.now() - eventTime.getTime(); + if (age > 5 * 60 * 1000) { return res.status(401).json({ error: 'Webhook too old' }); } - // Verify signature + const secret = await getSecretForOperation(req.params.operation_id); const expectedSig = crypto - .createHmac('sha256', process.env.ADCP_WEBHOOK_SECRET) + .createHmac('sha256', secret) .update(timestamp + JSON.stringify(req.body)) .digest('hex'); @@ -633,18 +607,26 @@ app.post('/webhooks/adcp', async (req, res) => { return res.status(401).json({ error: 'Invalid signature' }); } - await processWebhook(req.body); + await processWebhook(req.params.operation_id, req.body); res.status(200).json({ status: 'processed' }); }); ``` -**Authentication Best Practices:** -- **Bearer tokens**: Simple, good for development and testing -- **HMAC signatures**: Prevents replay attacks, recommended for production -- Credentials exchanged out-of-band (during publisher onboarding) -- Minimum 32 characters for all credentials -- Store securely (environment variables, secret management) -- Support credential rotation (accept old and new during transition) +#### Security Requirements + +**Clients:** +- Generate unique credentials per webhook configuration +- Use minimum 32 characters for credentials +- Verify signature before processing webhook +- Validate timestamp (reject >5 minutes old) +- Rotate credentials periodically + +**Publishers:** +- Sign every webhook with client-provided credentials +- Include timestamp in signature calculation +- Send signature in `X-ADCP-Signature` header +- Send timestamp in `X-ADCP-Timestamp` header +- Use HTTPS for all webhook deliveries ### Retry and Circuit Breaker Patterns diff --git a/docs/protocols/mcp-guide.mdx b/docs/protocols/mcp-guide.mdx index 51a3c4ea..def2aa7d 100644 --- a/docs/protocols/mcp-guide.mdx +++ b/docs/protocols/mcp-guide.mdx @@ -271,8 +271,8 @@ const response = await session.call('create_media_buy', pushNotificationConfig: { url: `https://buyer.com/webhooks/adcp/${taskType}/${operationId}`, authentication: { - schemes: ["HMAC-SHA256"], // or ["bearer"] for simple auth - credentials: "shared_secret_32_chars" + schemes: ["HMAC-SHA256"], + credentials: generateUniqueSecret(operationId) // Unique secret per operation } } } @@ -286,6 +286,12 @@ if (response.status === 'submitted') { } ``` +#### Webhook Authentication + +If authentication is provided, AdCP enforces HMAC-SHA256. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. + +For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). + **Webhook POST format:** ```json { diff --git a/docs/protocols/protocol-comparison.mdx b/docs/protocols/protocol-comparison.mdx index ad790332..aee775c9 100644 --- a/docs/protocols/protocol-comparison.mdx +++ b/docs/protocols/protocol-comparison.mdx @@ -111,13 +111,13 @@ const updates = await session.call('tasks/get', { include_result: true }); -// Optional: Configure webhook at protocol level (A2A-compatible structure) +// Optional: Configure webhook (HMAC-SHA256 authentication) const response = await session.call('create_media_buy', params, { push_notification_config: { url: "https://buyer.com/webhooks", authentication: { - schemes: ["HMAC-SHA256"], // or ["Bearer"] - credentials: "shared_secret_32_chars" + schemes: ["HMAC-SHA256"], + credentials: generateUniqueSecret(operationId) // Unique per operation } } }); @@ -140,14 +140,14 @@ events.onmessage = (event) => { console.log(`Status: ${update.status}, Message: ${update.message}`); }; -// Native webhook support +// Native webhook support (HMAC-SHA256 authentication) await a2a.send({ message: { /* skill invocation */ }, push_notification_config: { - webhook_url: "https://buyer.com/webhooks", + url: "https://buyer.com/webhooks", authentication: { - schemes: ["Bearer"], - credentials: "secret_token_min_32_chars" + schemes: ["HMAC-SHA256"], + credentials: generateUniqueSecret(operationId) // Unique per operation } } }); diff --git a/static/schemas/source/core/push-notification-config.json b/static/schemas/source/core/push-notification-config.json index 59df598f..9d6ef2ab 100644 --- a/static/schemas/source/core/push-notification-config.json +++ b/static/schemas/source/core/push-notification-config.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/push-notification-config.json", "title": "Push Notification Config", - "description": "Webhook configuration for asynchronous task notifications. Uses A2A-compatible PushNotificationConfig structure. Supports Bearer tokens (simple) or HMAC signatures (production-recommended).", + "description": "Webhook configuration for asynchronous task notifications. If authentication is provided, AdCP enforces HMAC-SHA256 signature-based authentication.", "type": "object", "properties": { "url": { @@ -10,18 +10,13 @@ "format": "uri", "description": "Webhook endpoint URL for task status notifications" }, - "token": { - "type": "string", - "description": "Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.", - "minLength": 16 - }, "authentication": { "type": "object", - "description": "Authentication configuration for webhook delivery (A2A-compatible)", + "description": "HMAC-SHA256 authentication. Client generates a unique shared secret per webhook configuration. Server signs payload and includes signature in X-ADCP-Signature header.", "properties": { "schemes": { "type": "array", - "description": "Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for signature verification (recommended for production)", + "description": "Must be ['HMAC-SHA256'].", "items": { "$ref": "/schemas/enums/auth-scheme.json" }, @@ -30,7 +25,7 @@ }, "credentials": { "type": "string", - "description": "Credentials for authentication. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.", + "description": "Client-generated shared secret. Must be unique per webhook configuration. Minimum 32 characters.", "minLength": 32 } }, @@ -38,6 +33,6 @@ "additionalProperties": false } }, - "required": ["url", "authentication"], + "required": ["url"], "additionalProperties": false } diff --git a/static/schemas/source/enums/auth-scheme.json b/static/schemas/source/enums/auth-scheme.json index 26087438..cc8b47c9 100644 --- a/static/schemas/source/enums/auth-scheme.json +++ b/static/schemas/source/enums/auth-scheme.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/auth-scheme.json", "title": "Authentication Scheme", - "description": "Authentication schemes for push notification endpoints", + "description": "Authentication scheme for push notification endpoints. If authentication is provided, AdCP enforces HMAC-SHA256.", "type": "string", - "enum": ["Bearer", "HMAC-SHA256"] + "enum": ["HMAC-SHA256"] } From ec4edc5351f6a31605f1200f596b6e3caef55801 Mon Sep 17 00:00:00 2001 From: Yusufbek Alimatov Date: Mon, 15 Dec 2025 20:06:54 +0500 Subject: [PATCH 2/3] docs: update Bearer token authentication --- docs.json | 1 + .../media-buys/optimization-reporting.mdx | 19 ++++++- docs/protocols/a2a-guide.mdx | 6 +-- docs/protocols/core-concepts.mdx | 52 ++++++++++++++++++- docs/protocols/mcp-guide.mdx | 6 +-- docs/protocols/protocol-comparison.mdx | 12 ++--- .../source/core/push-notification-config.json | 8 +-- static/schemas/source/enums/auth-scheme.json | 4 +- 8 files changed, 87 insertions(+), 21 deletions(-) diff --git a/docs.json b/docs.json index 6b1aa412..6748c700 100644 --- a/docs.json +++ b/docs.json @@ -54,6 +54,7 @@ "group": "Protocols", "pages": [ "docs/protocols/getting-started", + "docs/protocols/core-concepts", { "group": "Choose Your Protocol", "pages": [ diff --git a/docs/media-buy/media-buys/optimization-reporting.mdx b/docs/media-buy/media-buys/optimization-reporting.mdx index 2f1180a0..a8ee0e45 100644 --- a/docs/media-buy/media-buys/optimization-reporting.mdx +++ b/docs/media-buy/media-buys/optimization-reporting.mdx @@ -74,6 +74,23 @@ Publishers can proactively push reporting data to buyers through webhook notific Configure reporting webhooks when creating a media buy using the `reporting_webhook` parameter: +**With Bearer token:** +```json +{ + "buyer_ref": "campaign_2024", + "packages": [...], + "reporting_webhook": { + "url": "https://buyer.example.com/webhooks/reporting", + "authentication": { + "schemes": ["Bearer"], + "credentials": "client-token-min-32-chars" + }, + "reporting_frequency": "daily" + } +} +``` + +**With HMAC-SHA256:** ```json { "buyer_ref": "campaign_2024", @@ -91,7 +108,7 @@ Configure reporting webhooks when creating a media buy using the `reporting_webh **Authentication:** -If provided, AdCP enforces HMAC-SHA256 signature-based authentication. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. +Bearer: server echoes token in `Authorization` header. HMAC-SHA256: server signs payload with `X-ADCP-Signature` header. For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). diff --git a/docs/protocols/a2a-guide.mdx b/docs/protocols/a2a-guide.mdx index 385b1315..37d681ef 100644 --- a/docs/protocols/a2a-guide.mdx +++ b/docs/protocols/a2a-guide.mdx @@ -260,8 +260,8 @@ await a2a.send({ pushNotificationConfig: { url: `https://buyer.com/webhooks/a2a/${taskType}/${operationId}`, authentication: { - schemes: ["HMAC-SHA256"], - credentials: generateUniqueSecret(operationId) // Unique secret per operation + schemes: ["Bearer"], // or ["HMAC-SHA256"] + credentials: "client-token-min-32-chars" } } }); @@ -269,7 +269,7 @@ await a2a.send({ ### Webhook Authentication -If authentication is provided, AdCP enforces HMAC-SHA256. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. +AdCP supports Bearer token (server echoes back in `Authorization` header) or HMAC-SHA256 (server signs payload with `X-ADCP-Signature` header). For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). diff --git a/docs/protocols/core-concepts.mdx b/docs/protocols/core-concepts.mdx index da97cf6c..deb67617 100644 --- a/docs/protocols/core-concepts.mdx +++ b/docs/protocols/core-concepts.mdx @@ -518,9 +518,57 @@ AdCP webhooks use **at-least-once delivery** semantics with the following charac #### Webhook Authentication -If authentication is provided, AdCP enforces HMAC-SHA256 signature-based authentication. +AdCP supports two authentication methods: + +**1. Bearer Token (Simple)** + +Client provides token, server echoes back in `Authorization` header. Token format is client's choice (JWT, random string, etc.). + +```json +{ + "push_notification_config": { + "url": "https://buyer.example.com/webhooks/adcp/nike_q1_2025", + "authentication": { + "schemes": ["Bearer"], + "credentials": "client-provided-token-min-32-chars" + } + } +} +``` + +Server sends: +```http +POST /webhooks/adcp/nike_q1_2025 +Authorization: Bearer client-provided-token-min-32-chars +Content-Type: application/json + +{ "task_id": "...", "status": "completed", ... } +``` + +Client validates: +```javascript +app.post('/webhooks/adcp/:operation_id', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ error: 'Missing Authorization header' }); + } + + const token = authHeader.substring(7); + const expectedToken = await getTokenForOperation(req.params.operation_id); + + if (token !== expectedToken) { + return res.status(401).json({ error: 'Invalid token' }); + } + + await processWebhook(req.params.operation_id, req.body); + res.status(200).json({ status: 'processed' }); +}); +``` + +**2. HMAC-SHA256 Signature (Advanced)** + +Client provides secret, server signs payload. -**Configuration:** ```json { "push_notification_config": { diff --git a/docs/protocols/mcp-guide.mdx b/docs/protocols/mcp-guide.mdx index def2aa7d..09266fc5 100644 --- a/docs/protocols/mcp-guide.mdx +++ b/docs/protocols/mcp-guide.mdx @@ -271,8 +271,8 @@ const response = await session.call('create_media_buy', pushNotificationConfig: { url: `https://buyer.com/webhooks/adcp/${taskType}/${operationId}`, authentication: { - schemes: ["HMAC-SHA256"], - credentials: generateUniqueSecret(operationId) // Unique secret per operation + schemes: ["Bearer"], // or ["HMAC-SHA256"] + credentials: "client-token-min-32-chars" } } } @@ -288,7 +288,7 @@ if (response.status === 'submitted') { #### Webhook Authentication -If authentication is provided, AdCP enforces HMAC-SHA256. Server signs payload with `X-ADCP-Signature` and `X-ADCP-Timestamp` headers. +AdCP supports Bearer token (server echoes back in `Authorization` header) or HMAC-SHA256 (server signs payload with `X-ADCP-Signature` header). For complete implementation details, see [Core Concepts - Webhook Authentication](/docs/protocols/core-concepts#webhook-authentication). diff --git a/docs/protocols/protocol-comparison.mdx b/docs/protocols/protocol-comparison.mdx index aee775c9..5b8e83d7 100644 --- a/docs/protocols/protocol-comparison.mdx +++ b/docs/protocols/protocol-comparison.mdx @@ -111,13 +111,13 @@ const updates = await session.call('tasks/get', { include_result: true }); -// Optional: Configure webhook (HMAC-SHA256 authentication) +// Optional: Configure webhook (Bearer or HMAC-SHA256) const response = await session.call('create_media_buy', params, { push_notification_config: { url: "https://buyer.com/webhooks", authentication: { - schemes: ["HMAC-SHA256"], - credentials: generateUniqueSecret(operationId) // Unique per operation + schemes: ["Bearer"], // or ["HMAC-SHA256"] + credentials: "client-token-min-32-chars" } } }); @@ -140,14 +140,14 @@ events.onmessage = (event) => { console.log(`Status: ${update.status}, Message: ${update.message}`); }; -// Native webhook support (HMAC-SHA256 authentication) +// Native webhook support (Bearer or HMAC-SHA256) await a2a.send({ message: { /* skill invocation */ }, push_notification_config: { url: "https://buyer.com/webhooks", authentication: { - schemes: ["HMAC-SHA256"], - credentials: generateUniqueSecret(operationId) // Unique per operation + schemes: ["Bearer"], // or ["HMAC-SHA256"] + credentials: "client-token-min-32-chars" } } }); diff --git a/static/schemas/source/core/push-notification-config.json b/static/schemas/source/core/push-notification-config.json index 9d6ef2ab..fab2c963 100644 --- a/static/schemas/source/core/push-notification-config.json +++ b/static/schemas/source/core/push-notification-config.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/push-notification-config.json", "title": "Push Notification Config", - "description": "Webhook configuration for asynchronous task notifications. If authentication is provided, AdCP enforces HMAC-SHA256 signature-based authentication.", + "description": "Webhook configuration for asynchronous task notifications. Supports Bearer token (server echoes back) or HMAC-SHA256 signature authentication.", "type": "object", "properties": { "url": { @@ -12,11 +12,11 @@ }, "authentication": { "type": "object", - "description": "HMAC-SHA256 authentication. Client generates a unique shared secret per webhook configuration. Server signs payload and includes signature in X-ADCP-Signature header.", + "description": "Authentication configuration. Bearer: client provides token, server echoes in Authorization header. HMAC-SHA256: client provides secret, server signs payload with X-ADCP-Signature header.", "properties": { "schemes": { "type": "array", - "description": "Must be ['HMAC-SHA256'].", + "description": "Authentication scheme: ['Bearer'] or ['HMAC-SHA256']", "items": { "$ref": "/schemas/enums/auth-scheme.json" }, @@ -25,7 +25,7 @@ }, "credentials": { "type": "string", - "description": "Client-generated shared secret. Must be unique per webhook configuration. Minimum 32 characters.", + "description": "For Bearer: client-provided token (JWT, random string, etc.). Server echoes in 'Authorization: Bearer '. For HMAC-SHA256: client-generated shared secret. Must be unique per webhook configuration. Minimum 32 characters.", "minLength": 32 } }, diff --git a/static/schemas/source/enums/auth-scheme.json b/static/schemas/source/enums/auth-scheme.json index cc8b47c9..10455271 100644 --- a/static/schemas/source/enums/auth-scheme.json +++ b/static/schemas/source/enums/auth-scheme.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/auth-scheme.json", "title": "Authentication Scheme", - "description": "Authentication scheme for push notification endpoints. If authentication is provided, AdCP enforces HMAC-SHA256.", + "description": "Authentication scheme for push notification endpoints. Bearer: client provides token, server echoes in Authorization header. HMAC-SHA256: client provides secret, server signs payload.", "type": "string", - "enum": ["HMAC-SHA256"] + "enum": ["Bearer", "HMAC-SHA256"] } From fd3685d4b70e160c7675752bee9bc0c320169a74 Mon Sep 17 00:00:00 2001 From: Yusufbek Alimatov Date: Mon, 15 Dec 2025 20:16:01 +0500 Subject: [PATCH 3/3] docs: improve example credentials for hmac --- docs/media-buy/media-buys/optimization-reporting.mdx | 2 +- docs/media-buy/task-reference/create_media_buy.mdx | 2 +- docs/media-buy/task-reference/get_products.mdx | 2 +- docs/protocols/core-concepts.mdx | 11 ++--------- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/media-buy/media-buys/optimization-reporting.mdx b/docs/media-buy/media-buys/optimization-reporting.mdx index a8ee0e45..7f9d15d2 100644 --- a/docs/media-buy/media-buys/optimization-reporting.mdx +++ b/docs/media-buy/media-buys/optimization-reporting.mdx @@ -99,7 +99,7 @@ Configure reporting webhooks when creating a media buy using the `reporting_webh "url": "https://buyer.example.com/webhooks/reporting", "authentication": { "schemes": ["HMAC-SHA256"], - "credentials": generateUniqueSecret('campaign_2024_reporting') + "credentials": "shared_per_tool_call_secret_min_32_chars" }, "reporting_frequency": "daily" } diff --git a/docs/media-buy/task-reference/create_media_buy.mdx b/docs/media-buy/task-reference/create_media_buy.mdx index 2424526c..66f6d3a4 100644 --- a/docs/media-buy/task-reference/create_media_buy.mdx +++ b/docs/media-buy/task-reference/create_media_buy.mdx @@ -465,7 +465,7 @@ const result = await testAgent.createMediaBuy({ url: 'https://buyer.example.com/webhooks/reporting', authentication: { schemes: ['HMAC-SHA256'], - credentials: generateUniqueSecret('nike_q1_reporting') + credentials: 'shared_per_tool_call_secret_min_32_chars' }, reporting_frequency: 'daily', requested_metrics: ['impressions', 'spend', 'video_completions'] diff --git a/docs/media-buy/task-reference/get_products.mdx b/docs/media-buy/task-reference/get_products.mdx index b865b3af..6dbda059 100644 --- a/docs/media-buy/task-reference/get_products.mdx +++ b/docs/media-buy/task-reference/get_products.mdx @@ -668,7 +668,7 @@ POST /api/mcp/call_tool "url": "https://buyer.com/webhooks/adcp/get_products", "authentication": { "schemes": ["HMAC-SHA256"], - "credentials": generateUniqueSecret('product_search_123') + "credentials": "shared_per_tool_call_secret_min_32_chars" } } } diff --git a/docs/protocols/core-concepts.mdx b/docs/protocols/core-concepts.mdx index deb67617..a1098885 100644 --- a/docs/protocols/core-concepts.mdx +++ b/docs/protocols/core-concepts.mdx @@ -585,14 +585,7 @@ Client provides secret, server signs payload. **1. Client generates unique secret:** ```javascript -import crypto from 'crypto'; - -function generateUniqueSecret(operationId) { - // Generate cryptographically secure random secret - return crypto.randomBytes(32).toString('base64url'); -} - -const secret = generateUniqueSecret('nike_q1_2025'); +const secret = 'shared_per_tool_call_secret_min_32_chars'; ``` **2. Client provides secret in push_notification_config:** @@ -602,7 +595,7 @@ const secret = generateUniqueSecret('nike_q1_2025'); "url": "https://buyer.com/webhooks/adcp/nike_q1_2025", "authentication": { "schemes": ["HMAC-SHA256"], - "credentials": "Xk7fB9mPq2vZwC8LtN3jR5sY1fKdHg6A4uW0eT9iOp" + "credentials": "shared_per_tool_call_secret_min_32_chars" } } }