# External Integrations

**Stack:** any

**Layer:** infrastructure

## Intent

External integrations connect the product to providers it does not control, which can fail, change their responses, or deliver one message twice. This page states how a use case (one command or query the product offers) names the outside behavior it needs, where provider details live, how failures map to project-owned results with stable refusal codes, and how tests simulate the network boundary. A team applies it for each use case that calls a provider, receives a webhook, or imports provider-shaped data, so a provider change touches one folder instead of the whole codebase.

## Activation

Activation scope: `local`.

Applicable specification kinds: `use-case`, `rule`, `journey`.

The consumer enables `integrations` for each use case that calls an external system, emits to one, receives an inbound integration event, or uses a provider SDK.

## Baseline Relationship

This extension replaces no baseline provision.

This extension switches on no optional record type. Each provider is recorded as an external system, the core record that names its actor, operations, and erasure.

## Standards

### Record Each Provider as an External System

**ID:** `litespec/ext/integrations#record-each-provider-as-an-external-system`

**Strength:** rule

**Applies to:** artifact: record:external-system

**Statement:** An integration MUST record its provider as an external system naming its operations and erasure.

**Rationale:** The external-system record bounds the dependency: its summary names the party behind it, `litespec/core/access#declare-every-operation-on-every-external-system` names every operation the product may call, and `litespec/core/access#declare-erasure-for-every-external-system` names how personal data leaves it. A provider with no record gains new messages and fallbacks without review.

**Reviewed for:** Each provider has an external system page in the Behavior systems home naming operations and erasure.

### Call Only Declared Operations

**ID:** `litespec/ext/integrations#call-only-declared-operations`

**Strength:** rule

**Applies to:** artifact: record:use-case

**Statement:** A `calls` effect MUST name an operation the cited external system declares.

**Rationale:** Each outside call is a declared edge: the effect names the system and its operation, and the operation states the direction, timeout, retry, response, and cache. A call to an undeclared operation ships as an unreviewed dependency. `litespec/core/use-cases#declare-every-effect-with-one-verb-and-its-timing` states the effect verbs, and the `effect-undeclared` check reports an edge the records never declared.

**Example:** The payout effect cites `calls` on the payout bank `send-batch` operation with its timeout and retry count.

**Reviewed for:** Each calls effect names an operation the cited external system declares.

### Run Provider Calls from Event Handlers or Jobs

**ID:** `litespec/ext/integrations#run-provider-calls-from-event-handlers-or-jobs`

**Strength:** advice

**Applies to:** artifact: reaction, job

**Statement:** A provider call SHOULD run from an event handler or a job, never from a command handler.

**Deviation:** A project that keeps an inline provider call records an exception naming the compensation that undoes it.

**Rationale:** A command handler changes its aggregate and nothing else; each consequence runs after the commit, as `litespec/backend/application#call-external-systems-through-their-ports` requires. An event handler or a job calls the provider after the outbox delivers the fact, with one delivery per handler under `litespec/ext/outbox#deliver-each-event-to-each-handler-separately`, so a provider failure retries without rolling back the accepted change. A call whose result the command needs before it can answer records the intent first, and an event handler calls the provider; a project that keeps the call inline records an exception naming the compensation that undoes it.

**Example:** The reimburse-expense command records a pending payout on the expense, and the event handler for the recorded fact calls the payout provider through the business-action port.

**Reviewed for:** Each provider call runs from an event handler or a job, and each inline call carries an exception naming its compensation.

### Define Business-Action Ports

**ID:** `litespec/ext/integrations#define-business-action-ports`

**Strength:** rule

**Applies to:** artifact: port

**Statement:** Application MUST own a public external-service interface named for its business action.

**Rationale:** The use case needs a behavior, such as sending a payout, not a provider. A public interface named for that business action states the need with no provider in sight. Application then depends on the behavior while providers come and go behind it.

**Example:** `IPayoutSender` describes the business action without exposing a payout-bank client.

**Reviewed for:** Each Application port names a business action with no provider in its name or signature.

### Isolate Provider Implementations

**ID:** `litespec/ext/integrations#isolate-provider-implementations`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** Infrastructure MUST own provider clients, SDKs, authentication, and transport models.

**Rationale:** Provider clients, SDKs, authentication, and transport models name provider things in provider terms. Infrastructure owns all of them, outside Application's business boundary. A provider SDK upgrade then touches Infrastructure instead of the use case.

**Example:** `IPayoutProviderClient` is provider infrastructure rather than an Application port.

**Reviewed for:** Which Application layer code references a provider SDK or transport type? Look at the Application layer folders.

### Bind Client Configuration

**ID:** `litespec/ext/integrations#bind-client-configuration`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An external client MUST bind its base address, credentials, timeout, and policy through validated options.

**Rationale:** Base address, credentials, timeout, and policy decide where the client calls and how it behaves. Binding them through validated options checks each value at startup. A missing or invalid value fails fast instead of misdirecting the first live call.

**Reviewed for:** Which external clients miss bound base address, credentials, timeout, or policy through validated options? Look at the client registrations.

### Use Managed HTTP Clients

