Skip to main content

Menu

API Reference v1.0.4

ProntoID API Documentation

Welcome to the ProntoID API documentation. Our API provides a simple and secure way to integrate age verification and other platform utilities into your application. We offer several integration methods, including a direct Age Verification API, a Secure File Upload Flow, and a simplified OIDC Login flow.


Authentication

ProntoID uses two primary methods for authenticating server-to-server API requests. Please use the correct method for the endpoint you are calling.

Method 1

Header Authentication

Used for Age Verification API

👉 Use this method for:

  • /prod/verification-start
  • x-api-key: Your secret API Key
  • x-platform-id: Your unique Platform ID
Method 2

Body Authentication

Used for Platform Utilities

👉 Use this method for:

  • /production/get-platform-operation-authorization-session-token
  • platform_client_id: Your Platform ID
  • platform_api_key: Your Client Secret

⚡ Rate Limits

All endpoints are rate limited to 100 requests per minute per API key. If you exceed this limit, you'll receive a 429 Too Many Requests response.


Common Issues & Troubleshooting

Here are solutions to common problems you might encounter when integrating with ProntoID.

❌ Invalid Signature Error

If you're getting signature verification errors, ensure you're using the raw request body, not parsed JSON.

// ❌ Wrong - Don't use parsed data
$payload = json_encode($_POST);

// ✅ Correct - Use raw input stream
$payload = file_get_contents('php://input');

🔌 Testing Webhooks Locally

To test webhooks on your local development machine, use a tunneling service like ngrok:

ngrok http 3000

Then use the generated HTTPS URL (e.g., https://abc123.ngrok.io/webhook) in your ProntoID dashboard.

⏱️ Token Expiration

Session tokens expire after 10 minutes. If users see an "expired token" error:

  • Generate a new token from your backend
  • Don't store tokens - generate them on-demand
  • Redirect users immediately after token generation

🔑 Authentication Errors

If you receive a 401 Unauthorized error:

  • Verify you're using the correct authentication method for the endpoint
  • Check that your credentials are not expired
  • Ensure there are no extra spaces in your API keys
  • Confirm you're using the right environment (production vs. sandbox)

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 using Header Authentication. 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.

Success Response (200 OK)

{
  "verification_url": "https://secure.prontoid.com/verify?token=vtok_abc123...",
  "verification_token": "vtok_abc123...",
  "expires_at": "2024-11-07T16:30:00Z"
}

Error Response (401 Unauthorized)

{
  "error": "unauthorized",
  "error_description": "Invalid API key or Platform ID"
}

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_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.

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

Secure File Upload Flow (Model Release)

This flow allows your users to securely upload a file (like a Model Release Form) from your platform, have it processed and stored by ProntoID, and then have the resulting form_id automatically associated with your content.

This process uses a secure, single-use JWT token for authentication and a signed webhook for confirmation. It is a three-step asynchronous process.

Step 1: Get a One-Time Session Token

When a user wants to upload a file, your backend server must call our session token endpoint. This is a secure server-to-server call using Body Authentication (see Authentication).

This endpoint will return a short-lived (e.g., 10 minutes) JSON Web Token (JWT). This token is safe to send to your frontend JavaScript.

POST /production/get-platform-operation-authorization-session-token

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

JSON Body Parameters

platform_client_id Required. Your unique Platform ID (e.g., bentbox_4b07...).
platform_api_key Required. Your Platform's Client Secret (e.g., c_d3oXn...).
platform_content_url Required. The full URL of the content (photo, video, etc.) this file will be associated with. This will be shown to the user on the upload page.
platform_callback_url Required. The URL on your platform where the user should be redirected after a successful upload.
action_type Required. Must be set to RELEASE_FORM_UPLOAD.

Example: Getting the Session Token

<?php
// This code runs on your server (e.g., BentBox)
$PRONTOID_CLIENT_ID = getenv('PRONTOID_CLIENT_ID');
$PRONTOID_CLIENT_SECRET = getenv('PRONTOID_CLIENT_SECRET');
$PRONTOID_API_ENDPOINT = 'https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/get-platform-operation-authorization-session-token';

// Data from your authenticated user
$content_url_to_verify = 'https://your-platform.com/content/abc-123';
$final_callback_url = 'https://your-platform.com/pronto_return';

$payload = json_encode([
    'platform_client_id' => $PRONTOID_CLIENT_ID,
    'platform_api_key' => $PRONTOID_CLIENT_SECRET,
    'platform_content_url' => $content_url_to_verify,
    'platform_callback_url' => $final_callback_url,
    'expiration_seconds' => 600,
    'action_type' => 'RELEASE_FORM_UPLOAD'
]);

// ... cURL POST request to $PRONTOID_API_ENDPOINT with $payload ...
$response_json = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($http_code == 200) {
    $response_data = json_decode($response_json, true);
    $session_token = $response_data['session_token'];
    
    // Securely return this token to your frontend JavaScript
    echo json_encode(['status' => 'success', 'session_token' => $session_token]);
} else {
    // Handle error
    echo json_encode(['error' => 'Could not initiate session.']);
}
                                        
import os
import requests

PRONTOID_CLIENT_ID = os.environ.get("PRONTOID_CLIENT_ID")
PRONTOID_CLIENT_SECRET = os.environ.get("PRONTOID_CLIENT_SECRET")
PRONTOID_API_ENDPOINT = "https://uxvq4ew6td.execute-api.us-east-1.amazonaws.com/production/get-platform-operation-authorization-session-token"

# Data from your authenticated user
content_url_to_verify = "https://your-platform.com/content/abc-123"
final_callback_url = "https://your-platform.com/pronto_return"

payload = {
    "platform_client_id": PRONTOID_CLIENT_ID,
    "platform_api_key": PRONTOID_CLIENT_SECRET,
    "platform_content_url": content_url_to_verify,
    "platform_callback_url": final_callback_url,
    "expiration_seconds": 600,
    "action_type": "RELEASE_FORM_UPLOAD"
}

response = requests.post(PRONTOID_API_ENDPOINT, json=payload)
response.raise_for_status() # Raises exception for non-2xx status

session_token = response.json().get("session_token")

# Securely return this token to your frontend JavaScript
# (e.g., in a Flask response)
# return jsonify({"status": "success", "session_token": session_token})
                                        

Step 2: Redirect the User to ProntoID

Once your frontend JavaScript receives the one-time session_token from your backend, it must immediately redirect the user to the ProntoID secure upload page. The token is passed as a URL query parameter.

Example: JavaScript Frontend

// This code runs in the user's browser on your platform (e.g., BentBox)
async function onUploadButtonClick() {
    
    // 1. Get the one-time token from your backend API
    const response = await fetch('/your-backend/get-pronto-token-api', {
        method: 'POST',
        body: JSON.stringify({
            content_id: 'abc-123', // Your internal content ID
            // ... other params ...
        })
    });
    
    const data = await response.json();
    
    if (data.status === 'success') {
        // 2. Build the secure URL
        const sessionToken = data.session_token;
        const prontoUrl = `https://secure.prontoid.com/secure-upload-model-release-form.php?token=${encodeURIComponent(sessionToken)}`;

        // 3. Redirect the user (e.g., in a new tab)
        window.open(prontoUrl, '_blank');
    } else {
        alert('Error: ' + data.error);
    }
}
                                

When this page loads, ProntoID will consume the token from the URL, validate it, and exchange it for a secure, HttpOnly first-party cookie (prontoid_secure_session). The user then completes the form, and all subsequent API calls from that page (S3 uploads, form save) are authenticated with this new, secure cookie.

Step 3: Receive the Confirmation Webhook

After the user successfully saves the form, two things happen:

  1. The user's browser is redirected back to the platform_callback_url you provided in Step 1.
  2. ProntoID's backend asynchronously sends a signed webhook to your platform's registered webhook_url to confirm the save.

You must listen for this webhook, verify its signature (using the same logic as in Step 3 of the Age Verification flow), and then use the payload to link the form_id to your content.

Example Payload for release_form.completed

{
  "id": "evt_2a3b4c5d6e7f8g9h...",
  "object": "event",
  "api_version": "1.0",
  "created": 1728745780,
  "type": "release_form.completed",
  "data": {
    "object": {
      "platform_id": "bentbox_4b07...",
      "form_id": "ac416eb28c5f2e3ac5cb4af619c2f115",
      "content_url": "https://bentbox.co/box/box_12345abc",
      "status": "completed"
    }
  }
}
                                

After verifying the webhook signature, use the content_url and form_id to update your database and finalize the association.