Configuring a connector
The configuration form, how your credentials are encrypted, what the test button actually proves, and the exact messages you will see when something is wrong.
This article covers what happens when you fill in an integration's settings and press save: what the form is built from, where your API token ends up, what the test step proves, and the exact words you will see when something goes wrong. If you are searching for an error message you just saw, the tables below have it verbatim.
Finding the button, and what to do when there is not one
Open an entry in the catalogue and you get a panel headed What you can set up, listing that entry's capabilities one per row. The button lives on the capability, not on the entry. There is deliberately no single Configure button at the bottom of the panel any more, because on most entries it would have been a lie: it would have started the connector form for a row whose only real capability was single sign-on.
So the first thing to do is read the row you actually want. Three things can appear at the end of it.
- Configure
- The capability is backed by a connector. Pressing it opens the credential form described in the rest of this article — a name, the connector's own fields, a test and a save.
- Set up
- The capability is single sign-on, SAML or OIDC. It opens a different form entirely: you are registering an application, not storing a credential. There is nothing to encrypt, so there is no secret field, and the thing you get back at the end is a set of values to paste into the other product's admin console. That flow is covered in Single sign-on with SAML and Single sign-on with OIDC.
- A paragraph instead of a button
- The capability cannot be configured, and the sentence in that space is the reason, printed in full rather than summarised. It will tell you whether the missing piece is an endpoint, a worker, or a configuration schema — and, where one exists, which direction does work instead.
What the form is made of
Every connector declares its own settings as a list of fields, and the interface renders the form from that declaration. Each field carries a key, a label, a type, help text, an optional placeholder and default, whether it is required, whether it is secret, an optional pattern with a human-readable hint, and a link to the vendor's own documentation.
That declaration exists for a specific reason, recorded in the code that introduced it:
Before this existed, Configure() took an opaque JSON string and every connector decoded it into a private struct. Nothing outside the connector knew a Jira integration wants a site URL, an email and an API token — so the configuration UI had no source of truth to render a form from… That is why configuring an integration did not work: not a missing screen, a missing contract.
Help text is mandatory on every field for a related reason. client_secret means nothing to an administrator looking at the Azure portal for the first time, and an integration that fails because the wrong one of four similar-looking identifiers was pasted in is discovered during an incident rather than during setup.
The field types are text, secret, textarea, url, email, number, boolean and select. Fields marked advanced are collapsed by default; you can usually ignore them.
Secret and not-secret are decided for you
You submit one flat set of values. The server splits them: anything the connector declared as secret goes into the write-only credential store, and everything else is kept as ordinary configuration. The browser does not decide which is which, and neither do you — that knowledge lives once, next to the connector that needs it.
The split matters because the two are stored differently. Ordinary configuration is a JSON column that appears in query logs, EXPLAIN output and backups. Credentials are encrypted. Putting a token in the wrong half would be a security defect, so it is not a decision left to a form.
Validation tells you everything at once
The server checks your submission against the connector's schema and reports every problem in one response, attached to individual fields rather than as one anonymous banner. An administrator filling in six fields should not have to submit six times to learn about six mistakes. This runs on the server even though the browser also checks, because the API is reachable without the browser.
Unknown keys are rejected, not ignored. A misspelled key that is silently dropped produces an integration which authenticates with a missing setting and then fails later with an opaque 401 — the silent failure this whole surface exists to remove.
| Message | When you see it |
|---|---|
is required | A required field was left blank and has no default. |
must be a full URL including https:// | A URL field that would not parse, or has no host. |
must be an http or https URL | A URL with some other scheme. |
must use https | A URL that parsed as http://. Plain HTTP is refused. |
must be a valid email address | An email field that is not an address. |
must be a whole number | A number field containing something else. |
must be "true" or "false" | A boolean field with an unparseable value. |
must be one of: … followed by the allowed values | A select field with a value outside its options. |
is not in the expected format | A pattern mismatch on a field whose pattern has no friendlier hint. |
is not a setting this connector accepts | A key the connector never declared. |
One deliberate omission: if the pattern we wrote is itself malformed, your value is accepted rather than rejected. A bad pattern is a bug in our catalogue, not in your input, and refusing the value would blame the wrong party.
Where your token actually goes
Secret fields are stored with envelope encryption. In plain terms: each stored secret gets its own encryption key, and that key is itself encrypted under the master key held for your cell in a key management service. Nothing decrypts a tenant's secret without going through the master key, and rotating the master key re-wraps the per-secret keys without touching the encrypted data.
The integration store does not implement this itself; it reuses the same vault the privileged access module uses for credentials. That means integration secrets inherit master-key rotation for free, and there is one place in the codebase where a mistake in this area can be made rather than two.
- There is no read-back. Credentials are write-only over the API. Read endpoints return only the names of the fields that exist and the expiry date, never a value. There is no code path by which a stored secret leaves the process except to the upstream provider it belongs to.
- A deployment without encryption refuses to store anything. If no key wrapper is configured, saving a credential fails with
credential encryption is not configured on this deployment; refusing to store a secretand HTTP 503. The reasoning is blunt: a code path that can store an API token unencrypted will eventually run in production. - Empty values are refused, not stored.
integration store: refusing to seal an empty secretandintegration store: secret field "…" is emptyexist because an empty secret is almost always a form that did not submit, and storing it produces an integration that fails at its first real call with an opaque 401. - The plaintext is wiped from memory after sealing and after opening. Best effort, to shorten the window in which a crash dump would contain your API token.
The test step, and what it proves
You can test a configuration before it exists. The test endpoint stores nothing — no integration record, no encrypted credential, no health event. Your secrets exist only for the life of that one request. It gives up after 25 seconds.
The response is {ok, status, message, latency_ms, checked_at, hint} and it returns HTTP 200 even when the connection fails, because the test ran and produced an answer. A failed connection is a result, not a malformed request. Read the ok field, not the status code.
| Message | Status | Meaning |
|---|---|---|
Connected. | 200, ok: true | Credentials worked and the provider answered. |
The provider rejected these credentials. | 200, ok: false | Authentication failed. The hint suggests checking the secret was copied in full and has not expired or been revoked. |
The provider is rate limiting us. This is usually temporary. | 200, ok: false | Hint: Wait a minute and test again. |
Could not reach the provider. | 200, ok: false | Hint: check the URL or tenant identifier, and that the provider is not blocking traffic from this region. |
This connector does not support a connection test. | 200, ok: false | The connector has no probe. Rare among the eight. |
the connection test did not succeed | 200, ok: false | The health check reported a problem but supplied no message of its own. |
the connector's health check returned no result, which is a bug in the connector rather than in your settings | 200, ok: false | Ours to fix. Please report it. |
connector_id is required | 400 | The request named no connector. |
no catalog entry with connector id … | 404 | The identifier does not exist in the catalogue. |
this integration is not built yet and cannot be configured | 409 | A planned row. Nothing to test. |
this integration has no health check: it is configured through a template surface, whose health is proven by its last successful use | 409 | A template-driven application rather than a connector. |
check the values above; the connector could not be configured with them | 400 (as hint) | The values are internally inconsistent. A configuration problem, not a network one — do not go and check your firewall over a typo. |
Saving, and rotating a credential later
Save runs the test first, by default
Creating or updating an integration tests it before storing, with a 20-second health check, and refuses to save if the test fails:
not saved:followed by the detail, with the hintpass test_before_save=false to store the configuration anyway. If no detail is available the default isthe connection test failed. The escape hatch exists for the case where you know the provider is briefly down and you want the configuration stored regardless.A failure to encrypt rolls the whole thing back
If the credential cannot be stored securely you get
failed to store credentials securely:plus the cause, HTTP 500, and the integration record is deleted again. An integration with no credential is a row that will fail every operation, so it is not left behind.Rotating replaces the secret, and tests it too
Sending new credentials to the rotate endpoint validates them the same way and refuses on failure with
not rotated:plus the detail, and the hintpass test=false to store them anyway. The default detail isthe new credentials did not work.credentials is required and may not be emptyis what you get for an empty body.Do not put the same key in both halves
If a field appears in both the configuration and the credentials, the request is refused with
field "…" is present in both config and credentials; remove it from one of them. A collision is refused rather than silently resolved, because whichever one the server picked would be a guess about which you meant.
Two more messages worth recognising. this integration is disabled (409) means the record exists but is switched off. this connector does not synchronise a directory (409) means you asked for a sync from something that only sends notifications. And if the deployment has no integration storage wired at all, every one of these routes answers 503 with integration persistence is not configured on this deployment; call handler.SetStore during startup to enable configured integrations — deliberately explicit, rather than a 500 or a silently empty list.
Note that running a connection test requires write permission on integrations, not read. It makes an outbound request on your organisation's behalf; it stores nothing, but it can be used to probe, so it is gated as an action rather than as a view. See Roles and permissions.
What you can do to a connection after it is saved
A saved connection appears twice: back on the catalogue entry you configured it from, and on the connections screen that lists everything you have set up. Both give it the same three actions, and it is worth knowing which of them touches the provider and which only touches our record of it.
| Action | What happens | Reversible? |
|---|---|---|
| Test connection | Runs the health check against the configuration that is currently saved — not against anything you have typed but not saved. Stores nothing. Needs write permission, because it makes an outbound call on your behalf. | Nothing to reverse. |
| Edit | Re-opens the same form, filled in from the stored configuration, with the save button reading Save changes. Saving runs the test again, so a change that breaks the connection is refused rather than stored. | Yes — edit it again. |
| Disconnect | Deletes the record and the stored credentials with it. Everything relying on that connection stops. | No. You re-create it from the catalogue and enter the credentials again. |
Disconnect {name}? The stored credentials are deleted and anything relying on this connection stops working.
Two things surprise people here. The first is that Edit is not offered on every connection. Some integrations were configured through a template surface rather than a credential form, and those have no form to re-open; pressing Edit on one produces This connection has no editable configuration form in this build. That is a genuine limitation, not a permissions problem — to change one of those you disconnect and set it up again.
The second is what happens to secrets when you edit. A stored secret is never sent back to your browser, so the field cannot be pre-filled. It is shown empty, with the note already stored — leave blank to keep it, or type a new value to replace it. Leaving it blank is the normal case. This is also how you rotate a token without disturbing anything else: edit, type the new value into the one secret field, save, and the test runs against the new value before it replaces the old one.
Alongside those three there is Sync now on connections that read a directory, which is disabled while the connection is switched off, and Recent checks, an expandable history of the automatic health checks. The platform runs one every 15 minutes, treats a directory sync as stale after 48 hours, and warns 14 days before a stored credential expires. Connector health and silent failure covers what those signals mean.
What to do next
- Connector health and silent failure — what happens to this integration after you close the tab.
- Where notifications go — configuring a Slack connector is not the same as being notified in Slack, and this article explains the gap.
- Outbound webhooks — if none of the eight connectors fits, this is the general-purpose route.