Skip to content

feat: add optional reporting webhooks with metrics and aggregation support#81

Merged
bokelley merged 1 commit into
mainfrom
bokelley/reporting-webhooks
Oct 1, 2025
Merged

feat: add optional reporting webhooks with metrics and aggregation support#81
bokelley merged 1 commit into
mainfrom
bokelley/reporting-webhooks

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Implements automated reporting data delivery via webhooks for media buys, enabling publishers to push reporting data to buyers at configurable frequencies.

Key Features

Reporting Capabilities (on Products via get_products):

  • available_reporting_frequencies: hourly, daily, monthly options
  • expected_delay_minutes: expected delay before data becomes available (e.g., 240 for 4 hours)
  • timezone: timezone for reporting period boundaries (UTC or IANA timezone)
  • supports_webhooks: boolean flag indicating webhook support
  • available_metrics: array of available metrics beyond impressions/spend (clicks, conversions, video_completions, etc.)

Reporting Webhook Configuration (on create_media_buy):

  • Optional reporting_webhook object with URL, auth, and desired frequency
  • requested_metrics: buyers can optionally request a subset of available metrics to reduce payload size
  • Publishers commit to (campaign_duration / frequency) + 1 notifications

Webhook Payload Structure:

  • notification_type: scheduled (regular update), final (campaign complete), or delayed (data not yet available)
  • sequence_number: sequential notification tracking (starts at 1)
  • next_expected_at: ISO 8601 timestamp for next expected notification
  • media_buy_deliveries: ALWAYS an array to enable publisher aggregation

Publisher Aggregation:

  • Publishers SHOULD aggregate webhooks by URL, frequency, and reporting period
  • Reduces webhook volume for buyers with many active campaigns (e.g., 100 campaigns → 1 webhook instead of 100)
  • Buyers iterate the array and calculate totals if needed
  • aggregated_totals field is NOT included in webhook payloads (only in API responses)

Changes

New Schema:

  • static/schemas/v1/core/reporting-capabilities.json: Defines reporting capabilities structure

Modified Schemas:

  • static/schemas/v1/core/product.json: Added optional reporting_capabilities reference
  • static/schemas/v1/media-buy/create-media-buy-request.json: Added optional reporting_webhook configuration
  • static/schemas/v1/media-buy/get-media-buy-delivery-response.json:
    • Added webhook-specific metadata fields (notification_type, sequence_number, next_expected_at)
    • Renamed deliveriesmedia_buy_deliveries to clarify array semantics
    • Marked aggregated_totals as API-only (not in webhooks)

Documentation Updates:

  • docs/media-buy/task-reference/get_products.md: Documented reporting_capabilities field
  • docs/media-buy/task-reference/create_media_buy.md: Documented reporting_webhook configuration
  • docs/media-buy/media-buys/optimization-reporting.md: Added comprehensive "Webhook-Based Reporting" section
  • docs/media-buy/task-reference/get_media_buy_delivery.md: Updated to use media_buy_deliveries
  • docs/protocols/core-concepts.md: Added "Reporting Webhooks" section with implementation guidance

Test Plan

  • All schema validation tests pass
  • All example validation tests pass
  • TypeScript type checking passes
  • Documentation builds successfully
  • Schema-documentation consistency verified

Notes

  • This does NOT increment the schema version (will be part of a larger version bump)
  • All changes are backward compatible (all new fields are optional)
  • Webhook aggregation is strongly recommended but not required

🤖 Generated with Claude Code

…pport

Implements automated reporting data delivery via webhooks for media buys:

**Reporting Capabilities** (on Products):
- `available_reporting_frequencies`: hourly, daily, monthly options
- `expected_delay_minutes`: delay before data is available (e.g., 240 for 4hrs)
- `timezone`: reporting period boundaries (UTC or IANA timezone)
- `supports_webhooks`: webhook support flag
- `available_metrics`: metrics available beyond impressions/spend (clicks, conversions, etc.)

