Browse pages
External Integrations
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.
Stack: any
Layer: infrastructure
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#
ruleruleguidancelitespec/ext/integrations#record-each-provider-as-an-external-systemAn integration MUST record its provider as an external system naming its operations and erasure.
Applies toartifact: record:external-system
Why
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.
Checka reviewer answers
Reviewed forEach provider has an external system page in the Behavior systems home naming operations and erasure.
Marked guidance: a reviewer answers this question.
Call Only Declared Operations#
ruleruleguidancelitespec/ext/integrations#call-only-declared-operationsA calls effect MUST name an operation the cited external system declares.
Applies toartifact: record:use-case
Why
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.
Checka reviewer answers
Reviewed forEach calls effect names an operation the cited external system declares.
Marked guidance: a reviewer answers this question.
Run Provider Calls from Event Handlers or Jobs#
ruleadviceguidancelitespec/ext/integrations#run-provider-calls-from-event-handlers-or-jobsA provider call SHOULD run from an event handler or a job, never from a command handler.
DeviationA project that keeps an inline provider call records an exception naming the compensation that undoes it.
Applies toartifact: reaction, job
Why
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.
Checka reviewer answers
Reviewed forEach provider call runs from an event handler or a job, and each inline call carries an exception naming its compensation.
Marked guidance: a reviewer answers this question.
Define Business-Action Ports#
ruleruleguidancelitespec/ext/integrations#define-business-action-portsApplication MUST own a public external-service interface named for its business action.
Applies toartifact: port
Why
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.
Checka reviewer answers
Reviewed forEach Application port names a business action with no provider in its name or signature.
Marked guidance: a reviewer answers this question.
Isolate Provider Implementations#
ruleruleguidancelitespec/ext/integrations#isolate-provider-implementationsInfrastructure MUST own provider clients, SDKs, authentication, and transport models.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich Application layer code references a provider SDK or transport type? Look at the Application layer folders.
Marked guidance: a reviewer answers this question.
Bind Client Configuration#
ruleruleguidancelitespec/ext/integrations#bind-client-configurationAn external client MUST bind its base address, credentials, timeout, and policy through validated options.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich external clients miss bound base address, credentials, timeout, or policy through validated options? Look at the client registrations.
Marked guidance: a reviewer answers this question.
Use Managed HTTP Clients#
ruleruleguidancelitespec/ext/integrations#use-managed-http-clientsAn HTTP integration MUST use HttpClientFactory and applicable service discovery.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich HTTP integrations skip HttpClientFactory or applicable service discovery? Look at the HTTP registrations.
Marked guidance: a reviewer answers this question.
Reject Per-Request HTTP Clients#
ruleruleguidancelitespec/ext/integrations#reject-per-request-http-clientsAn HTTP integration MUST NOT construct a new HTTP client for each request.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich HTTP integrations construct a new HTTP client per request? Look at the HTTP call sites.
Marked guidance: a reviewer answers this question.
Bound Transient Retries#
ruleruleguidancelitespec/ext/integrations#bound-transient-retriesAn external client MUST retry transient failures with bounded attempts and jitter.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich external clients retry transient failures without bounded attempts and jitter? Look at the retry policies.
Marked guidance: a reviewer answers this question.
Protect Non-Idempotent Calls#
ruleruleguidancelitespec/ext/integrations#protect-non-idempotent-callsAn external client MUST NOT retry a non-idempotent provider call without provider idempotency and documented replay behavior.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich non-idempotent provider calls retry without provider idempotency and documented replay behavior? Look at the retry paths.
Marked guidance: a reviewer answers this question.
Translate Provider Failure Outcomes#
ruleruleguidancelitespec/ext/integrations#translate-provider-failure-outcomesAn integration MUST map provider errors to project-owned results and stable diagnostics.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich provider errors miss their mapping to project-owned results and stable diagnostics? Look at the integration failure mappings.
Marked guidance: a reviewer answers this question.
Exclude Provider Internals#
ruleruleguidancelitespec/ext/integrations#exclude-provider-internalsAn API MUST NOT expose provider bodies, credentials, headers, exception types, or internal account identifiers.
Applies toartifact: endpoint
Why
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.
Checka reviewer answers
Reviewed forWhich API responses expose provider bodies, credentials, headers, exception types, or internal account identifiers? Look at the API responses.
Marked guidance: a reviewer answers this question.
Trust Inbound Integration Events by Declared Trust#
ruleruleguidancelitespec/ext/integrations#trust-inbound-integration-events-by-declared-trustAn inbound integration event MUST declare trust as trigger-only or payload with its correlation.
Applies toartifact: record:integration-event
Why
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.
Checka reviewer answers
Reviewed forEach inbound integration event declares trigger-only or payload trust with its correlation.
Marked guidance: a reviewer answers this question.
Verify Inbound Provider Messages#
ruleruleguidancelitespec/ext/integrations#verify-inbound-provider-messagesAn inbound provider handler MUST verify signature, timestamp tolerance, replay protection, content type, size, schema, and event identity before processing.
Applies toartifact: endpoint
Why
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.
Checka reviewer answers
Reviewed forWhich inbound provider handlers skip signature, timestamp tolerance, replay protection, content type, size, schema, or event identity checks? Look at the inbound handlers.
Marked guidance: a reviewer answers this question.
Process Duplicate Messages Safely#
ruleruleguidancelitespec/ext/integrations#process-duplicate-messages-safelyAn inbound provider handler MUST store or process duplicate events idempotently.
Applies toartifact: endpoint
Why
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.
Checka reviewer answers
Reviewed forWhich inbound provider handlers leave more than one accepted effect for a duplicate event? Look at the duplicate-delivery paths.
Marked guidance: a reviewer answers this question.
Reject Formula-Leading Import Values#
ruleruleguidancelitespec/ext/integrations#reject-formula-leading-import-valuesA CSV or spreadsheet import MUST reject or neutralize a value that begins with a formula-leading character.
Applies toartifact: any
Why
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 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.
Checka reviewer answers
Reviewed forWhich CSV or spreadsheet imports keep a formula-leading value without rejection or neutralization? Look at the import boundaries.
Marked guidance: a reviewer answers this question.
Simulate Integration Boundaries#
ruleruleguidancelitespec/ext/integrations#simulate-integration-boundariesAn integration test MUST use WireMock.Net or a protocol-specific local endpoint.
Applies toartifact: test
Why
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.
Checka reviewer answers
Reviewed forWhich integration tests call a live provider instead of WireMock.Net or a protocol-specific local endpoint? Look at the integration test suite.
Marked guidance: a reviewer answers this question.
Cover Provider Failure Modes#
ruleruleguidancelitespec/ext/integrations#cover-provider-failure-modesAn integration test MUST cover timeout, connection failure, transient response, permanent response, malformed payload, rate limit, duplicate delivery, and unexpected response shape.
Applies toartifact: test
Why
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.
Checka reviewer answers
Reviewed forWhich 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.
Marked guidance: a reviewer answers this question.
Conventions#
Group Provider Code#
conventiondefaultguidancelitespec/ext/integrations#group-provider-codePlace provider code under Infrastructure/Integrations/{Provider}/.
ReplacementA project names another root for provider code in its documentation.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forEach provider's code sits under Infrastructure/Integrations/{Provider}/, or the project names another root in its documentation.
Marked guidance: a reviewer answers this question.
Keep Provider Components Together#
conventiondefaultguidancelitespec/ext/integrations#keep-provider-components-togetherKeep one options class, client, transport models, mappings, and registration module in each provider folder.
ReplacementA project names another grouping for provider components in its documentation.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forEach provider folder holds its options, client, models, mappings, and registration, or the project names another grouping in its documentation.
Marked guidance: a reviewer answers this question.
Keep Ports Near Use Cases#
conventiondefaultguidancelitespec/ext/integrations#keep-ports-near-use-casesKeep business-action ports with their Application use cases.
ReplacementA project keeps business-action ports in one shared ports folder.
Applies toartifact: port
Why
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.
Checka reviewer answers
Reviewed forEach business-action port sits with its Application use cases, or the project keeps its ports in one shared folder.
Marked guidance: a reviewer answers this question.
Dependencies#
Packages: Microsoft.Extensions.Http.Resilience, WireMock.Net in integration tests.
Related extensions: outbox.
Guides: None.