Troubleshooting
Failures common to both the issuer and the verifier, and how to tell them apart.
Start with the logs — docker logs <container> — and confirm the container is actually running with
docker ps. Both services print Starting server once they are ready to serve; if you never see that line, the
problem is in startup, not in your request.
Starting up
docker compose pull fails with an authentication or denied error.
Docker is not logged in to the registry, or the account lacks access. Run docker login registry.gitlab.com and
pull again. If it still fails, the account does not have access to the image —
book a call to arrange it.
Port already in use (8079, 8081 or 5433).
Stop whatever holds the port, or change the left-hand side of the mapping in docker-compose.yml, e.g.
"18081:8081". Change only the host side; the right-hand side must keep matching port in server.json.
The service exits complaining about the config file or the certificate.
signing is required, and the service will not start without both keyFile and certificateFiles present and
readable. Generate the pair before the first start — see
Signing certificates — and remember that paths in server.json are relative to the
container's working directory /app, so conf/signing-key/x.key means the file is at that path inside the
container. A path that exists on your host but is not mounted will not be found.
The service starts, then fails on a database migration. The log names the migration that failed. This normally means the database already carries a schema from a different version. Do not roll the image back and restart — restore the backup you took before upgrading. See Database.
The service cannot reach the database.
Inside a Compose network the database host is the service name, not localhost. jdbc:postgresql://postgres:5432/verifier
is correct; localhost:5432 is what a container would resolve to itself. Gate the backends on the database's
health check rather than start order.
The database exists but the service says the schema is missing.
The postgres-init scripts run only against a fresh data volume. If you added or edited them after the first
start, they never executed. docker compose down -v && docker compose up re-runs them — and deletes all data.
Making requests
401 on a call you expect to work.
The endpoint is administrative and needs an API key as a bearer token. Add
-H 'Authorization: Bearer <your key>'. Check you are sending the key and not its label: in
integrations.authentication the JSON key is the token. See API keys.
400 Bad Request when starting a verification session.
verification_query_id is required in the request body. A session cannot be started without naming a
verification query that already exists. Create one first with POST /verification-query/create. See
Request a presentation.
The OpenAPI page will not load.
Confirm the container is running and check its logs for configuration errors. The specification is at
/openapi on each service's own port — 8079 for the issuer, 8081 for the verifier.
The wallet side
The wallet scans the QR code and then nothing happens.
Almost always baseUrl. It is the address the service advertises to wallets, and a phone cannot resolve
localhost or reach a private address it is not on. Set baseUrl to something the phone can reach and restart
the container.
The wallet refuses the verifier's request, or shows a trust warning.
That wallet validates the certificate chain in the signed request against its own trusted roots, and checks
that the request's client_id hash matches — a check wallets are free to make or skip, which is why the
same request can work with one wallet and be refused by another. A self-signed verifier certificate is
accepted only by a wallet that does not check, or one configured to accept it. See
Trust model.
A credential fails verification with a trust or certificate error.
Its chain did not validate against your issuerTrustAnchors. Check the directory contains the right root and that
the files are named *.pem — nothing else is loaded from it. If the chain is genuinely from an issuer you do not
accept, the rejection is correct. See Issuer trust anchors.
A chain that looks right still fails. Certificates are checked for their validity period as well as their path, so an expired intermediate fails. Chains longer than 10 certificates are rejected before validation begins.
A credential verifies when you expected it to be rejected. Most likely you have no trust anchors configured, which skips the issuer check entirely — the signature is checked against whatever certificate the credential presents, so a self-signed one passes. The startup log carries a warning when the verifier is in this mode, so check there first. Configure trust anchors, or check the issuer identity in the result against your own accepted list. A newly added anchor needs a restart to take effect.
Sessions
A session reports FAILED.
Both status endpoints return an error_message alongside the status, which says what went wrong. Read that before
anything else: GET /issuance/oid4vci/status/{session_id} and
GET /presentation/oid4vp/status/{session_id}.
Results never arrive at your application.
Check sessionCallback in server.json points at a URL the service can reach — from inside its container, not
from your desktop — and that your endpoint returns a success status. See
Receive the issued credential and
Receive verified data.