**Reporting Webhook** (on create_media_buy):
- Optional webhook configuration with URL, auth, and frequency
- `requested_metrics`: buyer can request subset of available metrics
- Publisher commits to (duration / frequency) + 1 notifications

**Webhook Payload**:
- `notification_type`: scheduled, final, or delayed
- `sequence_number`: sequential notification tracking
- `next_expected_at`: timestamp for next notification
- `media_buy_deliveries`: ALWAYS an array (enables publisher aggregation)

**Aggregation**:
- Publishers SHOULD aggregate webhooks by URL/frequency/period
- Reduces webhook volume for buyers with many campaigns
- Buyers iterate array, calculate totals if needed
- No `aggregated_totals` in webhooks (arbitrary grouping)

All tests pass, docs updated, schema synchronized.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@bokelley bokelley merged commit 9290539 into main Oct 1, 2025
4 checks passed
bokelley added a commit that referenced this pull request Oct 4, 2025
Address Yahoo's production readiness feedback on PR #81 with enterprise-grade
webhook infrastructure requirements.

Security & Authentication:
- HMAC-SHA256 signature verification with X-ADCP-Signature header
- Replay attack prevention with timestamp validation
- Constant-time comparison for timing attack protection
- Secret rotation guidance

Reliability & Circuit Breaking:
- Exponential backoff with jitter for retries
- Circuit breaker pattern (CLOSED/OPEN/HALF_OPEN states)
- Bounded queues (1000 webhooks per endpoint) to prevent memory exhaustion
- Per-endpoint circuit breakers with automatic recovery

Privacy & Compliance:
- PII scrubbing requirements for GDPR/CCPA
- Explicit guidance on what to scrub vs. aggregate
- Minimum aggregation thresholds to prevent re-identification
- Publisher and buyer responsibilities documented

Operational Monitoring:
- Webhook health metrics in get_media_buy_delivery responses
- Circuit breaker status visibility for debugging
- Success/failure tracking and alerting guidance
- Last attempt timestamps and failure reasons

Data Integrity:
- get_media_buy_delivery API defined as authoritative source of truth
- Reconciliation process with discrepancy thresholds
- Late-arriving impression handling (adjustments array and correction webhooks)
- Attribution window guidance (7-90 days depending on ad type)

Partial Failure Handling:
- Best-effort delivery with per-campaign status indicators
- partial_data flag and unavailable_count field
- reporting_delayed status for temporarily unavailable data
- Guidance on when to use partial vs. delayed notifications

Timezone & DST:
- Explicit handling of 23-hour and 25-hour DST transition days
- IANA timezone identifier requirements
- ISO 8601 with timezone offset requirements
- Complete implementation examples for period calculation and billing
- Common pitfalls and test date guidance

Schema Updates:
- Add notification_type: "correction" for late-arriving data
- Add partial_data and unavailable_count fields
- Add webhook_health object with circuit breaker status
- Add reporting_period DST fields (duration_hours, is_dst_transition)
- Add status: "reporting_delayed" for partial failures
- Add adjustments array for incremental corrections

This addresses all critical gaps identified by Yahoo for production deployment
at scale, enabling Phase 1 rollout with proper security, monitoring, and
operational safeguards.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Oct 9, 2025
…#86)

* feat: add comprehensive webhook security and reliability specifications

Address Yahoo's production readiness feedback on PR #81 with enterprise-grade
webhook infrastructure requirements.

Security & Authentication:
- HMAC-SHA256 signature verification with X-ADCP-Signature header
- Replay attack prevention with timestamp validation
- Constant-time comparison for timing attack protection
- Secret rotation guidance

Reliability & Circuit Breaking:
- Exponential backoff with jitter for retries
- Circuit breaker pattern (CLOSED/OPEN/HALF_OPEN states)
- Bounded queues (1000 webhooks per endpoint) to prevent memory exhaustion
- Per-endpoint circuit breakers with automatic recovery

