A managed package that installs entirely into your own org: custom objects, Apex services, Lightning Web Components, and a library of Flows you can clone. The most configurable connector TSANet ships — and the one where the automation surface is a Flow library you clone and edit, where Dynamics gives you a configuration table and Zendesk its triggers.
Connector class
TSANet connectors divide by one question: will the platform run your code inside the member's own tenant? Salesforce is the fullest yes on the list — Apex is real server-side code, running on Salesforce's infrastructure, inside your org, under your governance. Intercom is the opposite pole, where nothing can run in-tenant at all and TSANet hosts a gateway; Zendesk sits in between, with client-side apps and declarative flows but no arbitrary server code.
| Where it runs | What that buys you | |
|---|---|---|
| Business logic | Apex, in your org | Real server-side code with governor limits, tests and debug logs |
| Automation | Flow, in your org | Clone and edit any shipped flow without writing Apex |
| Inbound delivery | Apex REST on a public Site | Push, not polling — plus a scheduled sweep as backstop |
| State | Custom objects | Reportable, dashboard-able, and subject to your sharing model |
| Credentials | Custom object in your org | Never leaves your tenant |
Architecture
Four layers, all inside the org: Lightning components the agent works in, a Flow library that drives the business logic, Apex services that talk to TSANet, and custom objects that hold the state. Two integration points cross the org boundary — an outbound scheduled sync and an inbound Apex REST endpoint.
TSANetCase__c — Accept, Reject, Request Information, Send Information, Create Note, Close, Upload File — each launch either a screen flow or an Aura action. Close is valid on outbound cases only: the API rejects a closure from the receiving company.HttpClient, with the environment host resolved from TSANetEnvironment__mdt and a matching Remote Site Setting.TSANetScheduledJob sweeps every 15 minutes by default, reconciling anything the push path missed.Data model
As on Dynamics, and unlike Zendesk and Fin / Intercom, collaboration state here lands in first-class Salesforce objects. That is the single biggest practical difference: the data is reportable, dashboard-able, subject to your sharing rules, and addressable from any Flow or Apex you write yourself.
The collaboration record. Status, Type (inbound/outbound), Priority, Summary, Description, Request Date, Response SLA, both parties' company and engineer contact details, and a lookup to the standard Case. Token is the ExternalID.
Notes on a case — summary, description, creator, status, priority, created-at. Token is the ExternalID, which is what makes upserts idempotent.
Formal replies and actions on inbound cases: type, note, engineer email and phone, created-at.
Username, password, environment (DEVELOPER, BETA, PRODUCTION), an integration-user designation, and isPrimary — of which exactly one per org may be true.
Org-default custom setting holding the access token in two parts plus its expiry. Not something you edit by hand.
The inbound subscription: parent credential, active flag, callback URL and selected event types. A child of the credential record.
Where ErrorLogger writes. First place to look when a webhook or a sync cycle misbehaves.
TSANetCaseUpdated__e and TSANetNoteFeedItemRequested__e. The former lets an open LWC show a live indicator while an async job is still processing an inbound event.
TSANetEnvironment__mdt and TSANetCustomField__mdt are custom metadata, not data — they carry environment hosts and field-mapping configuration, and they deploy with the package rather than living in your data.
Authentication
Auth is basic authentication against a TSANet API user on the member account, exchanged for a JWT that lives about 60 minutes. That is the API's legacy scheme: OAuth 2.0 client credentials is the current server-to-server path, and it removes the token handling described below — see Working with the API → Authentication. The managed package as documented here uses the login path. Storing and using that token runs into two Salesforce constraints that are worth understanding before you debug anything in this area.
Custom setting text fields cap well below the length of a Connect JWT, so AccessTokenSplitter splits the token at 175 characters into AccessTokenPart1__c and AccessTokenPart2__c, and TokenManager concatenates them on read. If you are inspecting stored credentials and see a truncated-looking token, that is why — check both parts.
Salesforce refuses a callout when there is uncommitted DML in the transaction. A naive implementation — log in, save the token, then immediately call the API — hits You have uncommitted work pending on the very first request. TokenManager therefore holds a freshly minted token in a static in-memory field and defers persistence until after the callouts complete.
// getAccessToken() returns the pending token without touching the database
// commitTokenIfPending() runs after the callouts, not before
String token = TokenManager.getAccessToken(); // no DML
... callouts ...
TokenManager.commitTokenIfPending(); // DML now safe
If you extend the package with your own Apex callouts, follow the same ordering. Persisting the token first is the natural thing to write and it will fail.
The environment is selected on the credential record and resolved through TSANetEnvironment__mdt at runtime. Each environment also needs its matching Remote Site Setting, which the package ships — a callout to a host with no Remote Site Setting is refused by the platform before it leaves the org.
| Credential environment | Host |
|---|---|
BETA | connect2.tsanet.net |
PRODUCTION | connect2.tsanet.org |
DEVELOPER | TSANet-operated development environment, by arrangement |
Inbound
TSANet cannot authenticate into your org, so inbound events land on an Apex REST endpoint exposed through a Salesforce Site running as a guest user. This is the part of the setup with the most moving pieces and by far the most common source of installation failures.
TSANet Webhook Guest Access permission set assigned to that Site's guest user. This is the 403 cause. Find the guest user under Setup → Sites → your site → Public Access Settings → View users.TSANetWebhook__c record under the credential, Active, with the callback URL set to <Site Base URL>/services/apexrest/tsanetconnect/webhook and both event types selected — an empty event-type list delivers nothing, silently.TSANetError__c have rows. A trace flag on the integration user with debug level SFDC_DevConsole covers what the error object does not.Scheduled sync
TSANetScheduledJob reconciles Salesforce against TSANet every 15 minutes by default, catching anything the push path missed. Changing that interval runs into a platform quirk that surprises everyone who has written a crontab.
*/X or 0/X increments. There is no "every 15 minutes" expression. A single schedule fires at most once per hour, at one specific minute — so a 15-minute cadence is four separate scheduled jobs, a 10-minute cadence is six, and a 5-minute cadence is twelve.# Every 15 minutes — four schedules, not one
0 0 * * * ?
0 15 * * * ?
0 30 * * * ?
0 45 * * * ?
# Use ? for Day-of-Month or Day-of-Week when the other is specified
Manage them under Setup → Environments → Jobs → Scheduled Jobs. Before shortening the interval, weigh it against your org's Apex governor limits — every schedule is an independent job making its own callouts.
Automation
This is what makes the Salesforce connector different in kind from the others. The package ships a library of Flows covering every lifecycle action, in both screen and autolaunched form, and every one of them is clonable. A member can change routing, approval policy, notification recipients and case-matching logic without writing Apex — the Apex is exposed as invocable actions that Flow calls as building blocks.
The most-copied pattern in the library, and a good template for your own policy. The record-triggered flow fires when a TSANet Case is created with no Salesforce Case linked, direction inbound, and a serial number present:
Get Asset by Serial Number → no match? reject the request
Get Contact by Customer Email → optional enrichment
Create Salesforce Case → Asset, Contact, Subject, Description
Update TSANet Case → attach the new Case Id
Approve Collaboration Request → invocable Apex, with engineer details
Send Custom Notification → to the case owner
Rejecting on no asset match is the entitlement check: a partner escalating about hardware you have no record of does not become a case your team has to triage. Adapt the matching step — contract, entitlement, account tier — and the rest of the flow is unchanged.
TSANet_Notification. A flow that silently does nothing at the notification step is usually a missing notification type.Setup
membership@tsanet.orgTSANetApplication component to the Case record page in Lightning App Builder, then activate and assignOrganizations that would rather not use the LWC can reach the same actions through page layouts instead — override the predefined actions in Salesforce Mobile and Lightning Experience Actions and add the TSANet Case Menu.
Gotchas
*/X in Salesforce cron. Every interval shorter than an hour means multiple schedules. See above.X-Hub-Signature-256, and the Dynamics, Zendesk and Fin / Intercom pages each verify a secret or signature; whether the package verifies it has not been re-probed. The only controls documented on this endpoint are Site activation and guest permissions.collaboration-request.created and note.created — are the whole set the platform delivers; the three v2-only types (closed, response.created, response.updated) never reach a v1 subscription, so closures and responses arrive through the scheduled sync.TokenManager does.GET /v1/collaboration-requests and the /v1/webhooks subscription paths. The API page records this connector on v1 webhooks (documented, not re-probed), and the deprecated v1 list accepts updatedAfter too, so the sync keeps working until the sunset; which list path the package calls has not been re-probed. See deprecation clocks.Accept: application/problem+json, and there is no idempotency key on create — a retried create is a second case. Both on Working with the API.TSANetError__c first. It is where ErrorLogger writes, and it is faster than a trace flag for most failures.