Embedding
The widget runs in an iframe on your page. Load the script and mount it:
<div id="cognau"></div>
<script src="https://verify.cognau.com/embed.js"></script>
<script>
Cognau.mount('#cognau', {
link: 'cgn_vl_…',
onVerdict: r => console.log(r.passed, r.sessionId)
});
</script>
Three ways to supply a session
// 1. A verification link. No backend needed.
Cognau.mount('#cognau', { link: 'cgn_vl_…' });
// 2. A session your backend already created.
Cognau.mount('#cognau', { session: sessionFromYourApi });
// 3. Your own endpoint, which we POST to when the widget is ready.
Cognau.mount('#cognau', { sessionEndpoint: '/api/start-verification' });
Options 2 and 3 never put the session token in a URL. It is handed to the
iframe over postMessage once the widget signals readiness, so the credential
never lands in browser history, a Referer header, or a server log.
Writing the iframe yourself
<iframe src="https://verify.cognau.com/?link=cgn_vl_…"
allow="camera"
style="width:100%;height:640px;border:0"></iframe>
Two things become your responsibility, and both fail silently if missed.
allow="camera" is mandatory. Permissions Policy blocks camera access
inside a cross-origin iframe unless the embedding page delegates it. Without
the attribute the widget reaches the permission screen and stops, which looks
like our bug rather than a missing attribute. Your page must also be HTTPS.
Validate event.origin on every message. Any page can postMessage to
your window, including a forged "passed".
window.addEventListener('message', e => {
if (e.origin !== 'https://verify.cognau.com') return; // REQUIRED
if (e.data?.source !== 'cognau') return;
// …
});
embed.js handles both. That is the only reason it exists.
Events
type |
Payload | Meaning |
|---|---|---|
ready |
mounted, waiting for the user | |
started |
camera granted, session running | |
verdict |
{ passed, sessionId } |
finished (advisory) |
consent_declined |
{ sessionId } |
declined at the consent screen |
error |
{ code } |
could not continue |
resize |
{ height } |
content height, for sizing the frame |
The verdict message is not authoritative. It travels through the end user's browser, so a determined user can post a fake "passed" to your own page. Use it to drive UI; decide from the webhook. The payload deliberately carries no scores, risk factors, or person identifiers.
Restricting who can embed you
By default any site can iframe your widget. To limit it:
curl -X PUT https://machine.cognau.com/api/v1/cockpit/embed-origins \
-H "Authorization: Bearer $COGNAU_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"origins": ["https://app.acme.com", "https://*.acme.com"]}'
Scheme and host only. https://*.acme.com matches any subdomain but not
the bare apex, so add https://acme.com separately if you need it. An empty
list means unrestricted, which is the default.
This governs embedding, not visiting: a link opened directly from an email is not embedded in anything and keeps working whatever the list says.
Be clear about its strength. The origin is reported by the visitor's browser,
so it stops a leaked link being farmed from an unrelated site and makes
misconfiguration loud, but it is not proof against a scripted browser. For a
browser-enforced guarantee, pair it with a CSP frame-ancestors header at your
own edge.
Reading this as an agent? The raw Markdown is at /docs/embedding.md.