
Understanding Optional SSAs in FDX: DCR with and without Software Statements
In the world of Dynamic Client Registration (DCR), one acronym tends to trip up even experienced implementers: SSA. While Open Banking specs like the UK's and Brazil's mandate Software Statement Assertions, the FDX spec takes a different approach—SSAs are optional. But what does that mean in practice, and how do you build for both worlds without shooting yourself in the foot?
What's a Software Statement Assertion (SSA)?
An SSA is a signed JWT issued by a trusted authority—typically a central directory or governance body—that vouches for a client's identity and metadata during the registration process. Think of it as a tamper-proof introduction letter between your fintech app and a data holder's authorisation server (AS).
This assertion contains claims like:
- Client name and description
- Redirect URIs
- Grant types, response types
- JWKS URI for key discovery
- Organisation and software identifiers
- Signature and expiry time
In heavily governed ecosystems like the UK's Open Banking or Open Finance Brasil, the SSA is non-negotiable. No SSA, no onboarding. But FDX throws a curveball: it's not mandatory.
FDX's Position: SSA Optionality
The FDX specification allows for both SSA-based and SSA-less registration. This flexibility makes sense when you consider the North American ecosystem:
- No central directory comparable to the UK's OB Directory
- Wider range of bilateral agreements between TPPs and data holders
- Desire to support legacy OAuth setups where SSA infrastructure doesn't exist
However, this optionality means additional complexity for implementers. As a fintech, you can't just assume all banks will follow the same rules. Some will expect a software_statement. Others will ignore it entirely.
When to Include the SSA—and When Not To
From an implementation standpoint, here are typical conditions for each path:
- Include an SSA if the data holder:
- Publishes SSA requirements in their developer portal
- Is part of a closed ecosystem that provides SSA issuance
- Rejects registrations missing software_statement
- Skip the SSA if:
- The data holder explicitly says it's not required
- You're using mutual TLS or manual vetting as a trust anchor
- The registration process is tightly coupled to a commercial agreement
Ideally, this decision should be made dynamically per target AS, based on metadata or environment configuration.
Annotated SSA Payload (JWT Claims)
{
"client_name": "My Fintech App",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"jwks_uri": "https://myapp.com/jwks.json",
"scope": "openid accounts",
"software_id": "abc123-456def",
"software_roles": ["data_recipient"],
"org_id": "fintech-co",
"authority_url": "https://fdx.directory.example",
"iss": "https://fdx.directory.example",
"exp": 1715208397,
"iat": 1715204797
}
Key fields:
-
software_id
: Unique to your software product, issued by the directory -
org_id
: Your legal entity identifier, registered with the governance body -
jwks_uri
: Where your public keys live for token and request signing -
authority_url
: The directory or SSA authority that issued this JWT
Sequence Diagram: Supporting Both SSA and Non-SSA Flows

Node.js Example: Registering with and without SSA
Here's a basic Node.js implementation using Axios that supports both paths:
const axios = require("axios");
async function getSSA(directoryUrl) {
const { data } = await axios.get(directoryUrl);
return data; // Assumes the response is the signed JWT
}
async function registerClient({ metadata, config }) {
const payload = { ...metadata };
if (config.requireSSA) {
payload.software_statement = await getSSA(config.directoryUrl);
}
const response = await axios.post(config.registerUrl, payload, {
headers: { "Content-Type": "application/json" },
httpsAgent: config.mtlsAgent
});
return response.data;
}
This snippet uses a `requireSSA` flag and gracefully degrades if the SSA isn't needed. You could extend this to support fetching a fresh JWKS before registration or caching SSAs locally.
Pro Tips from the Trenches
- Validate the SSA before sending it—check expiry, issuer, and audience
- Cache SSA tokens per client for reuse across environments
- Don't hardcode JWKS URIs—load them dynamically via SSA or config
- Log rejected SSA claims clearly—they're your best debug signal
- If the AS returns
invalid_software_statement
, verify base64 encoding and JWT structure
Closing Thoughts
The optional SSA model in FDX is a classic case of “flexibility at a cost.” It opens the door to lightweight integrations where appropriate, but it also demands that implementers be smarter, more adaptable, and slightly paranoid.
If you're building a unified DCR client across multiple ecosystems, treat SSA as a plug-in: load it when required, skip it when not, and validate it always. And remember—just because it's optional doesn't mean it's never needed.