---
id: ATS-PFM-INT-013
scope: CAP-PFM-INT-013
product_area: platform
module: integrations
revision: 3
status: proposed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-INT-013-01
    title: Ingress refuses invalid endpoints and overload
    level: api
    proves:
      - BR-CAP-PFM-INT-013-01
    preconditions: 'Prepare enabled, disabled and unknown endpoints.'
    steps: 'Post ordinary messages, an oversized body and traffic above the endpoint limit.'
    expected: >-
      Only the enabled endpoint records ordinary messages; invalid endpoints return empty not-found,
      oversized input refuses and overload is throttled.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-567
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#answers
      404 for a disabled endpoint, and records nothing
    category: boundary
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#answers
        404 for a disabled endpoint, and records nothing
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#answers
        404 for a token no endpoint has, and records nothing
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#answers
        413 for a body over 1 MB, storing no object
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#answers
        429 when the endpoint’s bucket is full
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-02
    title: Durable notification storage precedes acknowledgement
    level: api
    proves:
      - BR-CAP-PFM-INT-013-02
    preconditions: An enabled endpoint receives ordinary authentication headers and cookies.
    steps: Post a notification and inspect stored material and dispatch.
    expected: >-
      Sensitive headers are omitted, the pending event has its digest and queued work, and receipt is
      acknowledged afterwards.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-567
      - TST-PFM-INT-584
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#stores
      the body and headers, then records the event pending and queued with its digest and a queue row
    category: invariant
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/ingress.integration.spec.js#stores
        the body and headers, then records the event pending and queued with its digest and a queue row
      - >-
        services-platform:modules/integrations/tests/unit/services/webhook-rules.spec.ts#drops
        authorization and cookies the verification does not name
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-03
    title: Signature checks precede event production
    level: api
    proves:
      - BR-CAP-PFM-INT-013-04
    preconditions: 'Prepare valid, tampered and excessively old signatures.'
    steps: Process them and repeatedly fail processing of a retryable message.
    expected: >-
      Valid input produces a routed event, invalid signatures produce none and repeated retryable failure
      becomes dead at six attempts.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-568
      - TST-PFM-INT-480
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#rejects
      a request whose signature does not verify, writing no event
    category: failure
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#rejects
        a request whose signature does not verify, writing no event
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#processes
        a verified request into its integration events, each queued for routing, with the provider
        subject
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#retries
        a classified failure with the async backoff, and records it dead after six attempts
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#is
        rejected when its body was tampered with after signing
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-04
    title: Deduplication does not prevent explicit replay
    level: api
    proves:
      - BR-CAP-PFM-INT-013-05
    preconditions: Processed notifications exist with and without provider identifiers.
    steps: Resubmit both and explicitly replay the identified message.
    expected: >-
      Repeated notifications are retained, then marked ignored with the earlier message identified and no
      new emitted event; explicit replay processes again and emits new events.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-568
      - TST-PFM-INT-480
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#ignores
      a second request with the provider event id of one already processed, pointing at the first
    category: concurrency
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#ignores
        a second request with the provider event id of one already processed, pointing at the first
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#ignores
        the same body on the same endpoint within a day when the provider gives no event id
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#processes
        a replayed request again, bypassing deduplication, with replayed_from and new events
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#is
        ignored as a duplicate when the provider sends it again
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-05
    title: Manual setup reveals its secret once
    level: api
    proves:
      - BR-CAP-PFM-INT-013-06
    preconditions: Verification has provisioned a manually registered endpoint.
    steps: Read endpoint setup twice as an administrator.
    expected: Initial setup returns address and signing secret; the later read retains the address only.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-480
      - TST-PFM-INT-491
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#never
      answers the secret again
    category: rule
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#never
        answers the secret again
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#answers
        the administrator its public URL and, the first time, its secret
      - >-
        services-platform:modules/integrations/tests/e2e/reader-access.e2e.spec.js#is followed by an
        administrator who reads the signing secret once, and then never again
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-06
    title: Rotation retains the previous signature temporarily
    level: api
    proves:
      - BR-CAP-PFM-INT-013-07
    preconditions: The endpoint is in its signing-secret overlap window.
    steps: Process a message signed by the previous secret.
    expected: Verification accepts it during the rotation window.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-568
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#verifies
      with the previous secret too while the endpoint rotates
    category: transition
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#verifies
        with the previous secret too while the endpoint rotates
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-07
    title: Replay limits bound recovery work
    level: api
    proves:
      - BR-CAP-PFM-INT-013-08
    preconditions: Stored endpoint messages span ten days.
    steps: 'Replay one, request seven- and eight-day windows and request replay as a reader.'
    expected: >-
      Individual and permitted-window requests queue audited replay; the larger window and unauthorized
      caller refuse.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to: []
    provenance: proposed
    source: 'services-platform:docs/integrations/README.md#Webhooks (WS8, 2026-10-18)'
    category: boundary
    binding_evidence: []
  - id: ATS-PFM-INT-013-08
    title: Readers cannot consume the one-time secret
    level: api
    proves:
      - BR-CAP-PFM-INT-013-09
    preconditions: A reader can inspect an endpoint whose setup secret has not been read.
    steps: Inspect every permitted endpoint and notification read.
    expected: No secret returns and the administrator’s initial disclosure remains available.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-491
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/e2e/reader-access.e2e.spec.js#reads no webhook signing
      secret in any answer
    category: permission
    binding_evidence:
      - >-
        services-platform:modules/integrations/tests/e2e/reader-access.e2e.spec.js#reads no webhook
        signing secret in any answer
      - >-
        services-platform:modules/integrations/tests/e2e/reader-access.e2e.spec.js#leaves the
        administrator’s one reading of the signing secret unused
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-013-09
    level: api
    negative: true
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Exercise the complete webhook endpoint secret lifecycle
    category: transition
    proves:
      - BR-CAP-PFM-INT-013-06
      - BR-CAP-PFM-INT-013-07
      - BR-CAP-PFM-INT-004-09
    preconditions: >-
      Prepare one independent record in each starting state, including absent-record cases where the
      model defines creation.
    steps: >-
      Run each listed transition once with its guard satisfied, then repeat it with the guard denied.
      Inspect the resulting record and listed side effects after commit.
    expected: >-
      Each positive case reaches its listed destination and records its defined side effects. Each denied
      case preserves the starting record and creates no transition side effects.
    source: >-
      rhythm-v2-docs:specs/platform/integrations/design/state-models/STATE-PFM-INT-005-webhook-endpoint-secret-lifecycle.md#transitions
    state_model: STATE-PFM-INT-005
    transition_cases:
      - from: absent
        to: active
        trigger: Provision after verification
        guard: The release declares this endpoint kind
        side_effects:
          - Generate and store a signing secret
          - Request provider setup for API registration.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-registration.ts'
      - from: active
        to: rotating
        trigger: Request signing-secret replacement
        guard: The endpoint is neither disabled nor already rotating
        side_effects:
          - Create the replacement and retain the previous reference
          - Request API re-registration and record rotation.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-registration.ts'
      - from: rotating
        to: active
        trigger: Expire signing overlap
        guard: Rotation began at least twenty-four hours earlier
        side_effects:
          - Clear the previous-secret reference.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/work/work-dispatcher.ts'
      - from: active
        to: disabled
        trigger: End the account link
        guard: The connection reaches disconnected through closure or discard
        side_effects:
          - Disable ingress for its endpoints.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-registration.ts'
      - from: rotating
        to: disabled
        trigger: End the account link during overlap
        guard: The connection reaches disconnected through closure or discard
        side_effects:
          - Disable ingress regardless of endpoint rotation status.
        provenance: proposed
        source: >-
          services-platform:modules/integrations/services/persistence/integration-webhook-endpoints.ts:disableWebhookEndpoints
    proof_boundary: >-
      The parameterized cases prove the listed lifecycle outcomes. Separate assertions are needed for
      independent object invariants, numeric limits and simultaneous requests.
  - id: ATS-PFM-INT-013-10
    level: api
    negative: true
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Exercise the complete received provider message processing
    category: transition
    proves:
      - BR-CAP-PFM-INT-013-04
      - BR-CAP-PFM-INT-013-05
    preconditions: >-
      Prepare one independent record in each starting state, including absent-record cases where the
      model defines creation.
    steps: >-
      Run each listed transition once with its guard satisfied, then repeat it with the guard denied.
      Inspect the resulting record and listed side effects after commit.
    expected: >-
      Each positive case reaches its listed destination and records its defined side effects. Each denied
      case preserves the starting record and creates no transition side effects.
    source: >-
      rhythm-v2-docs:specs/platform/integrations/design/state-models/STATE-PFM-INT-009-received-provider-message-processing.md#transitions
    state_model: STATE-PFM-INT-009
    transition_cases:
      - from: queued
        to: processing
        trigger: Claim a message-processing attempt
        guard: The numbered queued attempt is current and can be claimed
        side_effects:
          - Lease endpoint secrets and supply a short-lived raw-body address.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts:processWebhookEvent'
      - from: processing
        to: processed
        trigger: Accept verified interpretation
        guard: Signature and deduplication checks pass
        side_effects:
          - Commit interpreted events and routing work
          - Save emitted event identities.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts'
      - from: processing
        to: ignored
        trigger: Discard rejected or repeated input
        guard: Signature fails or processed event identity or digest matches
        side_effects:
          - Save no new events and update rejection telemetry.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts'
      - from: processing
        to: failed
        trigger: Schedule recovery from transient processing failure
        guard: Backoff permits another attempt below the processing ceiling
        side_effects:
          - Retain failed status with future dispatch work.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts'
      - from: failed
        to: processing
        trigger: Claim the next processing attempt
        guard: The deferred attempt is due and still current
        side_effects:
          - Repeat secret leasing and adapter verification.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts:processWebhookEvent'
      - from: processing
        to: dead
        trigger: Exhaust processing retries
        guard: The sixth numbered attempt fails
        side_effects:
          - Save dead status and emit dead-message telemetry.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts'
      - from: processing
        to: dead
        trigger: Encounter permanent processing failure
        guard: The failure class does not permit retry
        side_effects:
          - Save dead status and emit dead-message telemetry.
        provenance: proposed
        source: 'services-platform:modules/integrations/services/webhooks/webhook-worker.ts'
    proof_boundary: >-
      The parameterized cases prove the listed lifecycle outcomes. Separate assertions are needed for
      independent object invariants, numeric limits and simultaneous requests.
  - id: ATS-PFM-INT-013-11
    level: api
    negative: false
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Verified notifications become routed events
    category: interaction
    proves:
      - BR-CAP-PFM-INT-013-04
      - BR-CAP-PFM-INT-015-02
    exercises:
      - CAP-PFM-INT-013
      - CAP-PFM-INT-015
    preconditions: A provisioned endpoint and matching application consumer are available.
    steps: 'Process a valid notification, route its emitted event, then process a tampered notification.'
    expected: The valid message reaches its consumer; signature refusal emits no event.
    source:
      - >-
        services-platform:modules/integrations/tests/integration/services/webhooks/webhook-worker.integration.spec.js#rejects
        a request whose signature does not verify, writing no event
      - 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
    workflows:
      - WF-PFM-INT-015
  - id: ATS-PFM-INT-013-12
    level: api
    negative: false
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Verification provisions notification endpoints
    category: interaction
    proves:
      - BR-CAP-PFM-INT-004-05
      - BR-CAP-PFM-INT-013-06
    exercises:
      - CAP-PFM-INT-013
      - CAP-PFM-INT-004
    preconditions: A release declares a manually registered notification endpoint.
    steps: 'Complete account verification, read setup once and inspect the ordinary endpoint read.'
    expected: >-
      The account activates, setup exposes its provisioned address and secret once, and later inspection
      omits the secret.
    source:
      - >-
        services-platform:modules/integrations/tests/integration/services/connections/connection-jobs.integration.spec.js#becomes
        active with the account and every declared feature verified
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#never
        answers the secret again
    workflows:
      - WF-PFM-INT-003
  - id: ATS-PFM-INT-013-13
    level: api
    negative: false
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Credential verification establishes endpoint setup
    category: interaction
    proves:
      - BR-CAP-PFM-INT-005-05
      - BR-CAP-PFM-INT-013-06
    exercises:
      - CAP-PFM-INT-013
      - CAP-PFM-INT-005
    preconditions: An initial key account declares a manual endpoint.
    steps: 'Enter valid credentials, finish verification and inspect endpoint setup twice.'
    expected: >-
      The verification job activates the account and provisions its endpoint; ordinary reads reveal no
      signing secret.
    source:
      - >-
        services-platform:modules/integrations/services/credentials/credential-store.ts:verification_retried
      - >-
        services-platform:modules/integrations/tests/e2e-adapters/events-dummy.adapters.spec.js#never
        answers the secret again
    workflows:
      - WF-PFM-INT-003
