Skip to content

webhooks.mdx forbids aggregated_totals in webhook result payloads, but no schema encodes it — which artifact is normative? #7329

Description

@KonstantinMirin

webhooks.mdx states a MUST NOT that no published JSON Schema encodes, at any
version I can find. An implementer validating against the schema and an
implementer reading the docs reach opposite conclusions about the same payload.

The prose

docs/building/by-layer/L3/webhooks.mdx:271 (at head):

For delivery-report data events such as scheduled, final, delayed, adjusted, and window_update, notification_id is absent by design; dedupe the transport event with idempotency_key. The aggregated_totals field is API-only for get_media_buy_delivery responses and must not be emitted in reporting webhook result payloads.

The schema

media-buy/media-buy-delivery-webhook-result.json, checked at three versions:

version additionalProperties declares aggregated_totals not / allOf guard
3.1 true no none
3.1.20 (latest_stable) true no none
3.2.0-rc.1 true no none

With additionalProperties: true and no negative constraint, a result payload
carrying aggregated_totals validates cleanly. There is nothing in the
schema for a conformance checker to fire on.

The generated SDK agrees with the schema, not the docs

The Python SDK's generated model behaves the same way, which matters because it
is what most implementers actually touch:

>>> from adcp.types import MediaBuyDeliveryWebhookResult as M
>>> M.model_config["extra"]
'allow'
>>> "aggregated_totals" in M.model_fields
False
>>> m = M.model_validate({..., "aggregated_totals": {"impressions": 1, "spend": 1.0}})
>>> "aggregated_totals" in m.model_dump()
True

So the model accepts the forbidden field and round-trips it into its own
output
. A sender that builds its payload through the SDK — the path you would
most want to be safe — gets no signal at all, and will happily re-emit the field
it was handed.

Three artifacts, two answers: the docs forbid it; the schema and the SDK permit
it and preserve it.

Why this is worth resolving rather than leaving to convention

The two artifacts are not equally consumable. Schemas are executable: they drive
validators, code generation, and conformance suites. Prose is not. When a
normative requirement exists only in prose, every implementer has to re-derive it
by reading, and no tool can catch a regression against it — which is how a
sender emits the field for a long time while its own test suite stays green.

Our reading is that the schema is the contract and the docs and SDKs are
derivatives of it: if the schema does not encode a restriction, a payload that
satisfies the schema is conformant. On that reading this MUST NOT is
unenforceable as written, and the sentence overstates what the protocol actually
requires.

The question

Which artifact is normative here?

  1. The prose is right and the schema is incomplete. Then the schema should
    encode it — for example "aggregated_totals": false under
    propertyNames/not, or an explicit
    "not": {"required": ["aggregated_totals"]} on the result schema. Happy to
    send a PR if you confirm the intended shape.
  2. The schema is right and the prose overstates. Then :271 should read as
    guidance ("aggregated_totals is API-only and SHOULD NOT be emitted...")
    rather than a MUST NOT, so implementers are not held to an unenforceable rule.

Either resolution is fine from our side — the ambiguity is the problem, not the
direction.

Context

Found while adding schema-compliance grading to a seller's webhook test suite:
the compliance check accepts a payload the docs forbid, so the general check and
a field-specific assertion disagreed, and tracing that disagreement led here.

Related, and possibly the same root cause: media-buy-delivery-webhook-result.json
sets additionalProperties: true generally, so any API-only field can ride along
in a webhook result without a validator objecting. If the intent is that a webhook
result is a strict subset of the API response, that may be worth stating in the
schema more broadly than one field.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.media-buyIssue concerns the media-buy protocol domainneeds-wg-reviewBlocked on a working-group decision — surface in WG meeting agendasschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions