Skip to main content

Agent DIDs and trust attestations

Every agent in Rivaro has a portable W3C Decentralized Identifier (DID). Use it to:

  • Carry agent identity across vendor boundaries (SIEM, ticketing, lineage tools).
  • Let any partner system look up an agent's current trust posture without a Rivaro account.
  • Verify Rivaro-issued claims about an agent (status, trust score, identity assurance, recent violations) using off-the-shelf JWT-VC libraries.

What you get out of the box​

  • A did:key identifier per agent, anchored in the organization's existing Ed25519 receipt-signing key. No hosting required, fully self-resolvable.
  • Optional did:web identifiers for customers who want resolvable URIs at their own domain.
  • A public, signed trust attestation endpoint at /.well-known/agent-trust/{agentId} that returns a W3C Verifiable Credential as a JWT-VC.

The DID flows alongside the internal agentId in:

  • TelemetryExportRecord (SIEM batch export).
  • The Agent-on-a-Page export (PDF + JSON).
  • The dashboard agent detail panel (copyable chip + "Check Trust Status" button).

Endpoints​

GET /api/agents/{agentId}/export/did?method=key|web&download=true|false   # auth required
GET /api/agents/export/did/bulk?method=key # auth required, returns the org-controller DID + every agent DID
GET /api/organizations/me/did.json # auth required, org-level controller DID
GET /.well-known/agent-trust/{agentId} # PUBLIC, returns a JWT-VC (application/vc+jwt)

Hosting did:web​

For v1 the demo posture serves everything from the main app host. Production layouts that scale:

  • Rivaro-hosted, app-domain. Default in v1. Resolves at https://{app-host}/.well-known/did.json for the org and https://{app-host}/agents/{agentId}/did.json per agent. Zero ops effort, but the DID identifier embeds Rivaro's hostname.
  • Rivaro-hosted, dedicated subdomain (recommended for production). Move the DID surfaces behind a stable subdomain such as did.rivaro.com so identifiers are decoupled from the app's hostname and can survive product reskins. The endpoint base URL is configurable — set rivaro.did.public-host=did.rivaro.com once and every emitted DID, service entry, and trust attestation iss follows.
  • Customer-domain via CNAME. Customers who want their identifiers rooted in their own domain (did.acme.com) point a CNAME at the dedicated subdomain. The DID document and trust resolver respond at the customer hostname; the signing key is still Rivaro's, so verification continues to work.

Verifying a trust attestation​

Any stock JWT-VC verifier can validate the response. Minimal Node example using did-jwt:

import { verifyJWT, ES256KSigner, EdDSASigner } from 'did-jwt';
import { Resolver } from 'did-resolver';
import { getResolver as keyResolver } from 'key-did-resolver';

const jwt = await fetch(`https://app.rivaro.example/.well-known/agent-trust/${agentId}`)
.then((r) => r.text());

const resolver = new Resolver({ ...keyResolver() });
const { payload } = await verifyJWT(jwt, { resolver, audience: undefined });

console.log(payload.vc.credentialSubject); // { trustScore, trustTier, status, identityAssurance, ... }

Manually with the JDK (no third-party dependency):

String jwt = httpClient.send(
HttpRequest.newBuilder(URI.create(trustUrl)).GET().build(),
HttpResponse.BodyHandlers.ofString()).body();

String[] parts = jwt.split("\\.");
byte[] signingInput = (parts[0] + "." + parts[1]).getBytes(StandardCharsets.US_ASCII);
byte[] signature = Base64.getUrlDecoder().decode(parts[2]);

PublicKey orgKey = /* loaded from the org's DID document or /api/organizations/me/did.json */;
Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(orgKey);
verifier.update(signingInput);
boolean ok = verifier.verify(signature);

The kid header in the JWT points at the org's verification method ({orgDid}#org-key-1), and the JWT iss claim is the org DID itself. The agent's DID is in the JWT sub claim and the credential subject's id.

A2A trust check recipe​

When agent A receives a request from agent B (identified by a Rivaro DID):

  1. Extract agentDid from the inbound request envelope (e.g., from a webhook payload, telemetry record, or DID auth header).

  2. Resolve the trust attestation:

    Send a GET request to https://app.rivaro.example/.well-known/agent-trust/{AGENT_ID} with an Accept: application/vc+jwt header and save the response as attestation.jwt.

  3. Verify the JWT-VC against the issuer's public key.

  4. Read credentialSubject.trustTier (TRUSTED / STANDARD / HIGH_RISK), credentialSubject.status (ACTIVE / QUARANTINED / etc.), and credentialSubject.violationCount to make a routing decision.

The validity window is short (5 minutes by default) so cached decisions don't outlive the underlying governance signals.

Configuration​

rivaro:
did:
public-host: app.rivaro.example # used in did:web identifiers and absolute URLs
scheme: https
trust-context-url: https://rivaro.com/contexts/agent-trust/v1
service-type-prefix: https://rivaro.com/spec/v1

Override per environment. The DID identifier and every emitted trust attestation iss follow these values, so plan the production hostname before going live — changing it rotates every DID for every agent.