---
id: STATE-PFM-INT-010
title: Integration event routing
object: DOM-PFM-INT-004
product_area: platform
module: integrations
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
  - name: pending
    meaning: The event and its routing work exist before any completed delivery.
  - name: delivered
    meaning: All selected consumers reported handling or ignoring the event.
  - name: retrying
    meaning: At least one consumer needs another attempt or could not be reached.
  - name: failed
    meaning: 'The table declares this status, but the examined router does not write it.'
  - name: dead
    meaning: The routing attempt ceiling was reached without all consumers succeeding.
  - name: no_consumer
    meaning: No matching eligible consumer registration was found.
transitions:
  - from: pending
    to: delivered
    trigger: Complete initial routing
    guard: Every selected consumer answers handled or ignored
    side_effects:
      - Retain the successful consumer identities.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
  - from: pending
    to: retrying
    trigger: Schedule another routing attempt
    guard: At least one consumer asks to retry or cannot be reached and backoff permits retry
    side_effects:
      - Save next routing dispatch work.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
  - from: retrying
    to: delivered
    trigger: Complete later routing
    guard: Every selected consumer answers handled or ignored
    side_effects:
      - Retain the successful consumer identities.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
  - from: retrying
    to: dead
    trigger: Exhaust routing attempts
    guard: The sixth attempt still has a failed consumer outcome
    side_effects:
      - Record dead-event telemetry for staff investigation.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
  - from: pending
    to: no_consumer
    trigger: Find no eligible consumer
    guard: 'No type or prefix matches, or the initiating application has no completion consumer'
    side_effects:
      - Record no-consumer telemetry and preserve the event for staff.
    who: Integration event router
    provenance: test
    source: >-
      services-platform:modules/integrations/tests/integration/services/events/event-router.integration.spec.js#lists
      the events nobody took for Rhythm HQ, across tenants
  - from: retrying
    to: retrying
    trigger: Defer another routing attempt
    guard: A consumer still cannot handle the event and the attempt ceiling remains
    side_effects:
      - Schedule the next numbered attempt.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
  - from: retrying
    to: no_consumer
    trigger: Find no consumer during retry
    guard: No currently registered consumer matches the retried event
    side_effects:
      - Save the unclaimed standing and telemetry.
    who: Integration event router
    provenance: observed
    source: 'services-platform:modules/integrations/services/events/event-router.ts:routeIntegrationEvents'
invalid:
  - from: delivered
    to: retrying
    why: A settled delivered event has no automatically scheduled next routing attempt.
  - from: no_consumer
    to: pending
    why: New consumer registration does not requeue past unclaimed events.
concurrency: >-
  Attempt claiming prevents duplicate execution of the same routing number. Consumer calls and event-result
  writes are separate boundaries; a later attempt can invoke a consumer that already acted. Receiver
  deduplication uses the stable event identity.
invariants:
  - INV-DOM-PFM-INT-004-04
  - INV-DOM-PFM-INT-004-05
evidence:
  - >-
    services-platform at ad3f6a65: modules/integrations/db/integration-events.table.ts;
    services/events/event-router.ts; services/events/complete-event.ts;
    services-platform:docs/milestones/INTG-M3-webhooks-events-deliveries.md (status Proposed)
---

# Integration event routing

## The Lifecycle

```mermaid
stateDiagram-v2
  state "pending" as s0
  state "delivered" as s1
  state "retrying" as s2
  state "failed" as s3
  state "dead" as s4
  state "no_consumer" as s5
  s0 --> s1: Complete initial routing
  s0 --> s2: Schedule another routing attempt
  s2 --> s1: Complete later routing
  s2 --> s4: Exhaust routing attempts
  s0 --> s5: Find no eligible consumer
  s2 --> s2: Defer another routing attempt
  s2 --> s5: Find no consumer during retry
```

## Transitions Explained

Routing waits for all selected consumers to handle or ignore an event. Any retry outcome schedules another numbered attempt, which may reach previously successful consumers again. The router saves unclaimed events when no eligible consumer exists; its declared failed status is not a reachable router outcome.

## What Cannot Happen

A settled delivered event has no automatically scheduled next routing attempt. New consumer registration does not requeue past unclaimed events.

## Concurrency and Timing

Attempt claiming prevents duplicate execution of the same routing number. Consumer calls and event-result writes are separate boundaries; a later attempt can invoke a consumer that already acted. Receiver deduplication uses the stable event identity.