**ID:** `litespec/ext/integrations#use-managed-http-clients`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An HTTP integration MUST use `HttpClientFactory` and applicable service discovery.

**Rationale:** `HttpClientFactory` centralizes handler lifetime, and service discovery resolves provider addresses in one place. Both keep HTTP construction out of the use case. A hand-built client in each call site would scatter both concerns.

**Reviewed for:** Which HTTP integrations skip `HttpClientFactory` or applicable service discovery? Look at the HTTP registrations.

### Reject Per-Request HTTP Clients

**ID:** `litespec/ext/integrations#reject-per-request-http-clients`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An HTTP integration MUST NOT construct a new HTTP client for each request.

**Rationale:** A new HTTP client per request skips the central handler configuration and opens sockets the process never reuses. Under load, the exhausted sockets fail calls that the provider would have served. Managed clients reuse what per-request construction throws away.

**Reviewed for:** Which HTTP integrations construct a new HTTP client per request? Look at the HTTP call sites.

### Bound Transient Retries

**ID:** `litespec/ext/integrations#bound-transient-retries`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An external client MUST retry transient failures with bounded attempts and jitter.

**Rationale:** A provider outage tempts every caller to retry at once, and the synchronized wave hits the provider the moment it recovers. Bounded attempts cap the load and jitter spreads it over time. An unbounded retry turns each caller into a second outage.

**Reviewed for:** Which external clients retry transient failures without bounded attempts and jitter? Look at the retry policies.

### Protect Non-Idempotent Calls

**ID:** `litespec/ext/integrations#protect-non-idempotent-calls`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An external client MUST NOT retry a non-idempotent provider call without provider idempotency and documented replay behavior.

**Rationale:** Retrying a payout, a shipment, or any irreversible provider call can perform it twice. The retry is safe only with provider idempotency behind it and documented replay behavior stating what a repeat does. Without both, the project trades one failed call for two completed effects.

**Reviewed for:** Which non-idempotent provider calls retry without provider idempotency and documented replay behavior? Look at the retry paths.

### Translate Provider Failure Outcomes

**ID:** `litespec/ext/integrations#translate-provider-failure-outcomes`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An integration MUST map provider errors to project-owned results and stable diagnostics.

**Rationale:** Each provider reports failure in its own codes and shapes. Mapping them to project-owned results with stable diagnostics keeps the project's failure contract steady while providers change theirs.

The project's own error catalog holds the stable codes. `litespec/backend/api#return-stable-problem-details` returns a code from that catalog. An integration that invents one code per provider creates one code space per provider. A client that branches on failure learns every code space. A provider's own error taxonomy is the input to the mapping, not its output.

**Example:** A rejected payout file and an unsettled batch both map to the project's `payout-refused` code. The provider's own code travels in diagnostics.

**Reviewed for:** Which provider errors miss their mapping to project-owned results and stable diagnostics? Look at the integration failure mappings.

### Exclude Provider Internals

**ID:** `litespec/ext/integrations#exclude-provider-internals`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** An API MUST NOT expose provider bodies, credentials, headers, exception types, or internal account identifiers.

**Rationale:** Provider bodies, headers, exception types, and account identifiers carry secrets, implementation details, and sometimes another account's data. An API that returns them leaks all three to its callers. The mapped project-owned outcome carries the meaning without the internals.

**Reviewed for:** Which API responses expose provider bodies, credentials, headers, exception types, or internal account identifiers? Look at the API responses.

### Trust Inbound Integration Events by Declared Trust

**ID:** `litespec/ext/integrations#trust-inbound-integration-events-by-declared-trust`

**Strength:** rule

**Applies to:** artifact: record:integration-event

**Statement:** An inbound integration event MUST declare `trust` as trigger-only or payload with its correlation.

**Rationale:** Readers place one of two trusts in an inbound fact: trigger-only wakes a read of the source, while payload is fit to act on. The trust states which one, and the correlation states how the event matches, so no handler guesses how far to follow a stranger's fields. `litespec/core/edges#describe-every-integration-event-for-its-direction` states the record, and `litespec/core/edges#reach-every-inbound-integration-event-through-a-webhook` gives each inbound fact one door.

**Reviewed for:** Each inbound integration event declares trigger-only or payload trust with its correlation.

### Verify Inbound Provider Messages

**ID:** `litespec/ext/integrations#verify-inbound-provider-messages`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** An inbound provider handler MUST verify signature, timestamp tolerance, replay protection, content type, size, schema, and event identity before processing.

**Rationale:** An inbound message arrives from the network, where anyone can send bytes. Signature, timestamp tolerance, replay protection, content type, size, schema, and event identity each guard a different part of that trust boundary. Skipping any one leaves its attack open.

Order matters here. Verify the signature first, over the raw body, before parsing the body or running any other check. An unauthenticated caller can reach a parser that runs first. A deserialized body is no longer the bytes that the signature covered.

**Example:** A failed signature returns `401` with no body. A message that names the failed check tells an unauthenticated caller how to pass it.

**Reviewed for:** Which inbound provider handlers skip signature, timestamp tolerance, replay protection, content type, size, schema, or event identity checks? Look at the inbound handlers.

### Process Duplicate Messages Safely

**ID:** `litespec/ext/integrations#process-duplicate-messages-safely`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** An inbound provider handler MUST store or process duplicate events idempotently.

**Rationale:** A provider repeats delivery after a timeout or an acknowledgement failure, so the same event arrives twice. Storing or processing duplicates idempotently leaves one accepted effect. A handler without that protection pays, ships, or notifies twice.

**Reviewed for:** Which inbound provider handlers leave more than one accepted effect for a duplicate event? Look at the duplicate-delivery paths.

### Reject Formula-Leading Import Values

**ID:** `litespec/ext/integrations#reject-formula-leading-import-values`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A CSV or spreadsheet import MUST reject or neutralize a value that begins with a formula-leading character.

**Rationale:** The formula-leading characters are the equals sign, the plus sign, the minus sign, the at sign, the tab, and the carriage return. Escaping when you export protects the reader of that export. It does not protect the reader of the next export. A different report can re-export an unchanged imported value without escaping it. [CSV injection](https://owasp.org/www-community/attacks/CSV_Injection) travels through the store, so the boundary that admits the value is where it stops.

**Example:** An import that rejects the value reports the row and the field. An import that neutralizes it records what it changed. A silently altered value is a data defect nobody can trace.

**Reviewed for:** Which CSV or spreadsheet imports keep a formula-leading value without rejection or neutralization? Look at the import boundaries.

### Simulate Integration Boundaries

**ID:** `litespec/ext/integrations#simulate-integration-boundaries`

**Strength:** rule

**Applies to:** artifact: test

**Statement:** An integration test MUST use WireMock.Net or a protocol-specific local endpoint.

**Rationale:** A live provider account makes tests slow, flaky, and expensive, and some failure modes never occur on demand. WireMock.Net or a protocol-specific local endpoint simulates the network contract under test control. The suite then covers provider behavior without calling the provider.

**Reviewed for:** Which integration tests call a live provider instead of WireMock.Net or a protocol-specific local endpoint? Look at the integration test suite.

### Cover Provider Failure Modes

**ID:** `litespec/ext/integrations#cover-provider-failure-modes`

**Strength:** rule

**Applies to:** artifact: test

**Statement:** An integration test MUST cover timeout, connection failure, transient response, permanent response, malformed payload, rate limit, duplicate delivery, and unexpected response shape.

**Rationale:** Timeout, connection failure, transient and permanent responses, malformed payloads, rate limits, duplicate delivery, and unexpected shapes each exercise a different path through retry, mapping, and idempotency. A mode without a test leaves its path unverified. The suite covers all eight so each path proves itself.

An unexpected response shape is the mode a provider produces without failing. A well-formed response can carry a field that moved, a type that changed, or a value no one planned for. The response parses. The behavior is still wrong. Every other listed mode reports that response as an error. A provider that versions its API changes shape on a schedule. A pinned version still changes on the day the provider raises it.

**Reviewed for:** Which declared failure modes lack a test: timeout, connection failure, transient response, permanent response, malformed payload, rate limit, duplicate delivery, or unexpected response shape? Look at the integration test suite.

## Conventions

### Group Provider Code

**ID:** `litespec/ext/integrations#group-provider-code`

**Strength:** default

**Applies to:** artifact: any

**Default:** Place provider code under `Infrastructure/Integrations/{Provider}/`.

**Replacement:** A project names another root for provider code in its documentation.

**Rationale:** Each provider's transport code lives under `Infrastructure/Integrations/{Provider}/`. One folder per provider keeps provider code inside one Infrastructure boundary. A reader finds everything about a provider in one place.

**Reviewed for:** Each provider's code sits under `Infrastructure/Integrations/{Provider}/`, or the project names another root in its documentation.

### Keep Provider Components Together

**ID:** `litespec/ext/integrations#keep-provider-components-together`

**Strength:** default

**Applies to:** artifact: any

**Default:** Keep one options class, client, transport models, mappings, and registration module in each provider folder.

**Replacement:** A project names another grouping for provider components in its documentation.

**Rationale:** Options, client, transport models, mappings, and the registration module change together when the provider changes. Keeping them in the provider folder keeps each change in one place. A registration module stored elsewhere is the file every provider change forgets.

**Reviewed for:** Each provider folder holds its options, client, models, mappings, and registration, or the project names another grouping in its documentation.

### Keep Ports Near Use Cases

**ID:** `litespec/ext/integrations#keep-ports-near-use-cases`

**Strength:** default

**Applies to:** artifact: port

**Default:** Keep business-action ports with their Application use cases.

**Replacement:** A project keeps business-action ports in one shared ports folder.

**Rationale:** A business-action port exists because its use case needs that behavior. Keeping the port with its Application use cases states the need next to the needer. A ports folder far from the use cases hides which behavior each port serves.

**Reviewed for:** Each business-action port sits with its Application use cases, or the project keeps its ports in one shared folder.

## Dependencies

Packages: `Microsoft.Extensions.Http.Resilience`, `WireMock.Net` in integration tests.

Related extensions: `outbox`.

Guides: `None.`
