Tutorial: build an issuer integration
Take the first lifecycle tutorial and make it something you would deploy.
Assumes: you have completed that tutorial.
Separate configuration from runtime
Configuration — organisations, issuers, credential configurations — belongs in a setup script or deploy step, run rarely and reviewed. Issuance belongs on your request path.
Mixing them means every request carries code that only matters once, and configuration changes ride in on whatever deploy happens to be next.
deploy pipeline -> Management API -> issuers, credential configurations
application -> Product API -> issuance
Script your configuration. Then creating the Production equivalent is the same script with a did_live. key.
Make every issuance idempotent
Derive the key from your own business identifier, once, before the first attempt:
Idempotency-Key: enrolment-2026-a41f9
Not a fresh UUID per attempt. See use idempotency.
Model the wait honestly
An issuance reaches awaiting_wallet and stays there until the holder accepts. Your data model needs a state
for that, and your UI needs to say something true about it.
issued_offer -> awaiting_wallet
holder_accepted -> the credential exists
expired -> the offer lapsed; reissue if still appropriate
An integration that reports "credential issued" at the point of the API call is telling users something that is not yet true.
Validate claims before you call
Issuance rejects claims not declared in the credential configuration. Catching that in your own validation gives a better error message and one less round trip.
Also decide what to do with missing optional claims: omitting them and sending an empty string are different, and the second produces a credential asserting an empty value.
Handle failure properly
| Status | Action |
|---|---|
400 |
A bug or bad data. Log and surface. Do not retry |
409 |
Idempotency key reused with a different payload. Investigate — likely a key collision in your code |
429 |
Back off and retry |
5xx |
Retry with backoff, same idempotency key |
Log x-ms-request-id on every failure. It is the difference between a diagnosable report and "it broke".
Least privilege
Your issuance service needs credentials.issue and probably credentials.read. It does not need
issuers.manage, credentials.revoke or anything about verification.
Use a separate key for the setup pipeline with the management scopes. A leaked issuance key should not be able to reconfigure your issuer.
Before production
- Configuration scripted and run against Production with a
did_live.key - Key read from a secret store, never committed
- A startup assertion that the key's environment is the one you expect
x-ms-request-idlogged on every response- Retries carrying idempotency keys
awaiting_walletrepresented honestly in your model and your UI