Skip to content

webhook envelope: type notification_id as a first-class field #4594

@bokelley

Description

@bokelley

Problem

The snapshot/log foundation contract (#4582, snapshot-and-log.mdx) Rule 1 introduces a per-state-event identifier:

notification_id — event-layer, per state event. Stable across re-emissions of the same logical event. For state-shaped events this equals the resource's stable id (e.g., impairment_id is the notification_id for impairment events).

Receivers MUST track both idempotency_key (per-fire, transport-layer dedup) and notification_id (per-state, application-layer state correlation).

But notification_id is documented only in prose. The webhook envelope schema (core/mcp-webhook-payload.json / core/push-notification-config.json) doesn't declare it as a typed field. Receivers building strictly from JSON Schema — generated SDKs, conformance test fixtures, type-driven receivers — won't see notification_id in their type definitions. They'll have to read prose to know it exists.

The current webhooks.mdx persistent-channel contract hedges this:

Receivers MUST dedupe by idempotency_key (or the equivalent event-scoped id surfaced in the payload — e.g., notification_id for notification-type events).

"Or the equivalent event-scoped id surfaced in the payload" is the tell — the contract is documented but not typed.

Proposal

Add notification_id as a typed field on the webhook envelope shape, alongside the existing idempotency_key. Two ways to do this:

(a) Add to the existing envelope schema as an optional top-level field. Webhooks for non-state-event types (delivery reports today) omit it; webhooks for state events (impairment, future creative lifecycle, etc.) populate it. Simplest.

(b) Define a typed envelope shape per notification_type value via a discriminator. Stricter but verbose.

Going with (a) is the smaller spec lift and matches how task_id, operation_id are already surfaced today.

Where this lands

Most likely in core/mcp-webhook-payload.json and/or a sibling envelope schema referenced from there. Need to verify which schema is the canonical envelope (current state: webhooks.mdx documents the shape inline but the schema layer is split across mcp-webhook-payload, push-notification-config, and the per-task webhook payload schemas).

Backwards compatibility

  • New field is optional; absent for fires that don't carry per-state semantics.
  • Receivers that don't read it continue to dedupe on idempotency_key as before.

Why this matters

Without typing, generated SDKs don't surface notification_id at the type level. Buyer agents calling webhook.notification_id would get a missing-property error or have to access via dynamic property lookup. That's a soft contract — receivers built strictly from generated types won't comply with the Rule 1 dedupe/correlate split unless they also read the prose.

Filing as a follow-up to #4588 (which landed the prose contract) and #2856 (which adds impairment to notification-type and is the natural place to plumb the envelope field through).

Acceptance

  • Identify the canonical webhook envelope schema (mcp-webhook-payload.json or sibling)
  • Add typed notification_id field with description matching snapshot-and-log Rule 1
  • Cross-reference from notification-type.json enumDescriptions
  • Update webhooks.mdx to remove the "or the equivalent event-scoped id" hedge
  • Changeset

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.schemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions