The hosted flow step by step
Technical guide: create a verification journey, redirect the user to the page hosted by Todis, confirm the result server-side, and receive signed webhooks. The other guides: online shops and mobile apps and native integration.
Create, redirect, confirm
Create the journey (here, an age check):
curl -X POST https://verify.todis.eu/hosted/sessions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"check": "age_over_18",
"country_code": "FR",
"locale": "en",
"success_url": "https://your-shop.example/verification-return",
"reference": "order-1042"
}'
Response: the URL to redirect to, and the identifier to keep.
{
"session_id": "…",
"hosted_url": "https://verify.todis.eu/v/…",
"expires_at": "2026-08-19T12:34:56Z"
}
Redirect your user's browser to hosted_url: Todis
displays the QR code (wallet on another device), the open button
(wallet on the same device), and handles the wait in the requested
language. success_url means the verification
succeeded, never that the user is of age: a proof of age
that is false lands there too, like any verified
result. Read the result server-side (claims.age_over_18
with the age_over_18 shortcut) and apply your own rule
to it. When the user returns to your success_url,
your backend confirms (never on the sole basis of URL parameters,
which the user can forge):
curl https://verify.todis.eu/verify/sessions/SESSION_ID \
-H "Authorization: Bearer YOUR_TOKEN"
{ "status": "verified", "claims": { "age_over_18": true } }
The "check": "identity" shortcut requests family
name, given name, birth date and nationality instead (the basis of
a KYC onboarding), and every advanced field of the
session API remains
available. cancel_url (optional) receives the user on
failure. The page carries Todis branding; a white-label display is
under consideration for the Premium plan.
The webhook, your safety belt
If the user closes the page before the redirect, you still get
told: add webhook_url at creation and your backend
receives a JSON POST
{"event": "session.verified", "session_id": "…", "reference": "…"}
when the journey concludes (automatic retries as long as you do
not answer 2xx). Every delivery is signed HMAC-SHA256 in the
X-Todis-Signature header with the
webhook_secret returned at creation:
// Node.js: verify the t=<timestamp>,v1=<hex> signature
const crypto = require("node:crypto");
function validSignature(body, header, secret) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", secret)
.update(t + "." + body).digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
The webhook never contains the verified data: it only tells you the journey is over, and you read the result through the authenticated call above.
The QR in your own page: the web component
If you would rather keep the user on your page than redirect them, a web component embeds the hosted page and reports state changes to you:
<script src="https://todis.eu/js/todis-verify.js"></script>
<todis-verify hosted-url="HOSTED_URL_GENERATED_BY_YOUR_BACKEND"></todis-verify>
<script>
document.querySelector("todis-verify")
.addEventListener("todis:verified", () => {
// Unlock the next step of YOUR interface,
// then confirm server-side as always.
});
</script>
Emitted events: todis:status on every change, then
todis:verified, todis:failed or
todis:expired. The hosted_url is always
generated by your backend: your license token never leaves your
server.
The three rules that never change. The license
token stays server-side. The result is confirmed through an
authenticated GET /verify/sessions/{id}, never on the
basis of a URL parameter or a browser event. And there is nothing
to store: no ID copy, no photo, only the verified answer you asked
for, as explained in the
FAQ.