Need-to-KnowNeed-to-Know
AuthenticationJWTKeycloakTroubleshooting

The Audience (aud) Claim Requirement

Fixing 'Service not found' and 'Invalid audience in token' errors in Canton deployments.

6 min read

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.

💡Related Reading

Symptoms You'll See

Common error strings include:

Service not found: https://canton.network.global
Invalid audience in token

Why 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.global

Once 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:

  1. Create a client scope (example: canton-audience)
  2. In that client scope: Mappers → Configure a new mapper → Audience
  3. Set:
    • Name: canton-audience
    • Included Custom Audience: https://canton.network.global (or your dedicated audience)
  4. Attach the client scope to the clients that obtain tokens used against Canton APIs
⚠️Important Nuance
Audience mappers typically add audiences to the access token by default. The ID token typically has only the client id as its audience due to OIDC expectations.

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:

ClientPurpose
wallet-web-uiWallet frontend OIDC login
validator-app-backendValidator app backend
ledger-apiM2M 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:

Decode JWT payload
echo "YOUR_JWT_TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq

You're looking for something like:

Expected aud claim
{
  "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.