Receive the Issued Credential
When an issuance completes, the issuance service pushes a copy of the issued credential to your application. This is step 9 of the issuance flow, and configuring it is how you learn that a credential was delivered without polling.
Requirements
- An installed credential issuance service.
- At least one credential configuration, so there is something to issue.
1. Expose an endpoint
Your application needs an endpoint that accepts POST requests with a JSON body. The issuance service calls it
once per completed session.
Give it a secret and check it. The configuration below sends a bearer token with every call; verify it on your side before processing the body. Without that check, the endpoint is an unauthenticated way into your application for anyone who learns the URL.
2. Configure the callback
Set sessionCallback in the issuance service's server.json:
{
"port": "...",
"baseUrl": "...",
"database": {
"..."
},
"issuer": {
"..."
},
"signing": {
"..."
},
"sessionCallback": { // (1)!
"targetUrl": "https://example-application.com/issued-credential", // (2)!
"bearerToken": "81952788-6321-412c-91b9-8f61ee2a1e52" // (3)!
}
}
- Where the copy of each issued credential is pushed, and how the request authenticates itself.
- Required within this block. The URL of the endpoint from step 1. It must be reachable from inside the issuance service's container — not merely from your desktop.
- Optional, but configure it. Sent as
Authorization: Bearer 81952788-6321-412c-91b9-8f61ee2a1e52on every callback.
3. Restart the service
Configuration is read at startup. Stop the container and start it again with the updated file:
docker run -d \
-p 8079:8079 \
-w /app \
-v "$(pwd)/issuer/conf:/app/conf:ro" \
registry.gitlab.com/secata/platform/did/release/issuer-backend:latest
What you receive
curl -X POST 'https://example-application.com/issued-credential' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer 81952788-6321-412c-91b9-8f61ee2a1e52' \
-d '{
"issuance_successful": true,
"session_id": "7d4633b273f000ca2243e258863975907756b3afc29783c7777cf47839264722",
"credential_response": {
"credentials": [
{ "credential": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6andrOi…~WyJ4cjRnako0N3FUeXJNbDBnaVNKa1dnIiwiZmlyc3RfbmFtZSIsIkplbnMiXQ~" }
]
}
}'
session_idmatches what you got back when you started the issuance. Use it to tie the result to the right holder.issuance_successfulis whether the credential reached the wallet.credential_response.credentialsholds the issued credentials themselves.
What is inside the credential
The credential value is the SD-JWT that now lives in the holder's wallet. Decoding its payload is worth doing
once, because two of its fields matter to you operationally:
{
"iss": "issuer.example.org",
"sub": "did:jwk:eyJjcnYiOiJQLTI1NiIs…",
"nbf": 1704067200,
"exp": 4102444800,
"vc": {
"type": ["VerifiableCredential", "IdCard"],
"credentialSubject": {
"id": "did:jwk:…",
"_sd": ["PgFAKDxd_8r9IhSlZhHHkf2n7CzGYZSp1YyeOnFWMEc", "…"]
},
"status": {
"status_list": {
"idx": 3,
"uri": "https://issuer.example.org/credential-status/status-list"
}
}
}
}
status.status_list.idxis the credential's index in your status list, and it is the only way to revoke this specific credential later. Store it alongside your own record of who the credential was issued to. See Revocation.subis the holder'sdid:jwk:identifier, derived from the key in their wallet. It is what binds the credential to that wallet.
The claim values do not appear in the payload directly — _sd holds hashes of them, and the values themselves are
the ~-separated disclosures appended after the signature. That is what makes selective disclosure possible: the
holder can send some disclosures and withhold others while the signature stays valid.
Keep only what you need from this payload
The copy contains the claims you issued, in readable form. Recording the session_id and the status list
idx is enough to reconcile the issuance and to revoke it later, so discard the rest rather than storing a
second copy of data the holder now carries.
If results do not arrive
Check, in order:
targetUrlis reachable from inside the container — not just from your machine.- Your endpoint returns a success status.
- The session actually completed:
GET /issuance/oid4vci/status/{session_id}and readerror_message.
More in Troubleshooting.
What's Next
- Revocation: What to do with the
idxyou just recorded. - Configuration reference: Every issuer setting.
- Going to production.