Hub / Connectors / Connect Gateway

Connect Gateway

One shared TSANet-facing core with thin per-platform adapters, for members whose platform has no native connector. This is the machinery behind Fin / Intercom and the gateway-class connectors that follow it — and the contract a member or platform vendor builds against to write an adapter of their own.

Status Work in progress Latest release v2.0.0 First adapter Fin / Intercom (early access) Class Gateway

Status

What it is, and how far along

Members on Salesforce, Dynamics and Zendesk install a connector into their own tenant. Members on platforms that will not host member code — Intercom today; Pylon, DevRev and the like are the obvious candidates — need the integration to run somewhere else. The Connect Gateway is that somewhere: a TSANet-facing engine that owns everything on the Connect side, embedded by a thin adapter that owns everything on the platform side. Why a platform lands in this class at all is a property of the platform, not a choice — the connector classes topic has the procedure.

The engine was not designed on paper. It was built and verified end to end inside the Fin / Intercom connector first, and extraction into its own repository deliberately waited until a second platform committed — so the module boundary was fixed by two real consumers rather than one and a guess. The Fin connector now consumes the published engine artifact like any other adapter.

Where it stands: the contract, the engine, the conformance harness and the worked example are real and published. The current release is v2.0.0 (2026-09-09): the engine takes connect-library 2.0.0, and all four modules (connector-core, conformance, gateway-engine and the parent) publish at 2.0.0. It is an engine change and nothing else: the TsanetGateway contract and the conformance suite are unchanged from v1.0.0 (the connector-core and conformance code and poms are identical between the two tags), so an adapter that compiles against 1.0.0 compiles against 2.0.0 unchanged. What moved is login handling: a failed login is classified and never ambiguous, and a stale token is renewed in a login lane before the operation, under its own budget rather than the operation's deadline. The runtime line v1.0.0 set (2026-08-28), Spring Boot 4.1.1 and Jackson 3, is unchanged. Around that core, plenty is still moving; the last section is honest about what.

The pin label and the artifacts now agree. Earlier revisions of this page recorded a drift: v1.0.0 shipped while the repository README and the adapter-author onboarding still named v0.1.2 as the certification pin. The repository closed that on 2026-09-09. The member-facing documents were moved to the published version, v1.0.0 was re-certified with a live contract run against Beta (9/9), and the publish workflow now refuses to publish a version those documents do not name, so the label cannot fall behind the artifacts again. v2.0.0 was cut under that gate, documents first, and re-certified the same way on the engine tree it tags. Consume 2.0.0; the repository's pin language is current.
The repository (tsanetgit/Connect_Gateway) is currently private. Adapter authors get access at onboarding, along with the published artifacts and a self-contained authoring kit — start with membership if that is you.

Design

One engine, thin adapters, one certified seam

The design rests on a single asymmetry: TSANet knows the Connect API and its failure modes; the platform owner knows the platform. So the gateway owns the whole TSANet side — authentication, per-member sessions, partner search, process forms, submission, reconciliation, deadline enforcement, and the classification of every failure — and the adapter owns the platform side in both directions. Adapter code never implements TSANet semantics: it never decides when a submission may be retried and never interprets a Connect error. That rule is what makes it safe for someone who is not TSANet to write an adapter, because the part that pages real engineers at real partner companies is not theirs to get wrong.

you write TSANet supplies Your platform helpdesk · CRM Your adapter outbound: submit flow inbound: guard-create the only code you write TsanetGateway the published contract zero dependencies the certified seam gateway-engine sessions · deadlines error classification embedded, TSANet's Connect API The conformance harness drives your real adapter through the contract and certifies the seam: failure scenarios that are hard to reproduce on demand against a live API, reproduced on every build.
The adapter never calls the Connect API and never parses its errors — the engine is the only thing that does. The seam between them is a published contract, and the conformance harness exists to certify exactly that boundary.

The same split, at estate scale: every platform lands as one thin adapter over the same contract, and everything to the right of the contract is shared — one session registry holding per-member credentials, one engine implementation, one path to the Connect API.

Member platforms none host TSANet-side code Fin / Intercom Canvas Kit + webhooks A member's case system case APIs, headless Pylon Custom App JSON + webhooks Future platform Wolken, … Global Connect Gateway · one hosted service Platform adapters thin, one per platform Intercom adapter case-system adapter Pylon adapter … adapter Shared core exists today, proven in the Fin connector TsanetGateway contract search · form · submit · reconcile · inbound · files + the conformance suite MemberSessionRegistry per-member sessions + credentials, never shared password | client-credentials SdkTsanetGateway the one implementation generated Connect client deadlines · classification TSANet Connect API
The estate view, drawn from the gateway proposal's topology. Only the engine touches the Connect API; the registry keeps one credential set per member and never shares a client between them. One committed platform is a member's internal case system, unnamed here by this hub's redaction standard. TSANet-hosted is the default topology as designed; today the one shipped adapter, Fin / Intercom, runs as its own process with the engine embedded, and the shared runtime is at proposal stage (see below). A member-deployed adapter embeds the same engine in the member's own service instead.