Privacy & Compliance:
- PII scrubbing requirements for GDPR/CCPA
- Explicit guidance on what to scrub vs. aggregate
- Minimum aggregation thresholds to prevent re-identification
- Publisher and buyer responsibilities documented

Operational Monitoring:
- Webhook health metrics in get_media_buy_delivery responses
- Circuit breaker status visibility for debugging
- Success/failure tracking and alerting guidance
- Last attempt timestamps and failure reasons

Data Integrity:
- get_media_buy_delivery API defined as authoritative source of truth
- Reconciliation process with discrepancy thresholds
- Late-arriving impression handling (adjustments array and correction webhooks)
- Attribution window guidance (7-90 days depending on ad type)

Partial Failure Handling:
- Best-effort delivery with per-campaign status indicators
- partial_data flag and unavailable_count field
- reporting_delayed status for temporarily unavailable data
- Guidance on when to use partial vs. delayed notifications

Timezone & DST:
- Explicit handling of 23-hour and 25-hour DST transition days
- IANA timezone identifier requirements
- ISO 8601 with timezone offset requirements
- Complete implementation examples for period calculation and billing
- Common pitfalls and test date guidance

Schema Updates:
- Add notification_type: "correction" for late-arriving data
- Add partial_data and unavailable_count fields
- Add webhook_health object with circuit breaker status
- Add reporting_period DST fields (duration_hours, is_dst_transition)
- Add status: "reporting_delayed" for partial failures
- Add adjustments array for incremental corrections

This addresses all critical gaps identified by Yahoo for production deployment
at scale, enabling Phase 1 rollout with proper security, monitoring, and
operational safeguards.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify webhook specifications based on implementation feedback

Simplify and clarify webhook specifications based on Yahoo's feedback and
team discussion. Focus on practical implementation guidance rather than
exhaustive edge cases.

Key Simplifications:

1. **Recommend UTC for Reporting**
   - Eliminates DST complexity for most use cases
   - Simplifies reconciliation across systems
   - Reduced DST documentation to brief "if you must use local time" section
   - Made DST payload fields optional (duration_hours, is_dst_transition)

2. **Clarify Hourly Webhooks Are Optional**
   - Publishers not required to support all frequencies
   - Recommend daily for Phase 1 deployments
   - Note cost considerations (hourly = 24x traffic)

3. **Add Offline Reporting Option**
   - Cloud storage bucket push for large buyer-seller pairs
   - 10-100x cost reduction vs. webhooks for high volume
   - Supports S3, GCS, Azure Blob Storage
   - Same payload format as webhooks (JSON Lines, CSV, Parquet)
   - Best for >100 campaigns or hourly reporting needs

4. **Move Reconciliation to General Reporting**
   - Not webhook-specific - applies to all delivery methods
   - Webhooks, offline files, and polling all need reconciliation
   - Late-arriving data affects all methods
   - API remains authoritative source of truth

5. **Clarify Webhook Correction Advantage**
   - Webhooks enable "overwrite this period" with notification_type: correction
   - Polling-only implementations must detect via reconciliation
   - Makes late-arriving impression handling easier for buyers

These changes address the feedback:
- "Is it possible / realistic to ask for all reporting in UTC?" → Yes, recommended
- "Publishers don't have to support hourly" → Clarified as optional
- "Offline reporting mechanism makes sense" → Added cloud storage option
- "Reconciliation isn't a webhook issue" → Moved to general reporting section
- "Webhooks could fire same range again with 'overwrite'" → Emphasized this advantage

No schema changes needed - existing fields support both simplified (UTC, daily)
and complex (local timezone, hourly) implementations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify webhook specs to UTC-only with minimal adjustment model

Further simplify webhook specifications based on team consensus:

1. **UTC-Only Reporting (MUST)**
   - Removed local timezone option entirely
   - All reporting periods use UTC (no DST complexity)
   - Daily: 00:00:00Z to 23:59:59Z (always 24 hours)
   - Removed duration_hours, is_dst_transition, dst_transition_type fields

