Menu

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.

POST /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.

GET /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.
  • openid: Requests the user's unique ID for login purposes.
  • age_verification: Requests the user's age verification status.
  • openid age_verification: Requests both the unique ID and age verification status.

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.

POST /production/get-oidc-token

Base URL: https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com

JSON Body Parameters

client_idRequired. Your unique Client ID.
client_secretRequired. Your Client Secret.
authorization_codeRequired. 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.

POST /production/verify-oidc-token

Base URL: https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com

JSON Body Parameters

id_tokenRequired. The raw ID Token string you received in Step 2.
client_idRequired. Your unique Client ID.
client_secretRequired. 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
}