Skip to main content

SDKs

Veridia publishes four SDKs. Three are server/API clients (JavaScript, Python, PHP). One is a mobile capture UI (Flutter).

All four are 0.1.0. The API surface is stable, but treat the version as what it says it is: pre-1.0.

The four

SDKPackageRegistryRequiresWhat it is
JavaScript / TypeScript@veridia/sdknpmNode 18.17+ (or a browser)API client + webhook verification
PythonveridiaPyPIPython 3.11+API client (sync + async) + webhook verification
PHPveridia/veridia-phpComposerPHP 8.2+API client + webhook verification
Flutterveridia_sdkprivate gitDart 3.10.3+ / Flutter 3.38.4+Camera capture UI as a single widget

The package name for JavaScript is @veridia/sdk. Not @veridia/sdk-js.

There is no React Native SDK

There never was one. If you found a reference to @veridia/react-native or a "React Native" entry in an older version of this sidebar, it pointed at a package that does not exist on any registry.

For React Native today, your options are the HTTP API directly plus your own capture screens, or a WebView hosting the web widget.

Which one do I need?

The capture step and the result step are different jobs, and they need different keys.

Capturing images — camera access, quality checks, uploading bytes. This runs where the user is: the web widget in a browser, or the Flutter SDK in an app. Both use a publishable key.

Reading the outcome — this runs on your server, with a secret key. Any of the three server SDKs does it, and so does a plain HTTP request.

The three server SDKs can also run init and submit, which is what you want if you capture images with your own code and just need a typed client for the API.

Key types, and which SDK can read a verdict

PrefixKindBelongs ininit + submitRead verdicts
qv_pub_, qv_pubt_publishablebrowser, mobile appyesno
qv_sec_, qv_sect_secretyour server onlyyesyes

The t variants are test mode. The test prefix is qv_pubt_ / qv_sect_ — not qv_pub_test_.

GET /v1/verify/{id} refuses a publishable key with 401 secret_key_required. That is deliberate: a publishable key sits in page source where anyone can read it, and a verification result carries the extracted identity fields. A publishable key that could read results would publish every one of your customers' KYC outcomes to every visitor.

This is why the Flutter SDK accepts only a publishableKey and cannot fetch a verdict. Shipping a secret key inside an app binary is not a workaround — an APK is unpacked in minutes, and the key it yields reads the verdicts of every customer in your tenant, not just the one holding the phone.

SDKCan capture imagesCan init / submitCan read a verdict
JavaScriptnoyesyes, with secretKey
Pythonnoyesyes, with a qv_sec_* key
PHPnoyesyes, with a qv_sec_* key
Flutteryesyesno, by design

The flow every SDK wraps

init → PUT each image → submit → webhook (or poll)
  1. POST /v1/verify/init returns one upload slot per image (docFront, docBack, selfie), plus an expiresAt in unix seconds.
  2. PUT the bytes to each slot URL. Send the slot's headers verbatim — see below.
  3. POST /v1/verify/submit with the keys from the init response. keys is required.
  4. The pipeline takes roughly 15 seconds. The outcome arrives by webhook, or you poll GET /v1/verify/{id} with a secret key.

Every SDK exposes a helper that collects the keys for you — keysFrom (JS), keys_from (Python), VerifySubmitKeys::fromInit (PHP) — because forgetting them is the single most common way to get a 400 out of submit.

Uploads do not go to R2

The slot URLs point at a Veridia endpoint, not at presigned object storage. They authenticate with X-Veridia-Upload-Token, a short-lived per-verification credential carried inside the slot's own headers. Your API key does not authenticate that endpoint at all.

Two consequences worth knowing before you write any upload code by hand:

  • Forward slot.headers verbatim. Rebuilding the headers yourself — or sending only Content-Type — drops the token, and every upload fails. All four SDKs do this correctly.
  • Allowlist the API host, not a storage host. If you are writing a CSP or an egress firewall rule, the bytes go to api.xxuxe.online.

The body must be a real JPEG (the endpoint checks for the FF D8 FF magic number) and at most 8 MB.

You may still see "presigned R2 upload" in an SDK's own README or in a doc comment. That wording is stale; the code in all four SDKs forwards the slot headers and is correct.

status is not verdict

Two independent axes, and conflating them is the most expensive mistake this API offers.

FieldQuestion it answersValues
statusDid the pipeline run?queued processing completed failed
verdictDid the person pass?approved review rejected — absent until completed

completed means the pipeline reached a conclusion. An approved, a review-required and a rejected verification are all completed. Branching on status to admit a user admits every rejected applicant.

Poll on status. Decide on verdict.

Two related traps: a null verdict is not a rejection (it means no decision was reached, either still running or failed), and review is final — it means a human has to look, not that the result is still settling. Polling a review waiting for it to resolve waits forever.

metadata does not survive

POST /v1/verify/submit accepts a metadata object, but it is dropped at the edge: it does not reach the pipeline and it is not present in the webhook payload. Do not use it to route or reconcile anything.

userRef, set at init, is the field that ties an event back to a user in your system. It is echoed in the webhook. If you also need to correlate on the polling path, store the verificationId → user mapping yourself: GET /v1/verify/{id} does not return userRef.

Webhooks

All four SDKs ship HMAC-SHA256 signature verification. Use it — the outcome of a human review can arrive long after submit returned, and the webhook is the only channel that carries it.

Facts that apply regardless of language:

  • The header is Veridia-Signature: t=<unix>,v1=<hex>. There is no X-Veridia-Signature and no separate timestamp header — the timestamp lives inside the signature value.
  • The MAC covers the bytes "<t>." + rawBody. Verify against the raw body. Re-serializing the JSON changes the digest and the check fails.
  • The payload is flatverdict, verificationId, userRef at the top level. There is no data envelope.
  • The discriminator is type, not event.
  • Exactly three event types: verification.approved, verification.review_required, verification.rejected. There is no created and no expired event to wait for.
  • Delivery is at-least-once. Deduplicate on id (evt_<hex>), which is stable across retries.
  • The default replay tolerance of 300 seconds is correct. Do not widen it — the dispatcher re-signs on every retry attempt, so even the last retry arrives with a fresh t.
  • fieldsExtracted carries identity PII: full name, document number, date of birth. That is why the endpoint must be https, and why the payload should not be written verbatim into application logs.

Full detail in the webhooks section.

Verifying a webhook belongs on a server. The Flutter SDK exposes a WebhookVerifier, but a mobile app is not a webhook destination — it has no stable URL and cannot hold the signing secret.

What's next