The modules

connector-core

The TsanetGateway contract, its DTOs and its two exception types. Zero dependencies, compile-scope — your adapter's own code is written against it.

gateway-engine

The TSANet-facing engine an adapter embeds: the contract implementation over the Connect API, the per-member session registry, and a Spring configuration the embedding service imports. Configuration under gateway.tsanet.*; the engine wires itself only when gateway.tsanet.mode=sdk is set, and without it the application starts with no gateway bean, which is the right state for tests against the conformance double.

conformance

The harness an adapter author runs to prove their adapter survives Connect's failure modes, plus the live certification suite the certification pin was cut against.

example-adapter & example-service

A complete runnable worked example: adapter over the contract, in-memory platform, conformance suite green, and a bootable service embedding the real engine. Fake mode runs the whole story with zero credentials.

There is also a TSANet-internal admin console (read and validate, not operate) over a versioned admin contract that TSANet-hosted adapters expose; member-embedded adapters are explicitly outside its scope, so monitoring and on-call for those stay with the author. Three of the modules above — connector-core, gateway-engine and conformance — ship from the repository's package registry together with the parent POM, at the release version: 2.0.0 since 2026-09-09, alongside the older 1.0.0 and 0.1.2. The examples and the admin console are source-only, built from the repository rather than published. (Verified against the registry's package list on 2026-09-13.)

Contract

Eleven methods, and an error model that does the hard part for you

The surface is deliberately small. Outbound: searchPartners, form (fetched fresh per submission, never cached), submit, and findSubmission for reconciling by your own case number. Inbound: fetchByToken and listInboundUpdatedSince — the poll behind the push on a TSANet-hosted adapter, and the whole inbound surface for a member-embedded one, since there is no push channel from TSANet to a member-embedded adapter and the poll interval is its inbound latency bound — plus the three response actions an adapter calls when its own user answers — approve, reject and requestInformation, each taking the request token and the responding engineer's identity (approval also carries the case number and next-steps text, as the Fin sequence shows). Files: forwardAttachments outbound, and an AttachmentDeliveryHandler seam for inbound delivery. One more, healthy, for readiness. The contract has no webhook seam: as of v2.0.0 the TSANet push — signature verification, CloudEvents parsing, routing — is terminated by the adapter itself (Fin / Intercom's TsanetWebhookController), so the next adapter would carry that too until the engine takes it on.

The centre of the design is the exception model. Submitting a collaboration case is not idempotent and there is no idempotency key — a submit that fails may still have created a case at your partner, and a blind retry pages a second real engineer. So the contract splits every failure into exactly two kinds:

ExceptionMeaningWhat you do
GatewayExceptionDefinitively rejected — no case exists or can existNothing was created: show the problem detail next to the field that fixes it, and let the user correct and submit again — a blind retry cannot succeed
GatewayTimeoutExceptionOutcome unknown — deadline expiry, 5xx after send, reset mid-response, or a 200 with no tokenPark it; reconcile with findSubmission before any resubmit

The one thing most likely to catch you out

GatewayTimeoutException extends GatewayException. A single catch of the parent silently treats uncertainty as rejection — the exact failure the whole model exists to prevent:

// Wrong: also catches GatewayTimeoutException — ambiguity treated as rejection
try { gateway.submit(draft); }
catch (GatewayException e) { giveUp(); }

// Right: subclass first, and the two arms do different things
try { gateway.submit(draft); }
catch (GatewayTimeoutException ambiguous) { park(internalCaseNumber); }
catch (GatewayException rejected)        { surfaceToUser(rejected); }

The engine does the classification — a parsed 4xx, a timeout, a connection reset all arrive pre-sorted — so adapter code never inspects an HTTP status. Carry your own case identifier through retries unchanged: it is your only means of reconciling, and generating a fresh one on retry destroys it.

Adapters

Building an adapter

Two audiences, split by one question: can TSANet reach the platform? Where it can — a multi-tenant platform with a public API — TSANet builds and operates the adapter, the way Fin / Intercom shipped. Where it cannot, which is the case whenever the target is a member's own internal system, the member or vendor writes the adapter themselves against a self-contained kit: the contract, the engine artifact, the conformance harness, and an authoring guide that assumes no access to TSANet's tooling. The split is not cosmetic — one audience can probe the platform and one cannot.

The build order the kit prescribes

What the conformance suite certifies

Everything it checks exists because submission is not idempotent. The first two rows together are an exactly-once property — checking only for duplicates would pass an adapter that drops every ambiguous case.

