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
| SDK | Package | Registry | Requires | What it is |
|---|---|---|---|---|
| JavaScript / TypeScript | @veridia/sdk | npm | Node 18.17+ (or a browser) | API client + webhook verification |
| Python | veridia | PyPI | Python 3.11+ | API client (sync + async) + webhook verification |
| PHP | veridia/veridia-php | Composer | PHP 8.2+ | API client + webhook verification |
| Flutter | veridia_sdk | private git | Dart 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
| Prefix | Kind | Belongs in | init + submit | Read verdicts |
|---|---|---|---|---|
qv_pub_, qv_pubt_ | publishable | browser, mobile app | yes | no |
qv_sec_, qv_sect_ | secret | your server only | yes | yes |
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.
| SDK | Can capture images | Can init / submit | Can read a verdict |
|---|---|---|---|
| JavaScript | no | yes | yes, with secretKey |
| Python | no | yes | yes, with a qv_sec_* key |
| PHP | no | yes | yes, with a qv_sec_* key |
| Flutter | yes | yes | no, by design |
The flow every SDK wraps
init → PUT each image → submit → webhook (or poll)
POST /v1/verify/initreturns one upload slot per image (docFront,docBack,selfie), plus anexpiresAtin unix seconds.PUTthe bytes to each slot URL. Send the slot'sheadersverbatim — see below.POST /v1/verify/submitwith thekeysfrom the init response.keysis required.- 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.headersverbatim. Rebuilding the headers yourself — or sending onlyContent-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.
| Field | Question it answers | Values |
|---|---|---|
status | Did the pipeline run? | queued processing completed failed |
verdict | Did 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 noX-Veridia-Signatureand 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 flat —
verdict,verificationId,userRefat the top level. There is nodataenvelope. - The discriminator is
type, notevent. - Exactly three event types:
verification.approved,verification.review_required,verification.rejected. There is nocreatedand noexpiredevent 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. fieldsExtractedcarries identity PII: full name, document number, date of birth. That is why the endpoint must behttps, 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
- JavaScript SDK
- Python SDK
- PHP SDK
- Flutter SDK
- HTTP API reference — for any language without an SDK