What you are hosting
Credential claims can come from an existing service or API, an existing database,
or static test data. You do not need to create a new database simply to provide claims.
Fork and review the backend implementation
Fork the SD-JWT branch of the AIR issuer service. It issuesSD_JWT_VC credentials under a did:web issuer DID.
- The service runs without
DATABASE_URLfor sandbox testing. For issuance history, revocation, and a token status list at scale across many users, you will need to persist the relevant data in your own database. - Iden3 issuers use a separate branch. See Iden3 credentials.
Choose a hosting path
Check each provider’s current plans and idle/sleep behavior before selecting a
sandbox host: Railway,
Render, Koyeb,
Neon, and
Supabase.
A sleeping service may delay the first credential request.
Prerequisites
- Your issuer backend repository
- An AIR Developer Dashboard Partner ID
- Partner secrets configured using the issuance quickstart
- A public HTTPS domain or development tunnel
Deploy the issuer service
The following steps use Railway as an example. Apply the same build commands, environment configuration, and public endpoint requirements on another host.1
Create a project and select your repository
Create a Railway project and add your fork of the AIR issuer service.
Use the same revision you configured and tested locally.
2
Configure build and start commands
If you enable database persistence, set the pre-deploy command to
npx mikro-orm migration:up after configuring DATABASE_URL.3
Configure the public origin and environment
Generate a public HTTPS domain in the host’s networking settings. Set
ISSUER_ORIGIN to that origin without a trailing slash. Use a custom
domain you control for production: the issuer DID is
did:web:<host of ISSUER_ORIGIN>, so the host is your issuer identity.Add the environment values from
Configure and start the issuer backend,
including Partner JWT key configuration, PARTNER_JWKS, and the issuer
API keys. Keep secrets in the host’s environment settings.For this sandbox example, use NODE_ENV=sandbox and sandbox API origins.
Railway supplies PORT; the issuer service listens on it.4
Optionally add a database
Skip this step if you are using a backend configuration without database
persistence.The reference issuer service uses PostgreSQL. Add PostgreSQL to the project,
or use an existing PostgreSQL service, and set Run migrations before starting the database-backed service. This enables the
persistence used by issuance history, revocation records, and the token
status list.
DATABASE_URL on the issuer
backend to its connection string. If you built your own backend, connect the
database it uses instead.For a Railway database named Postgres, the reference variable is:5
Deploy and check the service
Confirm the process starts successfully, then open
https://<your-host>/.well-known/did.json and
https://<your-host>/.well-known/jwt-vc-issuer. The DID document’s id is
the issuer DID you register with AIR. Verify that the public host routes
requests to your issuer endpoints and that its TLS certificate is valid.When you use a database, also confirm connectivity and successful migrations.
Use your implementation’s health checks.ISSUER_ORIGIN stable once you issue credentials. It defines the issuer
DID, and revocation and status-list URLs in issued credentials refer to it.
Optional database persistence
For the reference issuer service, apply its migrations whenDATABASE_URL is configured:
After hosting: connect AIR
- Register the issuer. Use Issuer → Settings in the sandbox or
production Dashboard and enter the
did:webissuer DID. Both environments are self-serve for SD-JWT issuers. - Register the endpoint URLs. Configure
availableVcApiUrlandissueVcApiUrlto point toPOST /available-vcandPOST /issue-vcon your backend, andrevocationStatusApiUrlto itsGET /revocation-status/:nonceroute. - Match the issuer API key. Register the backend’s
API_KEYwith AIR. AIR sends it asx-api-key; keep it out of browser code. - Register the public JWKS URL. Its keys must match the Partner JWT signing
key and
kid. See JWKS endpoint setup. - Register the web app origin. Add it under Account → Domains.
- Test issuance. Confirm the holder sees the preview, approves issuance, and can use the resulting credential. See the issuance quickstart.
*.air3.com. Restricting origins to your own domain breaks the holder claim flow.
Go-live checklist
-
did:webdocument served at${ISSUER_ORIGIN}/.well-known/did.jsonand its DID registered with AIR - Partner JWT keys (
PARTNER_*) match your JWKS, andPARTNER_JWKSholds the matching public keys - Each Dashboard schema has a matching class in
src/issuer/sd-jwt-vc-schemas/, exported fromindex.ts -
generateCredentialDatareturns schema-valid claims, anddisclosureFrameandexpirySecare set per schema - The database is durable and its migrations are applied
-
ISSUER_ORIGINis public HTTPS on a domain you control, and the status endpoints are reachable -
availableVcApiUrl,issueVcApiUrl,revocationStatusApiUrl, and the API key are set in AIR - If you use the token status list, the partition size is final and a publish job is scheduled
- The claim flow passes end to end with a test holder
Local development
Run the issuer locally and expose it through an HTTPS tunnel such as ngrok or cloudflared. Follow the HTTPS tunnel setup. SetISSUER_ORIGIN and the registered issuer endpoint URLs to the tunnel address.
If the tunnel address changes, update both. Add a local or managed database
connection (PostgreSQL for the reference issuer service) only when your backend
configuration uses database persistence.