The Audience (aud) Claim Requirement
Fixing 'Service not found' and 'Invalid audience in token' errors in Canton deployments.
The Problem
I'm getting errors like Service not found: https://canton.network.global or "Invalid audience in token" when using the wallet or validator APIs. What's wrong?
The Answer
Your Canton validator stack expects JWTs whose audience (aud) claim matches the audience configured for your validator/participant APIs.
A common "getting started" default is https://canton.network.global, but many IdPs (including default Keycloak setups) won't include this unless you configure an audience mapper.
Quick Mental Model
There are two places your tokens must work:
- Validator App REST API — wallet UI calls
/api/validator/* - Participant Ledger API — validator backend calls participant via gRPC
If either component checks the token's aud and it doesn't match, you'll see "service not found" or "invalid audience" style failures.
Symptoms You'll See
Common error strings include:
Service not found: https://canton.network.globalInvalid audience in tokenWhy It Happens
Splice deployments use JWT access tokens issued by an external OIDC provider. When first starting out, Splice suggests configuring both token audiences to the same simple default:
https://canton.network.globalOnce everything works, Splice recommends switching to dedicated audience values for:
- The Participant Ledger API
- The Validator backend API
This reduces the risk of tokens from one network/environment being usable on another.
Fix: Make the IdP Include the Audience
Step 1 — Confirm What Audiences Your Deployment Expects
You'll typically have two relevant audience settings:
- Participant (Ledger API) expects
auth.targetAudience - Validator backend API expects
auth.audience
Start with the default https://canton.network.global if you're early in setup, and move to dedicated values later.
Step 2 — Add an Audience Mapper in Keycloak
Keycloak supports adding aud to access tokens using an Audience protocol mapper (often via a client scope). This is commonly called a "hardcoded audience" approach.
High-level steps:
- Create a client scope (example:
canton-audience) - In that client scope: Mappers → Configure a new mapper → Audience
- Set:
- Name:
canton-audience - Included Custom Audience:
https://canton.network.global(or your dedicated audience)
- Name:
- Attach the client scope to the clients that obtain tokens used against Canton APIs
Which Keycloak Clients Should Get the Mapper?
At minimum, any client that obtains tokens used to call:
- The wallet UI / validator REST endpoints
- The participant Ledger API (machine-to-machine token used by validator backend → participant)
In practice, that often maps to:
| Client | Purpose |
|---|---|
wallet-web-ui | Wallet frontend OIDC login |
validator-app-backend | Validator app backend |
ledger-api | M2M access to participant Ledger API |
(Exact client names vary by operator setup.)
Verify the Token
After applying the mapper, decode the JWT and verify aud includes your expected value:
echo "YOUR_JWT_TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jqYou're looking for something like:
{
"aud": ["https://canton.network.global", "account"],
"sub": "your-user-id"
}Production Guidance
Once your setup works with the default audience, move to dedicated audiences for:
- The participant Ledger API
- The validator backend API
Keep the chosen audiences and IdP mapper config documented and version-controlled to avoid drift between Helm values and IdP token claims.