2. **Simplified Late-Arrival Handling**
   - Single approach: Resend period with is_adjusted: true
   - Removed adjustments array (incremental model)
   - Removed is_correction, correction_reason fields
   - notification_type: "adjusted" (replaced "correction")
   - Buyer simply replaces entire period when is_adjusted flag present

3. **Webhook Health via Task Management**
   - Removed webhook_health object from delivery response
   - Webhook delivery status tracked through global task management
   - Consistent with other AdCP operations
   - No need for media-buy-specific health endpoints

Schema Changes:
- notification_type: "correction" → "adjusted"
- Removed reporting_period.{duration_hours, is_dst_transition, dst_transition_type}
- Removed media_buy_deliveries[].{adjustments, is_correction, correction_reason}
- Added media_buy_deliveries[].is_adjusted (boolean)
- Removed media_buy_deliveries[].webhook_health (use task management)

Documentation simplified by ~200 lines while maintaining all critical security
and reliability features (HMAC, circuit breakers, PII scrubbing, etc.).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: clarify webhook behavior for async operations

## Summary
Documents webhook notification format and trigger behavior for asynchronous
AdCP operations, addressing confusion about when webhooks are called and
what payload format is sent.

## Changes

### Webhook Trigger Behavior
- **Key rule**: Webhooks are ONLY called when initial response is `submitted`
- `completed`/`failed` responses are synchronous - no webhook needed
- `working` responses complete within ~120s - no webhook, just wait
- `submitted` responses are long-running (hours/days) - webhooks used

### Webhook Payload Format
- Webhooks POST the complete task response object (not just status)
- Each webhook matches the task's response schema exactly
- For `create_media_buy`: full create-media-buy-response.json structure
- For `activate_signal`: full activate-signal-response.json structure

### Webhook URL Pattern
- Use task name in URL: `/webhooks/adcp/{task_name}/{agent_id}/{op_id}`
- Examples: `/create_media_buy/...`, `/activate_signal/...`
- Enables clear routing based on task type

### Status Change Notifications
For `submitted` operations, webhooks called on:
- `input-required` → Human needs to respond/approve
- `completed` → Operation finished successfully
- `failed` → Operation failed with error details
- `canceled` → Operation was canceled

## Files Modified
- docs/protocols/core-concepts.md - Added comprehensive webhook section
- docs/protocols/task-management.md - Updated webhook integration docs
- docs/media-buy/task-reference/create_media_buy.md - Clarified webhook flow
- docs/signals/tasks/activate_signal.md - Added webhook examples

## Examples Added
- Human-in-the-loop approval flow with webhooks
- Synchronous vs async operation handling
- Complete webhook payload structures
- Webhook handler implementation patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: clarify webhook behavior for async operations

Clarify that webhook security (HMAC-SHA256) is a core AdCP protocol
requirement, not an optional implementation detail.

**Key Change: Secure by Default**

The problem with "recommended security":
- Every integration becomes a negotiation
- Different publishers implement different auth schemes
- Default becomes "no security" because it's easier
- Buyers must handle N different authentication methods
- Result: Insecure by default

The benefit of required security:
- One standardized approach that always works
- Publishers know exactly what to implement
- Buyers know exactly what to expect
- Secure by default across all implementations
- Exception is "opt out of security" (conscious choice)

**Schema Changes:**

In `create-media-buy-request.json`:
- Changed `auth_type` + `auth_token` → `secret` (required)
- `secret` is shared HMAC-SHA256 key (min 32 chars)
- Exchanged out-of-band during publisher onboarding
- Used to generate X-ADCP-Signature header

**Documentation Changes:**

Added "Why Required" rationale to webhook security section:
- Explains default-secure philosophy
- Clarifies this is protocol requirement, not implementation guidance
- Updates examples to use `secret` field instead of auth_type/token

**Compliance:**

Implementations without HMAC signature verification are non-compliant with
AdCP specification. This applies to:
- Reporting webhooks (media buy domain)
- Task status webhooks (all domains)
- Any future webhook functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: standardize webhook security across all AdCP webhooks

