Connect can now carry case text that only the two members on the case can read. The sending connector encrypts and signs it before the API call, the receiving connector verifies and decrypts it after the fetch, and the platform in between stores it without holding a key. What exists today is an API contract on every environment and one proof-of-concept connector. This page covers both, and the sharp edges already found.
The model
Without E2EE, case content is encrypted in transit and readable by the platform: Connect renders it in the web app, sanitizes it, and stores it as text. End-to-end encryption removes the platform's ability to read a named set of fields. The keys live at each member's own connector. Connect's part is to store two extra fields, return everything exactly as it was submitted, and stay out of the cryptography.
| Encrypted | Left in the clear |
|---|---|
problemSummary and problemDescription on the requesteach customFields[].valuenote summary and description
|
Who the case is from and to, the form, priority and status contact details and both internal case numbers the notes Connect writes itself (Case Created, approval, rejection, closure) attachments, which travel by file delivery and are not covered webhook events, which carry only tokens on v2 |
The field list above is what the one implementation protects. A draft specification from the TSANet E2EE workgroup defines the authoritative list, the encryption and manifest profile, a per-relationship opt-in model, and test vectors. It is not public, so this page is read from the implementation and the API, which are.
The API contract
The platform's whole contribution is two optional fields on collaboration requests and notes. manifest is an opaque Compact JWS that the receiving connector uses to verify what it got. encrypted says whether the content is encrypted. Connect stores both and returns them exactly as submitted. It never parses or changes them, and it does no cryptography.
| Schema | manifest | encrypted |
|---|---|---|
CollaborationRequestDTO (create) | optional string | boolean, default: false |
CollaborationRequestStatusDTO (read) | left out when unset | always true or false |
CaseNoteTemplateDTO (create note) | optional string | boolean, default: false |
CaseNoteDTO (read note) | left out when unset | always true or false |
GET …/{token}, the list endpoints, and the notes timeline, for sender and receiver alike.encrypted is a 400, and nothing is created. A manifest that is not a JWS is stored anyway, because the platform does not look.encrypted: false with no manifest.| Environment | Status | Evidence |
|---|---|---|
| Development | live | Round trip probed by TSANet's contract-test harness on 2026-10-12 and 2026-10-13, including the full case lifecycle. |
| Beta | live | In the served spec since 2026-10-19. The harness's API-level E2EE checks pass 10/10 there (reported 2026-10-20). |
| Production | in the spec | The served spec carries both fields since 2026-10-26, identical to Beta's. Spec-read; not probed by the hub. |
The one implementation
The Dynamics connector carries the only working implementation, in TSANETBasePlugins/E2ee. It names its profile tsanet-e2ee-1. Read here at main 0586c96 as a worked example of the shape, not as the specification.
A256GCM. The protected header binds the profile, the manifest's jti and the field's name (tsa_ver, tsa_jti, tsa_fld), so a ciphertext cannot be moved to another field or replayed onto another request.RSA-OAEP-256: once for the partner, and once for the sender's own key, so the sender can still read its own outbound case.ES256, typ: tsanet-manifest+jws, holding a SHA-256 digest of each encrypted value exactly as sent. That means one signature per request or note rather than one per field, and a single record of who sent what to whom.// manifest claims, decoded (values illustrative)
{
"ver": "tsanet-e2ee-1",
"iss": "<sending member>", "aud": "<receiving member>",
"jti": "<unique per request or note>", "iat": 1761600000,
"ref": "<sender's case number>",
"fields": [
{ "name": "problemSummary", "alg": "S256", "dig": "<base64url>" },
{ "name": "problemDescription", "alg": "S256", "dig": "<base64url>" }
]
}
Keys. The private keys live in Azure Key Vault. The plugin asks the vault to sign and to unwrap, so the private key material never enters Dynamics. The two key ids are set as the environment variables ap_SigningKeyId and ap_UnwrapKeyId. Partners' public keys come from their JWKS URLs, configured as settings: one encryptedRecipient for the partner it encrypts to, and a signingKeyCounterParty for each sender it accepts.
Its failure reasons give a checklist for any receiver you build: ManifestMissing, SignatureInvalid, BindingMismatch, FieldSetMismatch, FingerprintMismatch, NoMatchingRecipient, AeadFailure, HeaderBindingMismatch.
Adoption
Both members on a case need E2EE support for it to work. Nothing in the platform checks that the receiver can decrypt before an encrypted case is sent.
| Connector | Status | Detail |
|---|---|---|
| Dynamics | PoC | v2.14.0.0's release notes list an "End to End Encryption PoC". That release encrypts requests and custom fields. Note encryption followed on main on 2026-10-20 and is not in a release yet. |
| Connect SDK | not yet | connect-library does not expose either field, so nothing built on it can send or read them. Tracked in Connect_SDK#137, which also requires the library never to trim or re-serialize the manifest. |
| Connect Gateway · Fin / Intercom | not yet | The engine sends encrypted: false on every create and waits on the SDK. One open question is policy, not code: on a TSANet-hosted adapter, TSANet would hold the member's private key. Until the workgroup decides on that, E2EE on a gateway adapter is a self-hosting feature. |
| Zendesk · Salesforce | none | No code in either repository reads encrypted or manifest. An encrypted case sent to one of them would show its JWE JSON in place of the summary and description. That follows from the design and has not been observed. |
| Direct REST | open | Any integration can send and read the two fields today. The cryptography, the keys and the partner agreement are yours. |
Sharp edges
problemDescription and note summary and description through its HTML sanitizer whatever encrypted says. The sanitizer stores every " as ", so the value is no longer JSON and its digest no longer matches the manifest. Observed on Development on 2026-10-23 for problemDescription; problemSummary came back intact, and the note paths call the same sanitizer in the source. A fix is requested from the platform. Until it lands, a receiver has to HTML-decode a protected value that starts {" before computing its digest, as the Dynamics PoC does in PayloadFieldMap.cs. It is the same rewrite behind note content coming back as HTML.problemSummary and note summary at 500 characters, yet Development accepted a 1,237-character encrypted problemSummary. The spec and the server disagree, and the note limit has not been tested. needs probeencryptedRecipient setting is a single record, and the plugin does not compare it with the case's receiver. An org with it set encrypts every outbound case and note to that one partner's key, whoever the case is addressed to. Configure it only in an org that sends to that partner alone. Source-read, not probed.manifest.Verified against
| Read on | Source | Used for |
|---|---|---|
| 2026-10-28 | openapi.yaml as Beta and Production serve it (last modified 2026-10-19 and 2026-10-26; identical) | The four schemas, the defaults, the field length limits, the per-environment status |
| 2026-10-28 | webhook-asyncapi.yaml on the API source's Beta branch (private repository) | v2 event payloads carry tokens only |
| 2026-10-28 | MS_Power_App TSANETBasePlugins/E2ee at main 0586c96, and the v2.14.0.0 release notes | Algorithms, header and manifest claims, key custody, failure reasons, the single-recipient setting, the decode workaround |
| 2026-10-28 | Connect_SDK#137 | The SDK's status; the Beta 10/10 harness result |
| 2026-10-12 to 2026-10-23 | TSANet contract-test harness results on Development, as reported to the API team; not re-run by the hub | Round trip, lifecycle, the 400 on a non-boolean flag, the sanitizer rewrite, the 1,237-character summary |
| 2026-10-28 | Source search of Zendesk_App and SFDC_App | Neither handles the fields |
| not read | The workgroup's draft specification | Referenced only; the authoritative field list and test vectors |