Receive Verified Data
When a verification session completes, the verification service pushes the result to your application. This is step 6 of the verification flow, and configuring it is how you get the claims without polling.
Requirements
- An installed credential verification service.
- A verification query, so there is something to verify.
1. Expose an endpoint
Your application needs an endpoint that accepts POST requests with a JSON body. The verification 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 verification service's server.json:
{
"port": "...",
"baseUrl": "...",
"database": {
"..."
},
"signing": {
"..."
},
"sessionCallback": { // (1)!
"targetUrl": "https://example-application.com/verified-claims", // (2)!
"bearerToken": "5I90CwXlZoolopOSplVBOoV63qXSTEo4piJKJ0iizFau79HU6viEvzgGReWycN8V" // (3)!
}
}
- Where verification results are 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 verification service's container — not merely from your desktop.
- Optional, but configure it. Sent as
Authorization: Bearer <value>on every callback.
claims and destinationUrl still work
This block used to be called claims, with the URL under destinationUrl. Both older names are still
accepted as aliases, so an existing configuration keeps working. New configurations should use
sessionCallback and targetUrl — the issuer uses the same names, so the two services stay consistent.
An auth field was described in older documentation. It does not exist. Authentication is bearerToken.
3. Restart the service
Configuration is read at startup. Stop the container and start it again with the updated file:
docker run -d \
-p 8081:8081 \
-w /app \
-v "$(pwd)/verifier/conf:/app/conf:ro" \
registry.gitlab.com/secata/platform/did/release/verifier-backend:latest
What you receive
curl -X POST 'https://example-application.com/verified-claims' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer 5I90CwXlZoolopOSplVBOoV63qXSTEo4piJKJ0iizFau79HU6viEvzgGReWycN8V' \
-d '{
"session_id": "14eb99d399dda3264a0ad85b93b26eab5396ce17e50056968c2749049ebf6ec1",
"verification_result": true,
"claims": {
"first_name": "Jens",
"last_name": "Jensen",
"city": "Aarhus"
}
}'
session_idmatches what you got back when you started the session. Use it to tie the result to the right interaction.verification_resultis whether the presentation passed cryptographic validation.claimsholds the disclosed values.
How claim keys are named
The keys in the claims object are chosen in this order of priority:
- The
idfield, if you specified one on the claim in the DCQL query. - Otherwise, the elements of
pathjoined with dots — e.g.address.city.
Set id on every claim you query if you want key names that are stable against changes to the credential
structure.
What verification_result: true covers
The presentation was correctly signed and untampered, and the issuer's certificate chain validated against the trust anchors you configured. With no anchors configured that last check does not run — see Trust model.
Accept or reject the result
The verification service checks that the presentation is genuine. Whether the claims are good enough for your application — the holder is old enough, the membership has not lapsed — is your decision, and you give it in the HTTP status your endpoint answers with:
| Your endpoint | Session status |
|---|---|
Answers with a 2xx status |
VERIFICATION_SUCCEEDED |
| Answers with any other status | DELIVERY_REJECTED |
| Cannot be reached, or does not answer in time | DELIVERY_FAILED |
The callback is sent once and is not retried, so answer with a 2xx only once you have taken the result in. The
holder's wallet waits while your endpoint runs, so answer within a few seconds and do any slow work afterwards.
Answer with a JSON body that says why, especially when you reject:
{
"message": "The holder must be at least 18 years old."
}
The verification service stores the body you answer with verbatim on the session, for audit, both when you accept and when you reject. Only the status decides the outcome: the body is not interpreted, so a missing or non-JSON body changes nothing. The body is not returned by the status endpoint. Keep personal data out of it — the claims you received do not belong in the answer.
Results that did not verify
A session that ends in VERIFICATION_FAILED or WALLET_REJECTED is also sent to your endpoint, with
verification_result: false and no claims. There is nothing to accept, so your answer to that call is
ignored and not stored.
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 /presentation/oid4vp/status/{session_id}and readerror_message.
More in Troubleshooting.
What's Next
- Request a presentation: The other half of the integration.
- Configuration reference: Every verifier setting.
- Going to production: Authenticating the callback endpoint, and what else has to change.