Skip to content

feat(webhooks): support tenant-scoped signing senders in PgTaskWebhookOutbox #1081

Description

@bokelley

Summary

PgTaskWebhookOutbox in 8.0.0-beta.8 accepts one fixed WebhookSender for the process. That is not sufficient for a multi-tenant seller whose request-scoped capabilities advertise tenant-specific RFC 9421 signing credentials.

This blocks adopting the SDK-managed durable outbox without either signing some tenant webhooks with the wrong key or collapsing all tenants onto one global signing identity.

Concrete adopter shape

Embedded Sales Agent serves multiple seller tenants through one PlatformRouter and one task registry. DecisioningPlatform.get_adcp_capabilities_for_request() resolves webhook_signing per tenant from that tenant active TenantSigningCredential. The existing delivery path loads the same tenant credential when producing the RFC 9421 signature.

The beta.8 outbox instead stores a single sender:

PgTaskWebhookOutbox(
    pool=pool,
    sender=WebhookSender.from_jwk(one_private_jwk),
    ...,
)

At worker time there is no request context from which the adopter can select the tenant credential. account_id is the buyer account identity and is not generically the seller tenant or signing scope. Buyer-supplied request_context is also not a trusted place for signing identity.

Using a subclass is intentionally unavailable: beta.8 correctly requires the exact PgTaskRegistry and PgTaskWebhookOutbox types so an override cannot invalidate the audited durability contract. The external-owner path would require duplicating the new atomic outbox just to vary key selection.

Required behavior

Please add an SDK-owned tenant-aware sender resolution path while retaining the exact audited registry/outbox types. For example:

  • capture a trusted, adopter-resolved signing scope when the task is issued;
  • store that scope in the encrypted/authenticated registration or outbox envelope;
  • resolve a WebhookSender from that scope for each delivery attempt;
  • validate every resolved sender as RFC 9421-capable, SDK-owned IP-pinned transport with private destinations disabled;
  • preserve byte-identical body/idempotency retries while allowing a fresh signature from the currently valid tenant key.

The signing scope must come from trusted server-side account/platform metadata, not push_notification_config, buyer context, or an unqualified buyer account ID.

A possible public shape is a concrete SDK resolver interface accepted by PgTaskWebhookOutbox, plus a handler hook that derives an opaque signing-scope ID from the resolved RequestContext. The fixed sender= path should remain the simple single-tenant default.

Acceptance criteria

  1. Two tenants handled by one registry/outbox can deliver with different kid values matching their request-scoped advertised credentials.
  2. The trusted signing scope survives task issuance, process restart, terminal enqueue, and worker delivery.
  3. A buyer cannot select or alter the signing scope through request fields.
  4. Exact body and idempotency-key replay guarantees remain unchanged.
  5. Missing/invalid tenant sender resolution fails closed and leaves a recoverable/quarantined delivery record.
  6. Key rotation can select the currently valid tenant sender without rewriting the immutable webhook body.

Related context: #875 established the need for tenant-specific externally managed webhook signing capabilities. Beta.8 now provides the durable publisher, but only for a single fixed signing identity.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions