Skip to content

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.