evidence:
  - >-
    Each binding identifies an existing source test at the pinned revision; unbound scenarios remain
    proposed proof obligations.
coverage:
  rule: covered
  transition: covered
  validation: 'gap: FND-PFM-INT-076'
  permission: covered
  edge: 'gap: FND-PFM-INT-076'
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  covered: A designed scenario exists; executable coverage and a passing run require separate evidence.
  gaps: >-
    The finding records categories without a designed scenario and remaining permission and rule audit
    obligations.
---
# Retain and verify incoming provider notifications: Capability Test Specification

## Coverage

| Rule | Proved By |
|---|---|
| `BR-CAP-PFM-INT-013-01` | `ATS-PFM-INT-013-01` |
| `BR-CAP-PFM-INT-013-02` | `ATS-PFM-INT-013-02` |
| `BR-CAP-PFM-INT-013-03` | `FND-PFM-INT-076` |
| `BR-CAP-PFM-INT-013-04` | `ATS-PFM-INT-013-03`, `ATS-PFM-INT-013-10`, `ATS-PFM-INT-013-11` |
| `BR-CAP-PFM-INT-013-05` | `ATS-PFM-INT-013-04`, `ATS-PFM-INT-013-10` |
| `BR-CAP-PFM-INT-013-06` | `ATS-PFM-INT-013-05`, `ATS-PFM-INT-013-09`, `ATS-PFM-INT-013-12`, `ATS-PFM-INT-013-13` |
| `BR-CAP-PFM-INT-013-07` | `ATS-PFM-INT-013-06`, `ATS-PFM-INT-013-09` |
| `BR-CAP-PFM-INT-013-08` | `ATS-PFM-INT-013-07` |
| `BR-CAP-PFM-INT-013-09` | `ATS-PFM-INT-013-08` |

## Interactions With Related Capabilities

Provider notifications become application events only after verification and processing. The interaction cases follow valid, tampered, repeated and replayed messages through downstream routing, keeping message recovery separate from duplicate provider-event handling.

The structured scenarios name the capabilities and rules involved. A bound test contributes only the outcomes it asserts; proposed transition matrices and composed interactions still require executable proof.