CheckThe failure it prevents
Ambiguous submit that did create the case: exactly one caseA blind retry sends a second case to the partner
Ambiguous submit that did not create the case: exactly one caseGiving up on ambiguity silently loses the request
Two ambiguous submits in a row: still exactly one caseA retry path that is correct once and blind the second time
A reconcile that itself fails is not read as "absent"The subtlest duplicate: a lookup that threw tells you nothing
Reconcile precedes any resubmissionSeparates being correct from being lucky
A definitive rejection creates nothingRetrying an invalid request cannot succeed
The process form is fetched fresh per submissionA cached form submits fields the partner has deleted

Passing is necessary, not sufficient

The harness only sees the TSANet side of your adapter, because TSANet has no access to your platform. Your inbound path, your platform writes actually landing, credential isolation (one client per member, never shared), and test-flag discipline are yours to test.

Using the kit with an agentic coding assistant

The authoring kit is written as an agent skill, deliberately: a SKILL.md that carries the build order and the rules, plus reference files it points into — the platform questions, the binding semantics, onboarding, deploy-and-go-live, and the vendored API spec. The spec is vendored because the specification's own repository is private and deliberately not granted; the copy is pinned to a beta-branch commit, byte-identical to it on 2026-09-13, and behind the main-branch spec by the webhook subscription filters (caseDirections, partnerCompanyIds) and a few field lengths. One reference had drifted the other way: the deploy-and-go-live file named Spring Boot 3.3.4, where the v2.0.0 engine pom, which it calls authoritative, says 4.1.1; that was corrected in the kit on 2026-09-14. Loaded into an agentic assistant, it makes the assistant apply TSANet's rules while it writes your adapter, instead of you relaying them.

When onboarding grants you repository access, copy the whole kit folder — the references must ride along with the SKILL.md that cites them:

cp -R Connect_Gateway/skills/connect-adapter-author  your-adapter-project/.claude/skills/

That path is Claude Code's project skills directory; for other assistants, attach SKILL.md and the references/ files as project instructions — the kit is plain markdown and assumes no particular tool. (A second, TSANet-internal skill covers the adapters TSANet builds itself; it is not part of the member kit.) Every skill TSANet ships, and how to load one into any assistant, is collected on Agentic usage with TSANet Connect.

Typical first steps, in the kit's own order:

Boundaries the skill itself enforces — keep them. It instructs the assistant to route "ask TSANet" items to TSANet rather than inventing a local answer, and to keep submissions test-flagged until going live is a deliberate, agreed step. Both exist because the cost of getting them wrong lands on a real partner. And as everywhere: registry credentials and platform secrets never belong in an assistant session.

Attachments

The attachment plane: transit-only by design

Case files get their own set of gateway-wide decisions, made ahead of any consumer because the first runtime that references a configuration key freezes it:

The platform-wide attachment model — no store, no listing, two-party configuration — is specified in TSANET-2026-002; the gateway decisions above are the gateway's answer to it.

Field lessons

What the adapters that exist already paid for

The gateway repository keeps a lessons-learned log with a deliberate promotion path: something seen on one platform is a lesson, something seen on two is a rule, and rules move into the adapter kits where they load automatically. The entries below are the ones that matter to anyone building an adapter, each tagged with where it was seen — the same probed-versus-read discipline the platform pages use.

Probing a new platform

Published documentation is a lead, not a fact

On the Intercom spike, the vendor's own documentation was wrong or silent on the majority of the findings that shaped the design — the response deadline, the signature byte format, the behaviour on unknown fields. Reading produces a plausible design; probing produces a correct one. Budget the spike accordingly, and tag every claim with whether it was probed or merely read.

Seen on: Intercom, consistent with Zendesk

But read the vendor's page once, carefully, first

The opposite failure also costs real time: two facts that shaped the Zendesk connector — integration names are globally unique, the OAuth client is auto-created — were documented on a single page nobody had read. Probing is right when documentation is silent or suspect; reading first is right when nobody has read it yet.

Platform-independent

An endpoint's name can describe the opposite of what it does

Zendesk's "Add Tags" endpoint replaces the tag set — and because other fields were tag-backed, the first write also blanked an unrelated status field. The failure is invisible unless you look at what was there before, which is why it ran in production before anyone noticed. Where a mutation's name implies a merge, read the object before and after.

Seen on: Zendesk

A success status is not evidence that anything happened

Intercom returns 200 for a write carrying a field name it does not recognize, and ignores the field — so every downstream bug looks like application logic rather than a wire-format mistake, and you debug the wrong layer for hours. Probe for this deliberately on every new platform: send a plausible-but-wrong field name and read the object back. Where it is true, read-back-and-assert becomes a standing rule for that adapter.

