A concrete end-to-end example of the self-service flow. Built around a credential definition created from the City Card template, this is what you'd show in an integration meeting: one form, one webhook, one approve call.
If you're a developer and want the smallest possible setup, see Quickstart first. If you need the full state machine, every event payload and edge case, see Detailed Reference.
The City of Springfield wants to issue a digital City Card to verified residents. The card carries the resident's name and date of birth, signed by the city, and lives in their wallet — usable for library lookups, public transport, age checks, and so on.
The constraint: residents shouldn't have to type anything. They should be able to prove they're who they say they are — picking whichever of the city's accepted identification methods suits them at the time — and the card should drop into their wallet seconds later.
Only the cross-system parts. Everything inside DeepCredentials and the identification provider (identification UI, OID4VP exchange with the wallet, state machine) is intentionally hidden — that's our problem to solve.
Three roundtrips total. Anything more complex — identification failure, rejection, expiry, retries — is a variation of the same shape and is covered in the Detailed Reference.
- Opens the share-link Springfield published on its website.
- Picks one of the identification methods the city accepts (e-ID, QES, passport-based, …) and completes it with the identification provider.
- Returns to the share-link page; a moment later a wallet deeplink appears (or arrives by email). Card is in the wallet.
No form to fill in. No data to type.
No code. Three UI tasks:
- Create the credential definition. Credential Definitions → Create credential definition → From a template → City Card → walk the wizard to Create, enabling the environment the card will be issued in. The template prefills the three claims and the card layout; the definition it produces is the city's own, under the city's own
vct, and that is the one you issue from. (A template cannot be issued from directly. If you wanted a custom shape you'd start blank, or edit the claims the template filled in.) - Register a webhook in the DeepCredentials portal under B2B API → Webhooks, for the same environment, and subscribe to at least
self_service.request.submittedandself_service.request.credential_issued. This comes before the config: awebhook-mode config can't be saved until the environment has a webhook endpoint. For the runtime B2B calls, use a Bearer token obtained for a DeepAdmin service user through DeepCloud with thedeepcredentials.self-servicescope; DeepCredentials does not mint an API key. - Create the self-service config. Self-Service → New config:
- Credential Definition — the City Card definition you just created.
- Verification mode — webhook.
- Identification — required. Pick the methods you'll accept (e.g. QES, optionally with Swiss e-ID passport); the resident picks one on the share-link page, and every method returns the same canonical set of verified attributes.
- Claim mapping — for each City Card claim, pick the identification attribute it draws from:
given_name←given_namefamily_name←family_namebirth_date←birth_date
- Credential validity — 2 years.
That's the entire setup. The share-link is now live at whatever slug you chose.
Three things, in this order: verify the webhook signature, fetch the snapshot, post the decision. With pre-filled identification claims and no admin-fill fields on this credential definition, the backend doesn't need to provide any data of its own — a passive approve is enough.
@app.post("/webhooks/deepcredentials")
def handle_webhook(req):
# 1. Verify the HMAC signature (see Webhooks guide for the helper).
verify_signature(req)
if req.json["event"] != "self_service.request.submitted":
return 200 # ignore other events for now
request_id = req.json["data"]["request_id"]
# 2. Fetch the snapshot — claims are already pre-filled from identification.
snapshot = dc.get(f"/b2b/v1/self-service/requests/{request_id}").json()
# (Optional) cross-check the resident against your roll.
if not springfield_roll.is_resident(snapshot["claims"]):
return dc.post(
f"/b2b/v1/self-service/requests/{request_id}/decision",
json={"decision": "reject", "reason": "Not on the resident roll."},
)
# 3. Approve. No admin_claims needed — every claim is identification-pre-filled.
return dc.post(
f"/b2b/v1/self-service/requests/{request_id}/decision",
json={"decision": "approve"},
)That's the whole production-grade integration. ~20 lines.
Maybe Springfield's CRM has the resident's preferred name on file and wants to use it instead of the legal given_name the identification returned. Add the override to the approve payload:
dc.post(
f"/b2b/v1/self-service/requests/{request_id}/decision",
json={
"decision": "approve",
"admin_claims": {"given_name": "Lisa"},
},
)Override precedence is fixed: user → identification → admin. Admin wins on any collision, every other claim flows through as it was pre-filled. See Detailed Reference → Overriding user-supplied claims for the full rules.
If Springfield's volume is low (a handful of cards a week), they can skip the backend integration entirely:
- In the config, set Verification mode =
manual(instead of webhook). - No webhook endpoint needed.
- Springfield's ops team gets a Requests table in the portal with Approve and Reject buttons. The same identification pre-fill works the same way.
When volume grows, register a webhook endpoint and flip the config to webhook — the rest of the setup (credential definition, identification mapping, validity) stays as is.
From the resident's tap on the share-link to the card landing in their wallet is under 30 seconds with zero data entry. Springfield wrote about 20 lines of code, and the heavy lifting — identity verification, credential issuance, wallet handover — is on us.
- Quickstart — the same flow without the use-case framing.
- Detailed Reference — every state, every event, error codes, idempotency, signature rotation.
- Webhooks — signature verification, event subscriptions, one-attempt delivery, and reconciliation.
- Credential Issuance Flow — machine-to-machine alternative when your backend already has the data.