Skip to Content
How-To GuidesOnboard a BYOK Issuer

Onboard a BYOK Issuer

Who this is for: an institution that wants to keep custody of its own Ed25519 signing key (HSM/KMS) instead of letting Attest ID hold it, and needs the task order — not the byte-level canonicalization spec — to get there.

Every claim below traces to BYOK Signing Spec (the authoritative contract) and BYOK Reference SDK & Oxford Demo Issuer. Where the two disagree, the signing spec wins.

Why BYOK instead of the default (custodial)

By default Attest ID generates and holds your signing key. BYOK inverts that: you keep the private key in your own HSM/KMS and Attest ID only ever sees your public key and your signatures — never anything that lets it reconstruct your private key.

Steps

1. Generate an Ed25519 keypair in your own key custody

Production: an HSM (PKCS#11), AWS KMS, Azure Key Vault, Google Cloud KMS, or equivalent — never a plaintext on-disk key. For local evaluation only, the reference SDK’s LocalKeystoreSigner (clients/byok-sdk-java) is explicitly a demo-only software keystore — don’t point it at anything you’d call production.

2. Register your public key

POST /api/v1/issuer/register { "name": "...", "website_url": "...", "contact_email": "...", "country_code": "...", "byok": true, "public_key": "<base64, raw 32-byte Ed25519 public key>" }

public_key is required once byok: true — registration fails 400 without it. You’ll get back status: "PENDING_APPROVAL" — unconditional for every issuer, custodial or BYOK; there is no path to ACTIVE straight out of registration.

3. Wait for admin approval

An Attest ID admin calls POST /api/v1/issuers/{issuer_did}/approve. IssuanceOrchestrator rejects issuance for any issuer whose status isn’t exactly ACTIVE. There’s nothing to poll for this specifically — retry issuance, or check status via GET /api/v1/issuers/{issuer_did}.

4. Build the credential, canonicalize, and sign — entirely on your side

This is the part with real byte-for-byte precision requirements — don’t reimplement it from a paraphrase. Follow the signing spec §2–§4 exactly, or use the reference SDK’s VcCanonicalizer/Signer (clients/byok-sdk-java), which implements the identical algorithm. The two things every from-scratch implementation gets wrong first:

  • You generate credential_id, issuanceDate, and the proof’s created yourself, before signing — not the server. Truncate every timestamp to millisecond precision before formatting, or your signature won’t verify.
  • You sign the concatenated hash pair (configHash || documentHash), never the raw document — signing the document alone leaves created/proofPurpose/verificationMethod completely unauthenticated.

5. Submit the issuance request with your signature

POST /api/v1/credentials/issue { "issuer_did": "...", "holder_did": "...", "credential_type": "...", "credential_data": {...}, "signature": "<base64, 64-byte Ed25519 sig>", "key_id": "...", "credential_id": "...", "issuance_timestamp": ..., "expiration_timestamp": ..., "proof_created_timestamp": ... }

All five BYOK-only fields are required together — a request with a signature but no proof_created_timestamp is rejected outright, since the server has no other way to know what created you actually signed over.

6. Assemble and hand over the final signed credential

The issuance response does not echo the signed VC — you already have everything needed: the unsigned shell from step 4, plus the signature and key_id you just used. Attach the proof object per the signing spec §6, using the exact created you signed over. This resulting document is the shareable, fully signed W3C VC.

7. Confirm the round trip

POST /api/v1/credentials/verify { "credential": { ...the fully signed VC... }, "check_status": true, "check_expiration": true }

is_valid: true and status: "VALID" confirm your signature verifies against your registered public key.

If something doesn’t verify

Almost always one of: a sub-millisecond timestamp that didn’t round-trip through Instant.ofEpochMilli, signing the document instead of the configHash || documentHash concatenation, or a created in the final proof object that doesn’t exactly match what was signed. See the signing spec §2.3–§2.4 and §6 for the precise byte-level rules.

Reference implementation

clients/oxford-demo-issuer exercises this exact flow end-to-end against a running Attest ID deployment, built entirely on the public clients/byok-sdk-java SDK.