Waterfall flow
Age verification with k-ID is a privacy-preserving process that allows users to prove their age without revealing personal information. This approach uses a Waterfall flow model.
Waterfall flow
AgeKit+ acts as a single-point orchestrator for age checks, automatically cascading through a waterfall of verification providers to confirm a user's age. In practice, one API call to k-ID presents the configured methods in sequence. For example, starting with email inference or a facial age estimation and then falling back to an ID document scan or other methods as needed, until the user's age is verified or all options are exhausted. This means developers integrate once with k-ID's API, and the platform handles trying multiple verification techniques behind the scenes, combining methods to maximize the chances of a successful verification.
The verification flow is initiated with an API call that returns a URL to be hosted in an iframe or mobile web view, where users complete the verification process. The available verification methods are determined by your product configuration in the Compliance Studio, ensuring compliance with jurisdiction requirements.
| API | Scenario |
|---|---|
/age-verification/perform-access-age-verification | To verify the age of a user before getting access to a feature, mature content, or the product itself. |
/age-verification/perform-trusted-adult-verification | To perform trusted adult (parent or guardian) verification. |
/age-verification/perform-age-appeal | For users who have failed age verification but want to appeal the decision. |
The Age Verification APIs are standardized in terms of the request & response format.
Request body
| Property | Description | Required? |
|---|---|---|
jurisdiction | The jurisdiction in which the age verification should happen | Yes |
criteria | The criteria for age verification | Yes |
subject.email | If the user verified their age with k-ID in any other context with an email address, then the original age is returned instead of asking the user to estimate or prove their age again. | No |
subject.claimedAge | If a user was asked for their age in an age gate, used to inform the age estimation process | No |
subject.id | An identifier used across multiple verification methods to report multiple failed attempts. This can be a temporary session ID, or hashed user ID. | No |
options.facialAgeEstimation.passIfOver | The estimated age threshold required to automatically pass facial age estimation. If the estimated age is at or greater than this value, the verification passes. | No |
options.facialAgeEstimation.failIfUnder | The estimated age threshold below which the verification fails. If the estimated age is below this value, the verification fails. Defaults to the verification criteria age when omitted. | No |
options.redirectUrl | The URL to redirect to after verification completes. Supports HTTP/HTTPS URLs or mobile deeplinks with custom protocol schemes. The redirect only occurs when the verification URL is opened directly in a browser or webview (not embedded in an iframe). When a redirect occurs, the URL includes verificationId and result (PASS or FAIL) as query string parameters. | No |
The passIfOver and failIfUnder parameters give you control over the variance allowed in facial age estimation results. When a facial age estimation scan is performed:
- If the estimated age is at or greater than
passIfOver, the verification PASSES and an age signal is determined. - If the estimated age is below
failIfUnder, the verification FAILS and an age signal is determined. - If the estimated age is greater than
failIfUnderand less thanpassIfOver, the result is considered inconclusive and the user can retry the facial age estimation.
This allows you to set a confidence range where results are clear enough to make a determination, while giving users the opportunity to retry when the estimation falls in an uncertain range. For example, if you need to verify users are 18+, you might set passIfOver to 25 and failIfUnder to 12. This means users estimated to be 25 or older pass immediately, users estimated to be below 12 fail immediately, and users estimated to be 12-24 can retry the scan or other verification methods.
Sample:
{
"jurisdiction": "US-CA",
"criteria": {
"ageCategory": "ADULT"
},
"options": {
"facialAgeEstimation": {
"passIfOver": 25,
"failIfUnder": 12
},
"redirectUrl": "https://example.com/verification-complete"
}
}
Redirect URL
The redirectUrl parameter allows you to specify where users should be redirected after completing verification. This is useful for:
- Browser-based flows: Redirecting to another web page after verification completes
- Custom success screens: Displaying your own custom success or failure page
- Mobile app deeplinks: Using custom protocol schemes (for example,
myapp://verification-complete) to return control to your mobile app
The redirect only occurs when the verification URL is opened directly in a browser or webview (not embedded in an iframe). When embedded in an iframe, verification results are delivered via DOM events instead.
When a redirect occurs, the redirect URL includes the following query string parameters:
verificationId: The unique verification IDresult: The verification result, eitherPASSorFAIL
Example redirect URL:
https://example.com/verification-complete?verificationId=7854909b-9124-4bed-9282-24b44c4a3c97&result=PASS
Response body
A successful request to the Age Verification API returns the following response.
| Property | Description |
|---|---|
id | A unique verification ID generated by Age Verification Service |
url | The age verification URL that must be embedded in an iframe and presented to the user, for them to verify themselves. |
Sample:
{
"id": "7854909b-9124-4bed-9282-24b44c4a3c97",
"url": "https://family.k-id.com/verify?token=eyJ..."
}
Embedding the verification interface
Use the returned URL to create an iframe in your website or app. Users complete their verification through this interface, with available methods automatically adapting to jurisdictional requirements.

<div id="verification-container">
<iframe
id="verification-widget"
src="VERIFICATION_URL"
width="100%"
height="600"
frameborder="0"
allow="camera;payment;publickey-credentials-get;publickey-credentials-create">
</iframe>
</div>
The allow attribute is required to enable the following features:
camera: Required for facial age estimationpayment: Required for credit card verificationpublickey-credentials-getandpublickey-credentials-create: Required for WebAuthn-based verification methods
Verification result
Once the user has successfully completed the age verification, or the user has retried the maximum number of times and hasn't succeeded, the Age Verification Result is delivered through both client-side and server-side channels. Implementations should use a combination of both: client-side events are best for controlling UI elements, while for data integrity, the actual results should come from either a webhook or a call to /age-verification/get-status.
For detailed information about analyzing verification results, including field presence rules, status types, and implementation guidance, see the Verification Event Contract.
Client-side (DOM events) - If the URL from the response body is included in an iframe, it's sent to the parent frame as a window message (MessageEvent) with a Verification.Result structure.
Server-side (webhooks) - An event is sent to the registered webhook in the form of a Verification.Result event.
Example of accessing the window message:
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.eventType === "Verification.Result") {
// Use DOM Events for immediate UI updates
updateUI(message);
}
};
window.addEventListener("message", handleMessage);
For data integrity, always verify results with events from webhooks or by calling /age-verification/get-status rather than relying solely on DOM Events. DOM Events are best suited for responsive UI updates.
The data element of the window event and the webhook event contains the following properties.
| Property | Description |
|---|---|
id | The verification ID for which this is the result. |
status | Indicates a PASS or FAIL status, based on whether the user met the age criteria or not. |
ageCategory | Indicates the age category the user belongs to in the jurisdiction specified in the request. Supported values are adult, digital-youth or digital-minor |
method | Indicates the method used for the verification. Supported values are id-document, age-estimation, age-attestation, credit-card, social-security-number |
failureReason | The reason the verification failed. Supported values are age-criteria-not-met, max-attempts-exceeded, or fraudulent-activity-detected. This is only set if status is FAIL |
age | Returns the lower bound and higher bound of the estimated or verified age as low and high. |
Sample:
{
"eventType": "Verification.Result",
"data": {
"id": "5a58e98a-e477-484b-b36a-3857ea9daaba",
"status": "PASS",
"ageCategory": "adult",
"method": "id-document",
"age": {
"low": 25,
"high": 25,
}
}
}
Handling a window event:
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.eventType === "Verification.Result" && ) {
if (message.data.status === "PASS") {
window.location.href = `https://www.example.com/success?verificationId=${message.data.id}`
}
if (message.data.status === "FAIL") {
window.location.href = `https://www.example.com/fail?verificationId=${message.data.id}`
}
}
};
window.addEventListener("message", handleMessage);
Verification error
If an unexpected error has occurred, a JavaScript event fires so that your implementation can gracefully handle the error.
For detailed information about the event structure, see Verification.Error.
Sample Message:
{
"eventType": "Verification.Error",
"method": "credit-card",
"status": "ERROR"
}
Checking verification status
In addition to sending events both in JavaScript and through the k-ID webhook, it's also possible to query for status for a verification. This is useful to be able to handle cases where the registered webhook was unreachable for a period of time, and the status event was never sent. To get status for a verification, use the /age-verification/get-status API. The data structure returned follows the same contract as the Verification.Result webhook event, but there are some differences in structure and field presence. For detailed information about analyzing verification results, including field presence rules, status types, differences between webhook and API endpoint responses, and implementation guidance, see the Verification Event Contract. For more information about webhook events, see Webhooks.
Limiting verification attempts
Each verification request allows users three attempts per available verification method. A verification fails if:
- All available verification methods have been exhausted and an age can't be determined
- An age is determined but falls below the required threshold for your criteria
When a verification fails, you can allow users to initiate a new verification attempt. However, to prevent misuse and abuse of the verification system, you should implement rate limiting on additional verification attempts. For example, you might limit users to three verification attempts within a 24-hour period.
Use the subject.id field in the verification request to track attempts across multiple verification requests. This field should contain a consistent identifier for the user (such as a temporary session ID or hashed user ID), allowing you to:
- Track the number of verification attempts per user
- Implement time-based rate limiting (for example, 3 attempts per 24 hours)
- Prevent users from bypassing limits by creating new sessions
Implement rate limiting on your server before initiating verification requests. This prevents unnecessary API calls and helps protect your system from abuse.
Verification methods
For detailed information about all available verification methods, see Verification methods.