ProntoID API Documentation
Welcome to the ProntoID API documentation. Our API provides a simple and secure way to integrate age verification into your platform. We offer two primary integration methods: a direct Age Verification API for your existing users and a simplified OIDC Login flow.
API Key Authentication
The Age Verification API uses API keys to authenticate requests. You can manage your API keys in the ProntoID Dashboard. Authentication is performed by providing your keys in the request headers for the `verification-start` endpoint.
Include the following headers in every Age Verification API request:
x-api-key: Your secret API Key.x-platform-id: Your unique Platform ID.
Age Verification for Authenticated Users
This flow is designed for users who are already signed into your platform. The process is asynchronous: you initiate the verification, the user completes the steps on ProntoID, and we notify your server of the result via a secure webhook.
Step 1: Initiate the Verification
To begin, make a server-to-server POST request to our `verification-start` endpoint. This creates a new age verification session and returns a URL to redirect your user to.
/prod/verification-start
Base URL: https://7b6fsp0mpi.execute-api.us-east-1.amazonaws.com
JSON Body Parameters
| client_reference | Required. A unique and stable identifier for the user from your system (e.g., your internal user ID). This value will be returned in the webhook payload, allowing you to easily look up and update the correct user record. |
Step 2: Receive the Webhook Notification
After the user completes the verification process, ProntoID will send an asynchronous event to your configured webhook endpoint. You can set up your endpoint URL and subscribe to events like `age_verification.succeeded` in your ProntoID developer dashboard.
Your Endpoint Must Be Secure
Your server must verify the webhook's signature to ensure it originated from ProntoID and was not tampered with. This is a critical security step.
Step 3: Verify the Signature
Every webhook request from ProntoID includes an `X-Pronto-Signature` header. This signature is a HMAC-SHA256 hash of the raw request body, created using your unique webhook signing secret (`whsec_...`).
<?php
// Get the secret from an environment variable for security
$webhook_secret = $_ENV['PRONTO_WEBHOOK_SECRET'];
// Get the signature from the request headers
$received_signature = $_SERVER['HTTP_X_PRONTO_SIGNATURE'] ?? '';
// Get the raw POST data from the request body
$payload_body = file_get_contents('php://input');
// Compute the expected signature
$expected_signature = hash_hmac('sha256', $payload_body, $webhook_secret);
// Use hash_equals for a timing-attack-safe comparison
if (!hash_equals($expected_signature, $received_signature)) {
// Signature is invalid. Reject the request.
http_response_code(403);
exit('Invalid signature.');
}
// Signature is valid. Proceed to process the event.
$event = json_decode($payload_body, true);
// ... your logic here ...
import os
import hmac
import hashlib
# In a web framework like Flask, you would get these from the request context
# from flask import request
# Get the secret from an environment variable for security
webhook_secret = os.environ.get('PRONTO_WEBHOOK_SECRET')
# Get the signature from the request headers
received_signature = request.headers.get('X-Pronto-Signature')
# Get the raw POST data from the request body
payload_body = request.data
# Compute the expected signature
expected_signature = hmac.new(
webhook_secret.encode('utf-8'),
payload_body,
hashlib.sha256
).hexdigest()
# Use hmac.compare_digest for a timing-attack-safe comparison
if not hmac.compare_digest(expected_signature, received_signature):
# Signature is invalid. Reject the request.
return 'Invalid signature.', 403
# Signature is valid. Proceed to process the event.
event = request.get_json()
# ... your logic here ...
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
// In a web framework, get these from the request context
// String receivedSignature = request.headers("X-Pronto-Signature");
// String payloadBody = request.body();
// Get the secret from an environment variable for security
String webhookSecret = System.getenv("PRONTO_WEBHOOK_SECRET");
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(webhookSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] hash = sha256_HMAC.doFinal(payloadBody.getBytes(StandardCharsets.UTF_8));
String expectedSignature = HexFormat.of().formatHex(hash);
// Use MessageDigest.isEqual for a timing-attack-safe comparison
if (!MessageDigest.isEqual(expectedSignature.getBytes(), receivedSignature.getBytes())) {
// Signature is invalid. Reject the request.
// response.status(403);
// return "Invalid signature.";
}
// Signature is valid. Proceed to process the event.
// ... your logic here ...
} catch (Exception e) {
// Handle exceptions
// response.status(500);
// return "Server error.";
}
Step 4: Process the Event Data
Once the signature is verified, you can safely process the event payload. The `type` field tells you which event occurred, and the full details are in the `data.object`.
Example Payload for `age_verification.succeeded`
{
"id": "evt_1a2b3c4d5e6f7g8h...",
"object": "event",
"api_version": "1.0",
"created": 1728735780,
"type": "age_verification.succeeded",
"data": {
"object": {
"platform_id": "your_platform_id",
"verification_token": "vtok_abcdef123456",
"is_adult": true,
"age_status": "verified_adult",
"client_reference": "your_internal_user_id_12345"
}
}
}
After receiving this event, you should use the `client_reference` to look up the user in your database and update their status to reflect that they are now age-verified.
Login with ProntoID (OIDC Flow)
For a robust integration, use our OpenID Connect (OIDC) flow. Our helper endpoints simplify the process, handling the complexities of token generation and validation. Authentication for this flow uses a Client ID and Client Secret which you can get from your ProntoID dashboard.
Primary Use Cases
1. Secure Age Verification
The primary use case is to confirm that a user has successfully completed an age verification process. After a successful login, the decoded ID Token will contain the https://prontoid.com/age_verified claim, which will be set to true.
2. User Login & Account Linking
You can also use this flow as a standard "Login with ProntoID" feature. The ID Token includes a stable and unique user identifier in the sub claim. You can use this ID to sign users into their existing accounts on your platform or to provision a new account for them.
Step 1: Get the Authorization URL
To begin, make a GET request to our authorization URL helper endpoint from your server. This endpoint returns a complete, secure URL containing a unique `state` parameter. You must parse this `state` value from the returned URL and store it in a secure, server-side session or an `HttpOnly` cookie before redirecting the user.
/production/pronto-login-get-authorization-uri
Base URL: https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com
Query String Parameters
| client_id | Required. Your unique Client ID. |
| scope |
Optional. A space-separated string of scopes to request specific claims. If omitted, your platform's default scope will be used.
|
Example: Requesting the Auth URL with Scopes
<?php
$API_ENDPOINT = 'https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/pronto-login-get-authorization-uri';
$CLIENT_ID = getenv('PRONTOID_CLIENT_ID');
// For both user ID and age verification
$params = http_build_query([
'client_id' => $CLIENT_ID,
'scope' => 'openid age_verification'
]);
$url = $API_ENDPOINT . '?' . $params;
// ... cURL GET request to $url to fetch the full authorization_uri ...
import os
import requests
API_ENDPOINT = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/pronto-login-get-authorization-uri"
params = {
"client_id": os.environ.get("PRONTOID_CLIENT_ID"),
"scope": "openid age_verification" # Request both claims
}
response = requests.get(API_ENDPOINT, params=params)
response.raise_for_status()
auth_uri = response.json().get("authorization_uri")
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
String apiEndpoint = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/pronto-login-get-authorization-uri";
String clientId = System.getenv("PRONTOID_CLIENT_ID");
// For both user ID and age verification
String scope = "openid age_verification";
String query = String.format("client_id=%s&scope=%s",
URLEncoder.encode(clientId, StandardCharsets.UTF_8),
URLEncoder.encode(scope, StandardCharsets.UTF_8)
);
String fullUrl = apiEndpoint + "?" + query;
// ... Perform HttpClient GET request to fullUrl ...
Step 2: Get the ID Token
After the user signs in, they are redirected to your `redirect_uri` with an `authorization_code`. Verify the `state` parameter, then exchange the code for an ID Token by making a secure server-to-server POST request to our token endpoint.
/production/get-oidc-token
Base URL: https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com
JSON Body Parameters
| client_id | Required. Your unique Client ID. |
| client_secret | Required. Your Client Secret. |
| authorization_code | Required. The code received in the callback. |
Example: Exchanging the Code for a Token
<?php
$TOKEN_ENDPOINT = 'https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/get-oidc-token';
$CLIENT_ID = getenv('PRONTOID_CLIENT_ID');
$CLIENT_SECRET = getenv('PRONTOID_CLIENT_SECRET');
$authorization_code = $_GET['code'];
// state verification logic...
$payload = json_encode([
'client_id' => $CLIENT_ID,
'client_secret' => $CLIENT_SECRET,
'authorization_code' => $authorization_code
]);
// ... cURL POST request to $TOKEN_ENDPOINT with $payload ...
$responseBody = curl_exec($ch);
$body = json_decode($responseBody, true);
$id_token = $body['id_token'];
import os
import requests
# ... state verification logic ...
TOKEN_ENDPOINT = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/get-oidc-token"
payload = {
"client_id": os.environ.get("PRONTOID_CLIENT_ID"),
"client_secret": os.environ.get("PRONTOID_CLIENT_SECRET"),
"authorization_code": request.args.get('code'),
}
response = requests.post(TOKEN_ENDPOINT, json=payload)
response.raise_for_status()
id_token = response.json().get("id_token")
// ... state verification logic ...
String tokenEndpoint = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/get-oidc-token";
String clientId = System.getenv("PRONTOID_CLIENT_ID");
String clientSecret = System.getenv("PRONTOID_CLIENT_SECRET");
String authorizationCode = request.getParameter("code");
String payload = String.format(
"{\"client_id\": \"%s\", \"client_secret\": \"%s\", \"authorization_code\": \"%s\"}",
clientId, clientSecret, authorizationCode
);
// ... HttpClient POST request to tokenEndpoint with payload ...
// Parse response.body() to get the "id_token"
Step 3: Verify the ID Token
For maximum security and simplicity, you should not validate the JWT manually. Instead, send the `id_token` you received to our dedicated verification endpoint. This endpoint will perform all necessary cryptographic and claim validations on your behalf and return the token's claims if it is valid.
/production/verify-oidc-token
Base URL: https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com
JSON Body Parameters
| id_token | Required. The raw ID Token string you received in Step 2. |
| client_id | Required. Your unique Client ID. |
| client_secret | Required. Your Client Secret, used to authenticate this request. |
Example: Verifying the Token
<?php
$VERIFY_ENDPOINT = 'https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/verify-oidc-token';
$id_token = '... the token from Step 2 ...';
$payload = json_encode([
'id_token' => $id_token,
'client_id' => getenv('PRONTOID_CLIENT_ID'),
'client_secret' => getenv('PRONTOID_CLIENT_SECRET')
]);
// ... cURL POST request to $VERIFY_ENDPOINT with $payload ...
$responseBody = curl_exec($ch);
$token_data = json_decode($responseBody, true);
if ($token_data['active'] === true) {
// Token is valid, access claims like $token_data['sub']
$is_age_verified = $token_data['https://prontoid.com/age_verified'] ?? false;
}
import os
import requests
VERIFY_ENDPOINT = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/verify-oidc-token"
id_token = "... the token from Step 2 ..."
payload = {
"id_token": id_token,
"client_id": os.environ.get("PRONTOID_CLIENT_ID"),
"client_secret": os.environ.get("PRONTOID_CLIENT_SECRET"),
}
response = requests.post(VERIFY_ENDPOINT, json=payload)
response.raise_for_status()
token_data = response.json()
if token_data.get("active"):
# Token is valid, access claims like token_data.get("sub")
is_age_verified = token_data.get("https://prontoid.com/age_verified")
String verifyEndpoint = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/verify-oidc-token";
String idToken = "... the token from Step 2 ...";
String payload = String.format(
"{\"id_token\": \"%s\", \"client_id\": \"%s\", \"client_secret\": \"%s\"}",
idToken, System.getenv("PRONTOID_CLIENT_ID"), System.getenv("PRONTOID_CLIENT_SECRET")
);
// ... HttpClient POST request to verifyEndpoint with payload ...
// Parse response JSON into a Map or custom object
// Map tokenData = ...
if (Boolean.TRUE.equals(tokenData.get("active"))) {
// Token is valid, access claims
boolean isAgeVerified = (boolean) tokenData.get("https://prontoid.com/age_verified");
}
Example Success Responses
The claims returned in a successful response depend on the scope you requested in Step 1.
Response for `scope=openid age_verification`
{
"active": true,
"iss": "https://prontoid.com",
"sub": "user-uuid-12345",
"aud": "your_client_id_here",
"exp": 1728681125,
"iat": 1728677525,
"https://prontoid.com/age_verified": true
}
Response for `scope=openid`
{
"active": true,
"iss": "https://prontoid.com",
"sub": "user-uuid-12345",
"aud": "your_client_id_here",
"exp": 1728681125,
"iat": 1728677525
}