Seen on: Intercom · unverified elsewhere

Permissions granted is not events subscribed

An authentication page showing every permission enabled does not subscribe a single webhook topic — topics are selected separately, and an empty topic list delivers nothing, silently, presenting exactly like a broken endpoint or a bad signature. When a webhook never arrives, check the subscription list before debugging the receiver.

Seen on: Intercom · the same split exists on several platforms

Make a probe fail once before trusting it

A probe that cannot report the negative case is measuring nothing — the estate has seen "clean" verdicts from checks that were scanning nothing at all. Before trusting a probe that returns clean, include a control you know it should catch, and check the exit status of the command you actually care about rather than the last one in a pipeline.

Platform-independent · seen repeatedly

Building and operating

A library correct in its reference embedding is not a correct library

The Connect client library worked perfectly inside Spring Boot and failed time serialization everywhere else — Boot was invisibly supplying a Jackson module the library needed. The gateway extraction's standalone certification run caught it: three of nine live contract tests failing outside Boot, the exact defect a member embedding the engine would have hit on day one. Certify an embeddable artifact outside its reference framework, and when a failure appears or disappears with the harness, suspect the classpath before the code.

Caught extracting gateway-engine, 2026-08-09

A lost push window never replays

During a Beta-estate webhook dispatch outage, events from a thirteen-hour window were permanently lost — nothing replayed on recovery, and the one live case created during it arrived only because the reconciliation poller listed it on the next cycle. The poller's cursor rests on a probed fact (a freshly created case returns updatedAt equal to createdAt, so an updated-after filter catches creations); had that been assumed and wrong, recovery would have missed exactly the new cases it exists to catch. Never build inbound on push alone: reconciliation is the source of truth, push is the latency optimization.

Seen on: TSANet Beta, 2026-08-09

A boolean cannot describe a partial success

An annotation that partly succeeded, recorded as a boolean "done", reports as fully done — and the manual recovery path stands on that record being honest. Wherever a write can partly land, the record needs at least three states; the Fin adapter's is none / degraded / full, with the degraded case audited loudly.

Platform-independent

A mutable reference in a deploy spec makes a rerun a silent no-op

A container template that references its image by tag rolls nothing when a rerun pushes genuinely new code: the platform never re-resolves a same-tag reference. Every check stays green and the old code keeps running. Pin by digest or SHA anywhere a deploy would otherwise re-resolve nothing — a git dependency pinned by branch name is the same class of mistake.

Seen on: Azure Container Apps

The newest seam is where the defect is

Across every review round on the first adapter, the blocking findings sat in whatever had been built most recently — and none were visible to unit tests. Review the newest seam hardest, and get the review from someone other than the author, because reviewing your own diff has a blind spot that care does not close.

Platform-independent

Merging is not releasing

A merged pull request ships nothing on its own where a build artifact carries its own version. Check the tag against the manifest after every merge that touches shipped assets.

Seen on: the Zendesk connector, more than once

Work in progress

What is settled, and what is still moving

AreaState
The contract, its exception model, the conformance checkssettled — unchanged through v2.0.0; certified against a live Beta run (9/9) at v2.0.0 on the same sources
The engine and the worked examplesettled — published artifacts at 2.0.0; the Fin connector consumes the published engine like any other adapter and is on 1.0.0 until its next bump
The runtime linemoving — v2.0.0 runs Spring Boot 4.1.1, Jackson 3 and connect-library 2.0.0, all in a tagged release; still open is moving the generated API calls onto the library's own HTTP client so every failure is one type
Adapters in productionone — Fin / Intercom, in early access; patterns have not yet been stress-tested by a second shipped adapter
Cross-integration conventionsconverging — the authoring kit keeps an explicit "not settled" list; those questions go to TSANet rather than being answered locally
Attachment hostingdeciding — self-service credential intake is the target state, not the day-one state, and whether TSANet ever offers production file storage is an open business decision
Hosting and sizing of the shared runtimeproposal stage — the production topology is designed, not yet operated at member scale

This page is deliberately ahead of the estate it describes — that is what the work-in-progress marker means. Expect it to change as the second and third adapters land, and read the Fin / Intercom page for what operating the first one actually looks like.

Verified against tsanetgit/Connect_Gateway at the v2.0.0 release (2026-09-09): the release notes, the published package versions on GitHub Packages, the module poms at the tag, the repository README, the conformance harness documentation, the adapter-author onboarding kit and the publish workflow's docs gate. The pin-label drift recorded in earlier revisions of this page was confirmed closed by reading the README at the tag and the gate step that enforces it. The design material this page draws on (the gateway decision log, the wiki's lessons-learned log of 2026-08-13 and the as-built architecture reference of 2026-08-13) was last checked against main on 2026-09-08 by the previous revision of this page.