WorkOS Pipes: Where your users' tokens actually live
A walk through the Pipes credential vault: envelope encryption, key context, what a compromised key reaches, and how key rotation happens without re-consent.
Your app has a "Connect Slack" button. Behind it, a refresh token for your user's workspace is sitting on a disk somewhere. That token is an API key to their data, and every security review eventually arrives at the same three questions: where is it, which key protects it, and what happens if that key is compromised.
WorkOS Pipes handles the OAuth flow, the refresh loop, and the credential storage for those connections. Here is what that means at the storage layer, in enough detail to answer a security questionnaire without hand-waving.
The connection record and the credential are two different things
When a user authorizes a provider, Pipes creates a connected account: the record of what they granted, which is separate from the secret itself. Read one back and you get metadata.
The record includes scopes, auth method, state, and last-four fragments, api_key_last_4 and client_secret_last_4, but never the credential values themselves. The connection is also scoped, either to an organization or to the user alone, and lookups require an exact match on that scope. The same shape covers every provider in the Pipes catalog, and a custom internal app connects the same way, over OAuth, an API key, or client credentials.
The credential itself lives in WorkOS's credential vault, tied to the connection's user, and to the organization when the connection is org-scoped, encrypted at rest with AES-256. PKCE is supported with the S256 challenge method and configured per provider, and Pipes inherits the platform's SOC 2, HIPAA, and GDPR posture.
If those tokens are currently in your own database, you can hand them over directly. Creating a connected account accepts existing OAuth tokens, which is the supported path for migrating connections from another system. Send the access token and the refresh token together: an access_token with an expires_at but no refresh_token is rejected with a 422, as is an expires_at with no access token. The update endpoint deliberately skips that validation and applies what you give it. As the docs put it, "callers are responsible for maintaining valid token and state combinations," and setting state to connected without valid tokens "will leave the connected account in an inconsistent state." A migration script is on its own there.
Envelope encryption, in the shape it actually takes
"Encrypted at rest" is where most vendor answers stop, and it is the least interesting part of the design. The interesting part is which key.
Secrets stored in Vault are cryptographically isolated by the context supplied with them. Data is encrypted with a data encryption key (DEK); the DEK is encrypted with a key encryption key (KEK). Each item in the key context identifies one of those long-lived KEKs, whose material lives in a hardware security module and cannot be exported to plaintext; it can only generate or decrypt data keys. KEKs are created just in time, with no configuration in advance.
An encryption operation generates a fresh, random DEK, wraps it with each KEK named by the context, and stores the wrapped keys next to the encrypted data. A DEK is unique per object rather than a long-lived key shared across a tenant. What sits on disk is a pair of ciphertexts. Neither half is useful alone, and the half that would decrypt the other can only be unwrapped inside the HSM.
What a compromised key reaches
This is the question worth rehearsing before an auditor asks it, because the answer is bounded and specific.
A single exposed data key exposes the data that key encrypted. The key context docs put it plainly: "even if one key is exposed, not all of the data will be at risk since many different data keys are used for the same key context". Because a DEK is generated per encryption operation and many of them serve the same context, a leaked DEK bounds the exposure to the objects it wrapped, not to a tenant.
At the layer above, the boundary is the KEK, and the honest answer depends on whose secret it is. For secrets you store in Vault yourself, key segmentation happens by organization, user, or any context you provide, which is what gives organizational secrets full cryptographic separation from other organizations in your application. With BYOK, a customer-managed key takes the place of the WorkOS-managed KEK for that customer's context, while a context belonging to a different organization still resolves to a WorkOS-managed KEK. BYOK covers AWS KMS, Google Cloud KMS, and Azure Key Vault, and because the operation is still matched to the customer's key through the key context, a compromise of that customer's own key manager stays inside that customer's own data.
The provider tokens Pipes manages for you ride this same storage engine, but under WorkOS-managed keys whose layout WorkOS owns. The controls you hold for those tokens are the ones in the next two sections: the API key, the relay, and the delete.
Where the encryption boundary stops
Encryption is not authorization, and pretending otherwise is how architecture diagrams mislead people.
If your WorkOS API key leaks, whoever holds it can call the credentials endpoint and get back a live provider token, refreshed on the way out. The key context did its job. The API key was the actual gate, and scoping it, injecting it at request time, and rotating it are the controls that close it. Related reading: cryptographic key isolation in multi-tenant SaaS.
Two ways the token leaves the vault
Pipes exposes the same connections and refresh machinery through two interfaces, and the difference between them is whether the token ever materializes in your process. Even a missing connection surfaces differently in each: a 200 carrying active: false on the token path, a 402 with an authorization_url through relay.
The first shape hands you the secret:
The response is a union on active: when the connection is live you get the token plus its expiry, the granted scopes, and missingScopes; when it is not, you get an error of needs_reauthorization or not_installed instead. Pipes refreshes the token if it needs refreshing when you request it, so what you receive is always valid and non-expired.
The second shape never hands it over. Relay is a forward HTTP proxy for AI agents and other untrusted runtimes; with a vended access token, the token exists inside the calling environment, where it can be copied, logged, or leaked.
Your API key authenticates the call, then gets stripped and is never sent upstream; X-Relay-User names the user, scoped by X-Relay-Organization when the connection belongs to an organization. WorkOS pulls the user's token from the Pipes credential store, refreshes it if it needs it, injects it into the header the provider expects, usually Authorization, and forwards the request over HTTPS.
The stripping is thorough in both directions. On the way out: hop-by-hop headers such as Connection and Transfer-Encoding, Cookie, forwarding metadata including X-Forwarded-*, Via, and X-Real-IP, your own Authorization header, and anything prefixed X-Relay- or X-WorkOS-. On the way back: hop-by-hop headers and Set-Cookie, plus Content-Encoding and Content-Length, which may be removed because compressed bodies are decoded in transit.
The constraints matter before you route production traffic through it: bodies up to 5 MB forwarded byte-for-byte, a 30-second upstream timeout, HTTPS only because credentials are never injected into a plaintext request, a per-provider host allowlist, and no redirect following. Two caveats to be straight about. Relay is in early access, so it has to be enabled for your environment. And it removes provider tokens from the calling environment, not your WorkOS API key. Inject that key at request time rather than baking it into agent-visible code or prompts, and rotate it from the dashboard if a runtime is compromised. Use relay where a live OAuth token should not live: an agent sandbox, ephemeral compute, a customer-controlled environment.
Rotation runs on a different clock than consent
Two lifecycles get conflated here, and separating them is what makes rotation cheap.
The cryptographic lifecycle belongs to whoever holds the keys, you for secrets you store in Vault, WorkOS for the tokens Pipes manages, and it moves without asking the user anything. Rotating a KEK means re-encrypting the DEKs associated with it, which is small, fixed-size key material; the encrypted data itself does not move. Generating a data key and re-keying one from one key context to another are both API operations, and the rekey endpoint exists precisely to "migrate data keys from one key context to another without re-encrypting the underlying data."
The OAuth grant belongs to the user, and rotating a KEK leaves it alone. The refresh token is the same string before and after; only the envelope around it changed. The provider's record of the grant is untouched, so nobody gets an email asking them to reconnect Slack.
Re-consent is triggered by grant-level events, which have their own signals. The token endpoint returns needs_reauthorization when the user has to reauthorize and not_installed when they never connected; the Pipes widget surfaces the same prompt when something is wrong with their access token. Through relay, a user who never connected, whose grant was revoked, or whose token can no longer be refreshed produces a 402 naming the connection and where to send them:
Send the user to that authorization_url, remembering that it can be null for API key and client credentials connections, which have no authorization screen. A scope mismatch on the organization header returns the identical 402, so check the scope of the connected account before asking a connected user to reauthorize. Two adjacent failures are not consent problems at all: a 400 relay_credential_invalid means the stored credential cannot be presented to the provider and needs replacing, and a 502 relay_credential_error means the credential could not be resolved for a reason re-authorization will not fix.
One design decision affects both clocks: what you put in the key context. Each unique name-value pair maps to its own KEK, and an environment gets 500 unique KEKs by default. Past that, an operation that would create a new one fails with a 422 while reads, updates, and deletes keep working.
Partition on long-lived boundaries, like an organization or a tenant, rather than per-record identifiers like UUIDs, which exhaust the limit. Contexts are capped at 10 string entries, names up to 120 characters and values up to 500. Cleanup is slower than creation: deleting objects releases no capacity, because the key material is retained so that data encrypted under it can still be decrypted, and deleting a key schedules its removal after a waiting period of at least 7 days while it still counts against the limit.
One more thing worth knowing for a resilience question. As of February 2026, every WorkOS-managed key is replicated to a secondary region, so encryption and decryption fail over automatically if the primary region becomes unavailable, and all existing single-region keys were migrated with no configuration required. Objects are a separate matter: if you stored one before February 2026 and want it covered, write a new version so it gets re-keyed with a replicated key.
Revoking access, and what revocation does not do
Deleting a connected account disconnects it and removes the stored access and refresh tokens; the response is a 204, and the user has to reauthorize to reconnect.
That deletion revokes nothing on the provider's side. The user may still need to disconnect your application from the provider's own settings. Any support article you write about disconnecting an integration should say both halves out loud.
The heavier instrument is the key. Deleting the key that protected a secret, once its mandatory waiting period has elapsed, renders that secret unreadable everywhere it resides, without a scavenger hunt through every system holding a copy.
The three questions
A credential store is only as good as the answers it lets you give under pressure. Name the key that encrypts a given secret, then say what else that key reaches. If the answer is "everything," the problem is the key layout rather than the algorithm. For secrets you store in Vault yourself, start from the boundary your customers already think in, the organization, and put it in the key context: destroying that key takes out exactly one tenant's stored secrets, which is the point. For the tokens Pipes manages, the levers you hold are the ones this post walked through: an API key you scope and rotate, a relay that keeps live tokens out of untrusted runtimes, and a delete that actually deletes.