Set up service-to-service authentication
Set up service-to-service authentication
Authenticate a backend service to the Moveworks Conversations API using a self-signed JWT bearer, letting your service make requests on behalf of any provisioned user.
The Conversations API is in Controlled Availability. Endpoints, behaviors, and configuration surfaces may change during CA. Contact your Moveworks account team to participate.
This guide covers authenticating a backend service to the Moveworks Conversations API, so that service can make requests on behalf of any provisioned user in your Moveworks tenant. Your backend generates and signs the JWT locally, then exchanges it for a Moveworks bearer token.
When to use this
Use this pattern when a trusted backend system (test harness, batch worker, monitoring bot, scripted integration) needs to make Conversations API calls on behalf of an already-known user, and there is no interactive user browser flow available to obtain a token.
Use one of the interactive user guides instead when:
- Real end users sign in through your application and each request represents that specific signed-in user. See Set up authentication with Okta or Set up authentication with Azure AD (Entra ID).
The trade-off:
How it works
Your backend service holds an RSA private key. The matching public key is registered as a credential in your Moveworks tenant. For each API call you want to make on behalf of a user:
- Your backend builds a short-lived JWT that names the user in the
subclaim and signs it with the private key. - Your backend exchanges the signed JWT at
POST /oauth/v1/tokenfor a Moveworks bearer token. - Your backend uses that bearer token on Conversations API calls. The bearer is scoped to the user in
sub.
The private key is a tenant-wide credential. Any process holding it can obtain a bearer for any user in the tenant. Treat it accordingly (see Security).
Prerequisites
- Your Moveworks tenant is provisioned for the Conversations API and your account team has provided the Bot Name (used in the
Assistant-Nameheader on every API call). - Admin access to your Moveworks Admin Portal (
https://{org}.moveworks.com). - A Unix-like environment (or WSL / OpenSSL for Windows) to generate keys.
- Ability to run a backend runtime that can sign JWTs (any language with an RS256 JWT library works; the samples in this guide are Python).
- The set of users your backend will act on behalf of are already resolvable against your Moveworks user roster.
Step 1: Generate an RSA keypair
On the machine that will run your backend service, generate a 2048-bit RSA keypair and lock down the private key permissions.
You will register public.pem with Moveworks in step 2 and keep private.pem on your backend.
Step 2: Create the JWT OAuth credential in Moveworks
In the Moveworks Admin Portal, go to https://{org}.moveworks.com/http-connectors/connector_studio/home → Credentials → create a new credential.
Save the credential. Moveworks displays a Key ID (KID) value; copy it. You will include it in every JWT header.
Because your backend signs the JWT itself, Issuer and Audience are values you choose. They must match exactly between the credential configuration and the JWT you sign. If either value differs, the token exchange fails with invalid_audience or invalid_signature.
Step 3: Sign the JWT and exchange for a bearer
The examples below use https://api.moveworks.ai, which is only correct for orgs on the US Production data center. Replace it with the base URL for your data center from the Base URLs table (for example, https://api.am-eu-central.moveworks.ai for EU). Requests to the wrong host return 404 or invalid_audience.
Your backend performs two operations per request (or per short-lived cache window):
- Build a JWT with the user’s identifier in
sub, sign with the private key. - Exchange the signed JWT at Moveworks’ token endpoint for a bearer.
Python example
Install dependencies:
Sign the assertion and exchange it for a bearer:
What each claim does
The kid header identifies which registered public key Moveworks should use to verify the signature.
Caching the bearer
The exchange response includes expires_in (typically ~1 hour). You can cache the returned bearer per user for its lifetime and only re-mint when it expires or nears expiration. Refresh proactively (for example, at 90% of expires_in) so long-running operations do not fail mid-flight.
Step 4: Call the Conversations API
Use the bearer on any Conversations API endpoint. Every request must include the Assistant-Name header (the Bot Name provided by your Moveworks account team) and the Authorization header.
A 201 response with a conversation_id confirms the integration works end to end. To switch to a different user on a subsequent request, sign a new assertion with a different sub and exchange it for a new bearer.
Step 5: Validate
Confirm end to end before wiring it into production:
- Sign an assertion for a known-good test user in your roster and decode it (for example, at jwt.io) to confirm
iss,aud,sub,iat,exp,jti, and thekidheader are all populated as expected. - Exchange the assertion at
/oauth/v1/token. A200response with anaccess_tokenmeans the signature verified and the user resolved. Common failure modes:invalid_audience: your JWT’sauddoes not match the credential’s Audience field.invalid_signature: thekiddoesn’t match a registered key, or the private key doesn’t match the registered public key.- Generic post-verification failure: the
subvalue doesn’t resolve to a user in the roster. Confirm the identifier your backend is sending insubis what your Moveworks tenant expects for that user.
- Create a conversation with the returned bearer as above. A
201with aconversation_idcloses the loop.
Security
The private key is a tenant-wide credential. Any process that holds it can obtain a bearer token for any provisioned user in your Moveworks tenant, and every Conversations API call made with that bearer is recorded as an action performed by the impersonated user. Treat the private key with the same rigor as a production API key or a service-account credential.
Recommended handling:
- Store the private key in a secret manager (AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, etc.). Do not commit it to source control, embed it in application bundles, or store it on developer laptops beyond initial setup.
- Restrict which services can read the private key. Only the specific backend process that needs to mint bearers should have access.
- Log every action taken on behalf of each user in your own audit trail. From Moveworks’ perspective, actions taken by your backend on behalf of a user are indistinguishable from actions that user performed themselves. Your own audit trail is the only source of truth for which system initiated the call.
- Rotate the credential if the private key is plausibly exposed. Generate a fresh keypair, register the new public key as a new credential, cut your backend over, then delete the old credential.
- Do not use this credential type for user-facing product integrations where end users obtain tokens directly. Use the interactive user auth guides for those flows.
Summary
For the full API contract and interaction patterns (polling and streaming), see the Conversations API reference.