Apply required HMAC-SHA256 security to ALL webhook configurations in AdCP,
not just reporting webhooks.

**Universal Webhook Security Model:**

All webhook configurations now use:
- `webhook_secret` (required, min 32 chars) - NOT `webhook_auth`
- `X-ADCP-Signature` header with HMAC-SHA256 signature
- `X-ADCP-Timestamp` header for replay protection
- Constant-time signature verification

**Changes Apply To:**

1. **Protocol-level task webhooks** (MCP, A2A)
   - `webhook_url` + `webhook_secret` parameters
   - Used for task status notifications (submitted → completed)
   - Applies to: create_media_buy, update_media_buy, sync_creatives, etc.

2. **Domain-specific webhooks** (e.g., reporting webhooks)
   - `reporting_webhook.secret` field
   - Used for scheduled reporting delivery
   - Applies to: media buy reporting

**Before (inconsistent):**
```javascript
// MCP task webhook
{ webhook_url: "...", webhook_auth: { type: "bearer", ... } }

// Reporting webhook
{ url: "...", auth_type: "bearer", auth_token: "..." }

// A2A push notification
{ webhook_url: "...", auth: { type: "bearer", ... } }
```

**After (standardized):**
```javascript
// ALL webhooks use same model
{ webhook_url: "...", webhook_secret: "min_32_chars..." }
{ url: "...", secret: "min_32_chars..." }
{ webhook_url: "...", secret: "min_32_chars..." }
```

**Documentation Updates:**
- core-concepts.md: Protocol-level webhook examples
- mcp-guide.md: MCP-specific task webhook examples
- protocol-comparison.md: Cross-protocol webhook examples
- task-management.md: Task webhook configuration
- optimization-reporting.md: Reporting webhook examples

**Why This Matters:**

As Brian said: "If it's clearly laid out in the spec, do it this way, then
okay, maybe once in a while people will make an exception, but there's no
exception. The default would be secure and best practice."

One security model across ALL AdCP webhooks = secure by default everywhere.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: align A2A webhook config with A2A protocol spec

Clarify that A2A's push_notification_config structure is defined by the
A2A protocol specification (not under our control), and document how AdCP's
HMAC security requirement maps to A2A's native auth structure.

**Key Insight:**
A2A is an external protocol - we adapt to it, not the other way around.

**A2A Protocol Structure (Fixed):**
```javascript
push_notification_config: {
  webhook_url: "...",
  auth: {
    type: "bearer" | "basic" | "custom",
    data: { /* auth-specific data */ }
  }
}
```

**AdCP's Approach:**
Use A2A's `auth.type: "custom"` extensibility to pass HMAC secret:
```javascript
push_notification_config: {
  webhook_url: "...",
  auth: {
    type: "custom",
    data: { hmac_secret: "..." }  // AdCP security requirement
  }
}
```

**Server Implementation:**
Regardless of protocol (MCP or A2A), AdCP servers MUST:
- Extract HMAC secret from protocol-specific location
- Send X-ADCP-Signature and X-ADCP-Timestamp headers
- Implement HMAC-SHA256 signature generation

**Documentation Added:**
New "Protocol Alignment" section explains:
- MCP: AdCP adds webhook_secret parameter
- A2A: AdCP maps to auth.type: "custom"
- Both: Servers always send AdCP security headers

This maintains AdCP's security requirements while respecting that A2A's
structure is defined by an external specification we cannot change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify webhook security to Bearer token authentication

Replace complex HMAC-SHA256 signature verification with simple Bearer
token authentication. This simplification is appropriate because:
1. We control the AdCP protocol specification
2. TLS already provides transport security
3. Bearer tokens are simpler to implement and debug
4. Uniform auth structure across all webhook types

Changes:
- Unified webhook_config structure with auth.type and auth.token
- Replace X-ADCP-Signature header with Authorization: Bearer <token>
- Standardize across protocol-level webhooks, reporting webhooks, and A2A
- Update schema to require auth object instead of secret field
- Update all documentation examples to use Bearer token pattern

