← all docs

Verifiably β€” Backend Integration Guide

This prototype is fully functional against an in-memory mock layer. To connect a real backend, you do not touch the UI code. You reimplement the BACKEND object inside vc-prototype.html.

Where to look in the code

Open vc-prototype.html and search for these three markers, in order:

  1. const MOCK = { β€” every piece of fake data (DPG catalogs, schemas, subject values, issuer identities, offer URIs, verification outcomes). You can delete this entire object once the backend is live, but it's useful to keep around as a fixture for local development.

  2. const BACKEND = { β€” the ten async adapter functions. Their names, argument shapes, and return shapes are stable. Replace each body with a fetch(...) call to your real endpoint. The UI calls these via await BACKEND.foo(...) and doesn't care where the data comes from.

  3. const DEBUG_SHOW_MOCK_MARKERS = false β€” flip to true during integration to see small [mock] pills on every UI surface still rendering from the mock layer. When you've wired all adapters, none of the pills should appear.

Adapter β†’ endpoint mapping per DPG

Each BACKEND.* function has a // TODO: comment naming the concrete endpoint. The table below expands those into copy-ready targets. Base URLs shown as env-var placeholders β€” substitute your deployed DPG URLs.

Adapter walt.id Community Stack v0.18.2 Inji Certify v0.14.0 Inji Web Wallet v0.16.0 Inji Verify v0.16.0
listIssuerDpgs N/A (app-level registry) N/A β€” β€”
listHolderDpgs β€” β€” N/A (app-level registry) β€”
listVerifierDpgs β€” β€” β€” N/A (app-level registry)
listSchemas(issuerDpg) GET {ISSUER_API}/credentialConfigurations GET {CERTIFY_API}/v1/certify/credential-configuration β€” β€”
prefillSubjectFields(schema) N/A (operator types into form) MOSIP Identity Plugin: GET {CERTIFY_API}/v1/certify/issuance/data-provider/{plugin}?key={uin} β€” β€”
issueToWallet({issuerDpg, schema, subjectData}) POST {ISSUER_API}/openid4vc/jwt/issue (or /sdjwt/issue, /mdoc/issue depending on format) β†’ returns offer URI POST {CERTIFY_API}/v1/certify/issuance/credential-offer β†’ returns pre-authorized code + offer URI β€” β€”
issueAsPdf({issuerDpg, schema, subjectData}) Not supported at v0.18.2 β€” must build on top of the issuer library Relies on Claim 169 QR Code spec (pixelpass lib) + MOSIP Identity Plugin; POST {CERTIFY_API}/v1/certify/issuance/credential with QR-embed flag β€” β€”
issueBulk({issuerDpg, schema, csvRows}) POST {ISSUER_API}/openid4vc/batchIssue POST {CERTIFY_API}/v1/certify/issuance/bulk (check v0.14.0 docs for exact shape) β€” β€”
parseOffer(offerUri) POST {WALLET_API}/wallet/{walletId}/exchange/resolveCredentialOffer β€” Redirect flow β€” not called from this app β€”
claimCredential(offer) POST {WALLET_API}/wallet/{walletId}/exchange/useOfferRequest β€” Redirect flow β€”
requestPresentation({verifierDpg, templateKey}) POST {VERIFIER_API}/openid4vc/verify β†’ returns request URI + state β€” β€” Redirect flow
fetchPresentationResult({state}) GET {VERIFIER_API}/openid4vc/session/{state} or register a statusCallbackUri on the verify call β€” β€” Redirect flow
verifyDirect({verifierDpg, method, credentialData}) POST {VERIFIER_API}/openid4vc/verify/credential β€” β€” For W3C VCDM: POST {VERIFY_API}/v1/verify/vc-verification. For SD-JWT VC (new in v0.16.0): POST {VERIFY_API}/v1/verify/vc-submission then GET /v1/verify/vp-result/{transactionId}

Authentication

OIDC providers are configured in two layered files plus a runtime admin UI:

  • config/auth-providers.system.json β€” bootstrap providers written by deploy.sh (defaults: Keycloak + WSO2 IS). Re-run-safe; ./deploy.sh run all overwrites it. Set VERIFIABLY_NO_DEFAULT_IDPS=1 to skip the demo defaults entirely β€” the UI then shows a first-run form on /auth that lets the operator register their own provider before signing in.
  • config/auth-providers.user.json β€” admin-UI-managed providers. Persisted across ./deploy.sh runs. Created on first POST to /admin/auth-providers (or migrated automatically from any pre-existing auth-providers.docker.json.bak). User entries override system entries with the same id.
  • VERIFIABLY_OIDC_PROVIDERS env (JSON array) β€” when set, replaces both files for that boot. Highest precedence; useful for IaC / Helm / Compose deploys.

Per-field env overrides (VERIFIABLY_OIDC_<ID>_{ISSUER_URL,CLIENT_ID,CLIENT_SECRET,DISPLAY_NAME,KIND,SCOPES,INSECURE_SKIP_VERIFY}) apply on top of the file/env-JSON merge.

Surface control via VERIFIABLY_AUTH_ADMIN. Persistence is independent of this flag β€” it works in every mode.

Value Admin page /auth "+ Add" form Effect
rw (default) Visible (login + list + delete) Visible Full surface; admin curates AND users can self-add.
ro Visible (login + list + delete) Hidden Admin curates a fixed list; end users can only pick from existing tiles.
off 404 (no nav link, no admin login) Hidden No UI for provider management at all. Providers come only from VERIFIABLY_OIDC_PROVIDERS / auth-providers.system.json / per-field env overrides.

Fresh-install (registry empty) bypasses the ro and off add-form lockdown so a single env flag can't permanently shut out bootstrapping.

Admins can delete rows of any source (user, system, env). System / env rows reappear on the next ./deploy.sh run all / container restart β€” that's the desired behaviour during admin iteration; for permanent removal, edit the source (deploy.sh's auth_providers_for / the env var) and restart.

The UI already has a Keycloak / WSO2 provider selection on the auth screen. Once authenticated, whatever session token your IDP returns should be attached to every BACKEND.* call. A clean way to do this:

const BACKEND = {
  _fetch(url, opts = {}) {
    return fetch(url, {
      ...opts,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${STATE.authToken}`,
        ...(opts.headers || {})
      }
    }).then(r => r.ok ? r.json() : Promise.reject(r));
  },
  async listSchemas(issuerDpg) {
    const base = ISSUER_BASE_URLS[issuerDpg];
    return this._fetch(`${base}/credentialConfigurations`);
  },
  // ...
};

Store the token in STATE.authToken from completeAuth() (currently stubs auth β€” swap with a real IDP redirect + token exchange).

Format note β€” return shapes

The UI expects specific shapes. If your backend returns something different, adapt it inside the BACKEND adapter rather than changing the UI:

  • listSchemas returns an array of { id, name, std, dpgs, desc }.
  • issueToWallet returns { offerUri, offerId, flow, expiresIn }.
  • issueAsPdf returns { issuerName, issuerDid, payloadSizeKb, fields }.
  • issueBulk returns { accepted, rejected, errors: [{ row, reason }] }.
  • parseOffer returns { title, issuer, type, fields } where fields is a flat keyβ†’value map.
  • claimCredential returns the claimed credential with status: 'accepted' added.
  • requestPresentation returns { requestUri, state, template: {...} }.
  • fetchPresentationResult and verifyDirect return { valid, method, format, issuer, subject, requested?, issued, checkedRevocation }.

If your verifier supports richer result metadata (signature suite, trust chain, revocation list source, etc.), add fields to the return shape β€” renderVerifyResult(opts) gracefully ignores unknown fields, and you can extend it when you need to surface them.

Cross-version compatibility warning

The four DPG versions used in this prototype (walt.id v0.18.2, Certify v0.14.0, Inji Web v0.16.0, Inji Verify v0.16.0) are not a tested-compatible matrix β€” each vendor publishes its own compatibility table. When deploying:

  • Inji Web Wallet v0.16.0 declares compatibility with Inji Certify v0.13.1 and Inji Verify v0.17.0.
  • Inji Verify v0.16.0 declares compatibility with Inji Web v0.14.0 and Inji Wallet v0.20.0.
  • walt.id Community Stack is versioned as a unit; all three (issuer, wallet, verifier) ship together at v0.18.2.

Pick one vendor's matrix and align. Mixing versions is how interop bugs happen.

Known issues to guard around

  • INJIVER-1131 (Inji Verify v0.16.0 cross-device flow): presentation is reported valid even when a wrong VC is submitted. Implement credential-type validation in your Relying Party code until fixed upstream.
  • INJICERT mDoc (v0.14.0): issuance is mock-only per the GitHub README. Don't ship mDoc credentials from Certify in production.
  • walt.id wallet OID4VP v1.0 (through v0.18.2): not yet fully supported in the wallet/demo apps. Older OID4VP (Presentation Exchange) works.

Swap checklist

Once the backend is live, verify:

  • DEBUG_SHOW_MOCK_MARKERS = true shows no [mock] pills during a full end-to-end flow
  • DPG capability cards still reflect your deployed versions (edit MOCK.issuerDpgs etc., or replace with a listIssuerDpgs backend call)
  • parseOffer correctly handles both credential_offer=... (inline) and credential_offer_uri=... (by-reference) forms
  • Auth token refresh is handled for long-lived sessions
  • Errors from BACKEND calls surface as user-friendly toasts (currently most failure paths throw silently)
Source: INTEGRATION.md