OEM integrator API · European Digital Identity Wallet backend
Wallet API — embed the CodeB Web Wallet backend in your native mobile app.
Twenty-five documented endpoints across four capability groups (A: 10, B: 2, C: 3, D: 10). OEM integrators building native iOS and Android applications call the same backend that powers our browser-based European Digital Identity Wallet (web-wallet.html) — the difference is that the native app implements the client-side crypto with the platform Keychain / Keystore instead of WebCrypto, and manages its own persistence instead of IndexedDB. The wire protocol is identical.
Hosting. Default OEM tier is shared managed hosting on https://phone.aloaha.com/. Each OEM receives a dedicated OIDC client_id that scopes their app's users and credentials from every other tenant on the platform. Per-OEM subdomain and on-premise deployments are available as tiered options — contact info@aloaha.com for terms.
Auth model. Every non-public endpoint requires an OIDC access token in the Authorization: Bearer <token> header. The token is introspected against /oidc.ashx?action=introspect; the resolved sub keys the user's per-app credentials and vault. The OEM's client_id appears in the token's azp claim and is included in the storage-key hash for the vault (D2/D3), providing cross-OEM isolation on shared managed hosting as of 2026-08-12.
Wallet Attestation (WA / WIA). The /wallet-attestation.ashx endpoint (C3) mints a HAIP §5.11 wallet attestation: a JWT with typ=wallet-attestation+jwt in the header, per-tenant HS256-signed, binding the wallet's public JWK to the acting OIDC sub, 24-hour expiry. The OEM app presents it to verifiers as the OAuth-Client-Attestation header.
Native-parity gaps — what your app must implement locally. The web wallet uses several browser primitives that native apps must substitute with platform equivalents. Summarised inline at each endpoint; a summary at the bottom of the page lists them together.
Related artefacts. Cookbooks (in preparation): native credential import, native credential presentation, WA onboarding, native vault sync, self-issue from OIDC. Machine-readable OpenAPI 3.1 spec and Postman collection follow. See wallet-oem.html for the OEM licensing landing page.
A. Credential import — OID4VCI holder side
Fetching a credential from an issuer (either an external one like issuer.eudiw.dev, or our own vci.ashx). Ten endpoints wrap the OID4VCI 1.0 FINAL flow with pre-auth grant, authorization-code grant + PAR + PKCE, and RFC 7591 dynamic client registration.
RFC 7591 dynamic client registration. Present at external issuers (e.g. issuer.eudiw.dev) whose registration_endpoint metadata field advertises this URL. Not exposed on CodeB's own vci.ashx — for OEM apps against our issuer, the OEM's OIDC client_id is issued during onboarding and does not need to be re-registered per install. Discover via A2 metadata; if the field is absent, the issuer does not support dynamic registration.
Request body (when supported by the external issuer)
Native parity: use your app's custom URL scheme as redirect_uris[0]. Persist the returned client_id in secure preferences (Keychain on iOS, EncryptedSharedPreferences on Android). Web wallet caches in localStorage per issuer host.
Native parity: generate code_verifier and state as CSPRNG-random URL-safe strings; SHA-256 the verifier for the challenge. Store the verifier under state in in-memory app state (web wallet uses sessionStorage). After PAR returns, redirect to <authz_endpoint>?client_id=...&request_uri=... using ASWebAuthenticationSession (iOS) or Custom Tabs (Android).
Native parity: HTTPS POST with form body. Keep access_token in a KeychainAccessible / Keystore-protected value, never plain UserDefaults / SharedPreferences.
OID4VCI 1.0 FINAL nonce endpoint. Public. Returns a fresh c_nonce for the credential proof.
Native parity: straightforward. Some legacy issuers return the initial c_nonce on the token response — treat this endpoint as authoritative but fall back to the token response if absent.
Native parity: the proof-JWT is signed on-device with the wallet's ECDSA-P256 key. Generate keypair in SecKeyCreateRandomKey(kSecAttrTokenIDSecureEnclave) on iOS (or software-backed EC key + Keychain), or KeyPairGenerator.getInstance("EC", "AndroidKeyStore") with KeyGenParameterSpec.Builder on Android. Header MUST include typ=openid4vci-proof+jwt, alg=ES256, jwk=<public JWK>. Payload MUST include iss=client_id, aud=credential_issuer, iat, nonce=c_nonce.
Native parity: the returned CBOR must be persisted verbatim; do not re-encode. Native mDoc libraries (e.g. iOS Sparkle-Motion CBOR, Android Cbor4j) can parse the IssuerSigned map. Web wallet stores as base64url string; native can store as raw Data/byte[].
Fetch by-reference credential offer when the deep-link uses credential_offer_uri= instead of an inline credential_offer=. Returns the offer JSON.
Native parity: straight fetch. The deep-link arrives via UIApplicationDelegate.openURL (iOS) or intent filter (Android). Parse both haip-vci://, openid-credential-offer://, or the offer's canonical scheme.
Used when verifying the issuer signature on a persisted SD-JWT. The issuer's JWT-VC issuer document + JWKS.
Native parity: verify the SD-JWT issuer signature on-device before storing. Reject if the issuer JWKS does not contain the kid or x5c from the SD-JWT header.
B. Self-issue from OIDC identity
The signed-in user's OIDC profile drives an on-server minting: no external issuer needed. Two endpoints on our own vci.ashx.
Native parity: after OIDC login, call once to enumerate available VCs. Show only ready:true as import candidates in your UI. Use missing (list of missing claims) to explain the block to the user.
Server mints a credential using the signed-in user's OIDC profile as source. Requires the wallet's public JWK in cnf.jwk so the issued credential is bound to the wallet key. Returns an SD-JWT with disclosures.
Native parity: generate the wallet keypair (Secure Enclave / Android Keystore); export the public JWK for cnf.jwk; persist the private key handle keyed by credential id. credentials is an array of objects each carrying credential (OID4VCI 1.0 FINAL §8.3 shape); credential as a scalar is emitted alongside for backwards compatibility.
C. Credential presentation — OID4VP holder side
Responding to a Verifier's presentation request. Three endpoints cover the JAR fetch (browsers only), the direct-post response, and Wallet Attestation issuance.
Same-origin proxy that returns the verifier's signed JAR (application/oauth-authz-req+jwt). Exists to bypass browser CORS on cross-origin request_uri fetches. Auth is same-origin session (browser cookies); no Bearer required today. Server-side hardening (as of 2026-08-12): caller must be HTTPS; target must be HTTPS and public-routable (host string check + DNS-resolved IP check); HTTP redirects refused (single-hop only, no auto-follow); upstream error bodies logged internally but NOT returned to the caller.
Native parity:do NOT use. Native apps have no CORS — fetch request_uri directly. This endpoint exists solely for the browser wallet.
POST<verifier response_uri>Open (form) + WA header#
Two response shapes, selected by the JAR's response_mode:
direct_post: form fields vp_token, state, optional mdoc_generated_nonce.
direct_post.jwt: form field response = JWE (ECDH-ES + A128GCM) whose plaintext is the same JSON. Optional mdoc_generated_nonce lives in the JWE's apu header per OID4VP 1.0 FINAL §8.1.
Optional header OAuth-Client-Attestation: <WA JWT> when the verifier requires HAIP §5.11 wallet attestation.
Native parity: for direct_post.jwt, generate an ephemeral P-256 keypair on-device; do ECDH-ES with the verifier's ephemeral public key (from JAR client_metadata.jwks); derive the CEK via Concat KDF (NIST SP 800-56A); encrypt with AES-128-GCM. Same wire format as web wallet uses via WebCrypto. Native libraries: iOS CryptoKit.HKDF + AES.GCM; Android javax.crypto.
Mints a Wallet Attestation JWT (WIA / WA per HAIP §5.11) bound to the wallet's public key. Cached client-side for 23 hours. Under the OEM tier, the issuer key is the OEM's own KYB-attested key.
Native parity: attach the platform app-attestation (Apple App Attest assertion / Google Play Integrity token) as a Bearer companion or in a header your OEM tenant is configured to expect. The KYB step wires which app-attestation your OEM tenant will accept.
D. Wallet operations
Vault sync (encrypted server backup), narrow-scope session tokens, mDoc verification, and tenant DID discovery.
Mints a narrow-scope HS256-signed token used only for vault push / pull / delete. Trades the full OIDC access token for a short-lived storage-scoped token whose payload carries the caller's OEM azp so wallet-backup can preserve per-OEM isolation.
The minted JWT's payload additionally includes iss=codeb-wallet-session, sub, tenant, jti, azp, scope="wallet-backup:save wallet-backup:load wallet-backup:delete".
Native parity: straightforward. Refresh before each vault sync attempt or on 401. The narrow token is HS256 to a per-tenant HMAC key that only wallet-backup.ashx knows; do not attempt to introspect it against your OIDC provider.
Push the encrypted vault blob to the server. The server stores it opaquely and never sees the plaintext.
Request body (opaque to server)
{
"v": 1,
"wrappedDeks": [ { "kek_id": "...", "dek_cipher": "..." } ],
"cipher": "<AES-GCM ciphertext of the vault contents>",
"updatedUtc": "2026-08-12T18:30:00Z",
"hint": "<optional device hint for conflict resolution>"
}
Native parity: the KEK is derived from a device-attested factor. Web wallet uses WebAuthn PRF + device-bound HKDF; native app substitutes with iOS LAContext-gated key from Secure Enclave (via SecAccessControl(.biometryCurrentSet)), or Android BiometricPrompt-gated key from Keystore. Never decrypt on-server; the server has no keys.
Pull the encrypted vault blob. Response is the same opaque JSON persisted at save time. 404 {"error":"not_found"} if the user has never saved a vault.
Native parity: after fetch, decrypt using the same KEK the local device holds. Merge with local state via updatedUtc comparison. Do not overwrite a newer local vault silently — surface a conflict prompt.
Server-side verification of a stored mDoc credential. Used by the wallet to display trust indicators without shipping the mDoc verify stack into the client.
Native parity: cache results 5 minutes per credential id, keyed by sha256(issuerSigned). Native apps that ship an mDoc library MAY verify locally instead; the endpoint remains available as a fallback.
RP-initiated logout. Sweeps the server-side session and returns 200 text/html containing a Clear-Site-Data header plus a JS/meta redirect to post_logout_redirect_uri. Not a 302 — the response has to reach the browser so Clear-Site-Data can wipe cookies/storage before navigation.
Native parity: in-app browser (ASWebAuthenticationSession / Custom Tabs) opens this URL. Native apps CANNOT rely on Clear-Site-Data to wipe local storage — on sign-out, ALSO clear the wallet's own vault + WA cache + credential-key handles locally, matching the two-sided sweep pattern in memory codeb-signout-two-sided-sweep.
Delete the server-side vault blob. Requires the caller to POST {"confirm":"delete"} to reduce accidental wipes. Symmetric with save/load; keyed on the same (sub, tenant, azp).
Native parity: expose as a “delete cloud backup” option in the wallet's settings screen. Do NOT chain to local-vault delete without an explicit confirm dialog.
Member-facing offer mint: builds an OID4VCI credential-offer URL that the signed-in user can consume from another device (e.g. desktop generates the QR, phone wallet scans it). Complement to B2 (which issues in-session on the same device).
Native parity: use to hand off issuance from a desktop or web session to a native wallet on the phone.
ARF 3.0 TS7 data-deletion right endpoint. The signed-in wallet user can request server-side deletion of their PII footprint (session logs, vault, cached credentials, WA issuance history).
Native parity: surface in the wallet's privacy settings screen. Required for GDPR / ARF conformance.
ARF 3.0 TS8 DPA-report endpoint. Operator-only (superuser); produces a Data Protection Authority-facing report of user activity with a 24-month cap. Not called from the wallet; documented for OEM operators.
Native parity: N/A — consumed by tenant admins from a dashboard, not the wallet itself.
Native-parity summary — what your app must implement locally
The web wallet uses browser primitives; native apps substitute with platform equivalents. This inventory maps each substitution.
PKCE code_verifier / state → CSPRNG via SecRandomCopyBytes / SecureRandom.
Storage substitutions
IndexedDB (vault, credentials, WA cache, settings) → iOS Core Data or SQLite with SQLCipher; Android Room with SQLCipher. Keys wrapped by Keychain / Keystore-held KEKs.
localStorage (dynamic-client-reg cache) → iOS UserDefaults (non-secret) or Keychain (if you consider client_id sensitive); Android EncryptedSharedPreferences.
sessionStorage (PKCE verifier) → in-memory app state; drop on backgrounding.
Browser-only endpoints your app should skip
/oidc.ashx?action=jar-proxy (C1) — only for browser CORS bypass. Fetch request_uri directly.
Any use of navigator.credentials (WebAuthn PRF) — substitute with platform biometric-gated Keychain / Keystore keys.
Service-worker offline caching — substitute with your app's own resource cache.
Common headers to send from your native client
User-Agent: <OEM app name>/<version> (native; iOS 17.4; iPhone15,3) — helps our support triage and lets us apply per-OEM rate limits accurately.
X-CodeB-Client-Id: <your OEM OIDC client_id> — optional but recommended for support diagnostics.
Accept-Language: <user locale> — some error responses localise error_description.