Get started

Docs · Get started

Quickstart

Create a verification, let a wallet answer it, and read the verified claims — the whole round trip in four steps.

Before you begin

You need an organisation and an API key. Sign in to the dashboard, open Keys, and mint one. Tick verifications:create and verifications:read — enough for this walkthrough.

Keep it out of your shell history
export VALITEL_API_KEY='vk_test_...'

1. Create a verification

Request
curl -s -X POST https://api.valitel.eu/v1/verifications \
  -H "authorization: Bearer $VALITEL_API_KEY" \
  -H "content-type: application/json" \
  -d '{"template":"age_over_18"}'
201 Created
{
  "id": "vrf_G3mgaU2NnqZU-vNBd_jFAw",
  "status": "pending",
  "template": "age_over_18",
  "wallet_uri": "openid4vp://?client_id=valitel-dev-verifier&request_uri=...",
  "request_uri": "https://api.valitel.eu/wallet/request/vrf_G3mgaU2NnqZU-vNBd_jFAw",
  "created_at": "2026-08-14T16:20:43.170Z",
  "expires_at": "2026-08-14T16:25:43.170Z"
}

wallet_uri is what you render for the holder. request_uri inside it points at the signed request object; you never fetch that yourself — the wallet does.

2. Show the wallet URI to the holder

Render wallet_uri as a QR code for a cross-device flow, or as a link the holder taps for a same-device flow. The session expires at expires_at — 300 seconds after creation by default, adjustable per verification with ttl_seconds.

If you would rather not build the QR modal and the polling loop, the drop-in widget does both; see the widget below.

3. Read the result

Request
curl -s https://api.valitel.eu/v1/verifications/vrf_G3mgaU2NnqZU-vNBd_jFAw \
  -H "authorization: Bearer $VALITEL_API_KEY"
200 OK
{
  "id": "vrf_G3mgaU2NnqZU-vNBd_jFAw",
  "status": "completed",
  "template": "age_over_18",
  "claims": { "age_over_18": true },
  "credentials": [
    {
      "format": "mso_mdoc",
      "doctype_or_vct": "eu.europa.ec.av.1",
      "claims": { "age_over_18": true },
      "issuer": { "common_name": "Test Document Signer", "country": "UT" },
      "trusted": true,
      "signature_valid": true,
      "holder_binding_valid": true,
      "findings": [
        {
          "check": "san_match",
          "status": "skipped",
          "detail": "SAN matching applies to the sdjwt-issuer profile only"
        }
      ]
    }
  ],
  "created_at": "2026-08-14T16:20:43.170Z",
  "expires_at": "2026-08-14T16:25:43.170Z",
  "responded_at": "2026-08-14T16:20:47.117Z"
}

trusted: true is the single bit that means every trust check passed. findings lists only the checks that did not pass outright — the skipped entry above simply records that SAN matching does not apply to the mdoc profile.

4. Or have the result delivered

Polling is fine for a demo. In production, create the verification with a webhook_url and Valitel posts the same payload to you the moment the session reaches a terminal state, signed with your webhook secret.

Request
curl -s -X POST https://api.valitel.eu/v1/verifications \
  -H "authorization: Bearer $VALITEL_API_KEY" \
  -H "content-type: application/json" \
  -d '{"template":"age_over_18","webhook_url":"https://your-server.example/webhook"}'

See Webhooks for the payload, the signature scheme and a reference verifier.

The widget variant

The drop-in widget renders the button, the QR modal and the deep link, and polls the public status endpoint until the session is terminal. It never sees claims — only {id, status}.

HTML
<div id="verify"></div>
<script src="/widget.js"></script>
<script>
  const started = await (await fetch("/checkout/start", { method: "POST" })).json();
  // started = { id, wallet_uri, status_url }

  window.ValitelWidget.mount({
    el: "#verify",
    walletUri: started.wallet_uri,
    statusUrl: started.status_url,
    label: "Verify age",
    onSuccess: () => { /* order placed */ },
    onFailure: (v) => { /* v.status is "failed" | "expired" */ },
    onError: (e) => { /* e.kind — diagnostics, not a verdict */ },
  });
</script>

The modal polls every 1.5 seconds (pollIntervalMs) until a terminal status or a 300-second timeout. ValitelWidget.open is the same modal without the mount-point button, for merchants driving their own trigger. Every class is prefixed valitel- and every element carries a data-valitel-* hook, so the widget never collides with your styles.

onError is diagnostics, not a verdict — it fires for trouble that never reaches onFailure: wallet_link_unresponsive (the deep link produced no app switch), tab_backgrounded (the browser throttled polling while hidden), csp_violation (your CSP blocked the widget's styles or its status poll), timeout_expired, and poll_failing (five consecutive unusable status responses). Each is also written to the browser console whether or not you wire a handler, and every status poll carries the widget build as ?wv=<version>.