Net change: -123 lines (reduced complexity)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: correct A2A authentication structure to match protocol spec

Fix A2A push_notification_config to use the correct authentication
structure according to A2A v0.2.5 specification:

- Change from `auth: { type, credentials }` to `authentication: { schemes, credentials }`
- Use `schemes: ["Bearer"]` (array format per A2A spec)
- Update all A2A examples in core-concepts.md and protocol-comparison.md

This ensures AdCP correctly maps to A2A's PushNotificationAuthenticationInfo
which uses:
```typescript
interface PushNotificationAuthenticationInfo {
  schemes: string[];      // Array of authentication scheme names
  credentials?: string;   // Optional credentials string
}
```

Reference: https://a2a-protocol.org/v0.2.5/specification/#69-pushnotificationauthenticationinfo-object

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: adopt A2A PushNotificationConfig as universal webhook structure

Replace custom webhook_config with A2A's PushNotificationConfig structure
across all AdCP webhooks. This provides:

1. **Industry-standard structure**: Uses A2A v0.2.5 specification
2. **Zero protocol mapping**: Same structure works for MCP and A2A
3. **Multi-auth support**: schemes array handles Bearer, HMAC-SHA256, etc.
4. **Simpler implementation**: One webhook config format to learn

Structure changes:
- FROM: webhook_config { url, auth: { type, token } }
- TO: push_notification_config { url, authentication: { schemes, credentials } }

Supported authentication schemes:
- Bearer: Simple token auth for development
- HMAC-SHA256: Signature verification for production (prevents replay attacks)

Benefits:
- Consistent developer experience across protocols
- Natural multi-auth support via schemes array
- Future-proof for JWT, mTLS, or other auth types
- Eliminates protocol-specific webhook configuration

Reference: https://a2a-protocol.org/v0.2.5/specification/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract reusable push-notification-config schema

Create shared core schema for webhook configuration that can be reused
across any task-specific webhooks (reporting, creative sync, etc.).

Changes:
- New: static/schemas/v1/core/push-notification-config.json
- Updated: create-media-buy-request.json uses allOf + $ref pattern
- reporting_webhook extends base push_notification_config with
  reporting-specific fields (reporting_frequency, requested_metrics)

Benefits:
- Single source of truth for webhook authentication structure
- Easy to add webhooks to other async tasks (sync_creatives, etc.)
- Maintains A2A PushNotificationConfig compatibility
- DRY principle for webhook configuration

Note: Protocol-level push_notification_config (for task status
notifications) is handled at MCP/A2A wrapper level, not in task schemas.
This schema is for task-specific business webhooks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add webhook support to async task requests

Add optional push_notification_config to update_media_buy and
sync_creatives task requests for task-specific async notifications.

Schema changes:
- update-media-buy-request.json: Add push_notification_config field
- sync-creatives-request.json: Add push_notification_config field
- Both reference core/push-notification-config.json for consistency

Documentation updates:
- update_media_buy.md: Add webhook configuration section
- sync_creatives.md: Add webhook configuration section
- Clarify when webhooks are triggered (working→completed, submitted→completed)

Task-specific vs Protocol-level webhooks:
- Task-specific: Passed in task params (this commit)
  - update_media_buy, sync_creatives, reporting_webhook
  - Notify about specific task completion
- Protocol-level: Passed in MCP/A2A wrapper options
  - Generic task status notifications
  - Works for ALL async tasks

Benefits:
- Consistent webhook structure across all async tasks
- Easy to add webhooks to future long-running operations
- Clear documentation for when/why to use task-specific webhooks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: clarify webhook triggers include HITL approval

Update sync_creatives push_notification_config description to mention
manual approval/HITL as a webhook trigger, not just large bulk operations.

Webhooks are sent when:
- Large bulk operations take longer than ~120 seconds
- Manual approval required (Human-in-the-Loop workflows)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant