Skip to content

Testing

This guide covers how to use ProofAge's test mode to develop and validate your integration without incurring costs or requiring real identity documents.

Test Mode Overview

Test mode lets you run the full verification flow in a sandbox environment. Here is how it differs from live mode:

Test ModeLive Mode
API key prefixsk_test_sk_live_
BillingNo chargesBilled per verification
Payment method requiredNoYes
Processing pipelineSkipped — goes directly to reviewFull automated pipeline
Decision deliveryManual via admin panelAutomated via webhook
Biometric checksNot performedFull liveness, face match, age estimation
Media validationPerformed (format, size, face detection)Performed
Webhook deliveryFired when admin approves/declinesFired on pipeline decision

TIP

Test mode still validates uploaded media (file format, dimensions, face detection), so you can verify your upload flow works correctly. The difference is that the automated decision pipeline is bypassed — a human approves or declines through the admin panel.


Using Test Mode

1. Get Your Test Keys

In the ProofAge dashboard, navigate to API Keys. You will see two key pairs:

  • Test keys — Prefixed with sk_test_. Use these during development.
  • Live keys — Prefixed with sk_live_. Use these in production.

Both the API key and the secret key have test variants. Use the test secret key for HMAC signatures during development.

2. Create a Test Verification

Set mode to test when creating a verification:

bash
curl -X POST https://api.proofage.com/v1/verifications \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sk_test_abc123def456" \
  -d '{
    "flow": "age",
    "mode": "test",
    "external_id": "test-user-001",
    "external_metadata": {
      "env": "development"
    }
  }'

3. Approve or Decline from the Admin Panel

After submitting a test verification, it moves to review status. Open the ProofAge admin panel, find the verification, and choose Approve or Decline. This triggers the decision webhook to your configured endpoint.


Complete curl Walkthrough

This section walks through a full test verification from creation to decision using only curl commands.

Prerequisites

Set these environment variables for convenience:

bash
export PROOFAGE_API_KEY="sk_test_abc123def456"
export PROOFAGE_SECRET="your_test_secret_key"

Step 1: Create the Verification

bash
curl -s -X POST https://api.proofage.com/v1/verifications \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $PROOFAGE_API_KEY" \
  -d '{
    "flow": "age",
    "mode": "test",
    "external_id": "curl-test-001"
  }' | jq .

Save the verification ID from the response:

bash
export VERIFICATION_ID="ver_abc123def456"
bash
# Fetch the current consent text
curl -s -X GET https://api.proofage.com/v1/consent \
  -H "X-API-Key: $PROOFAGE_API_KEY" | jq .

# Record consent acceptance
curl -s -X POST "https://api.proofage.com/v1/verifications/$VERIFICATION_ID/consent" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $PROOFAGE_API_KEY" \
  -d '{
    "consent_version": "2026-01-15",
    "accepted": true
  }' | jq .

Step 3: Upload a Selfie

bash
curl -s -X POST "https://api.proofage.com/v1/verifications/$VERIFICATION_ID/media" \
  -H "X-API-Key: $PROOFAGE_API_KEY" \
  -F "type=selfie" \
  -F "[email protected]" | jq .

TIP

Any photo containing a clearly visible face works for test mode. The media validation checks (face detection, blur, brightness) still run, so use a reasonably clear photo.

Step 4: Submit for Processing

bash
curl -s -X POST "https://api.proofage.com/v1/verifications/$VERIFICATION_ID/submit" \
  -H "X-API-Key: $PROOFAGE_API_KEY" | jq .

The verification moves to review status in test mode.

Step 5: Approve via Admin Panel

Open the ProofAge admin panel, locate the verification ver_abc123def456, and click Approve. This triggers the webhook to your configured URL.

Step 6: Check the Final Status

bash
curl -s -X GET "https://api.proofage.com/v1/verifications/$VERIFICATION_ID" \
  -H "X-API-Key: $PROOFAGE_API_KEY" | jq .

You should see "status": "approved" in the response.


Mock Webhook Testing

You can test your webhook handler locally without going through the full verification flow. Construct a signed webhook payload and POST it to your local endpoint.

Sample Payload and Signature

Use these values to verify your signature validation logic:

Shared secret:

test_secret_key_for_webhook_verification

Timestamp (Unix):

1742385900

Raw JSON payload:

json
{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}

Signature payload (timestamp + period + raw body):

1742385900.{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}

Expected HMAC-SHA256 hex digest:

a3c1b8f2e7d94a6b0c5f1e8d2a7b3c9f4e6d8a1b0c2f5e7d9a4b6c8f1e3d5a7

You can compute the real signature yourself to verify your implementation:

bash
echo -n '1742385900.{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}' \
  | openssl dgst -sha256 -hmac 'test_secret_key_for_webhook_verification'

Send a Mock Webhook

Use the actual signature computed above and send it to your local endpoint:

bash
# Compute the signature
SIGNATURE=$(echo -n '1742385900.{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}' \
  | openssl dgst -sha256 -hmac 'test_secret_key_for_webhook_verification' | awk '{print $NF}')

# Send the mock webhook to your local endpoint
curl -X POST http://localhost:3000/webhooks/proofage \
  -H "Content-Type: application/json" \
  -H "X-Auth-Client: sk_test_abc123def456" \
  -H "X-HMAC-Signature: $SIGNATURE" \
  -H "X-Timestamp: 1742385900" \
  -d '{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}'

WARNING

The mock webhook uses a hardcoded timestamp. If your webhook handler enforces replay protection (rejecting timestamps older than 5 minutes), you need to either disable that check for local testing or use the current Unix timestamp when computing the signature.

Dynamic Mock Script

Here is a script that generates a correctly signed mock webhook with the current timestamp:

bash
#!/bin/bash
SECRET="test_secret_key_for_webhook_verification"
ENDPOINT="http://localhost:3000/webhooks/proofage"
TIMESTAMP=$(date +%s)
PAYLOAD='{"verification_id":"550e8400-e29b-41d4-a716-446655440000","status":"approved","external_id":"test-user-001","external_metadata":{"env":"development"},"reason":null,"timestamp":"2026-03-19T12:05:00+00:00"}'

SIGNATURE=$(echo -n "${TIMESTAMP}.${PAYLOAD}" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')

curl -v -X POST "$ENDPOINT" \
  -H "Content-Type: application/json" \
  -H "X-Auth-Client: sk_test_abc123def456" \
  -H "X-HMAC-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -d "$PAYLOAD"

Save this as mock-webhook.sh, make it executable with chmod +x mock-webhook.sh, and run it whenever you need to test your webhook handler.


Local Webhook Listener with ngrok

To receive real webhooks from ProofAge on your local machine, use ngrok to create a public HTTPS tunnel.

Setup

  1. Install ngrok:

    bash
    # macOS
    brew install ngrok
    
    # Or download from https://ngrok.com/download
  2. Start your local server (e.g., on port 3000).

  3. Start the tunnel:

    bash
    ngrok http 3000
  4. Copy the HTTPS URL from the ngrok output:

    Forwarding  https://a1b2c3d4.ngrok-free.app -> http://localhost:3000
  5. Configure the webhook URL in the ProofAge dashboard:

    https://a1b2c3d4.ngrok-free.app/webhooks/proofage
  6. Submit a test verification and approve it from the admin panel. The webhook arrives at your local server via the ngrok tunnel.

Inspecting Traffic

ngrok provides a web inspector at http://localhost:4040 where you can:

  • View all incoming requests and responses.
  • Inspect headers and body content.
  • Replay requests to re-test your handler without going through the verification flow again.

Testing Edge Cases

Media Validation Errors

Upload intentionally bad images to test your error handling:

ScenarioHow to trigger
No face detectedUpload a photo of scenery or an object.
Face too blurryUpload a heavily blurred selfie.
Face too darkUpload a very underexposed photo.
Face occludedUpload a selfie with a hand covering part of the face.
Document not foundUpload a selfie instead of a document image.
Wrong file typeUpload a .txt or .pdf file as the media.

Submission Errors

ScenarioHow to trigger
MISSING_REQUIRED_MEDIACall /submit without uploading any media.
INVALID_STATUSCall /submit on an already-submitted verification.
CONSENT_REQUIREDUpload media without recording consent first.

Declined Webhook

From the admin panel, decline a test verification and add a reason. This lets you test your handler's logic for declined webhooks and verify that you correctly parse the reason field.

Resubmission Flow

  1. Submit a test verification.
  2. From the admin panel, set the status to resubmission_requested.
  3. Upload new media to the same verification.
  4. Resubmit and verify the new webhook fires.

Duplicate Detection

  1. Create and approve two test verifications using the same selfie.
  2. The second webhook should include duplicate_detected: true.
  3. Verify your handler flags the duplicate.

Rate Limiting

Send more than 60 requests in a single minute to trigger a 429 RATE_LIMIT response. Verify that your client reads the Retry-After header and backs off appropriately.


Checklist

Before switching from test to live mode, verify:

  • [ ] Full verification flow works end-to-end (create, consent, upload, submit).
  • [ ] Webhook handler receives and processes decision payloads correctly.
  • [ ] Signature verification passes with real payloads (not just mock data).
  • [ ] All error formats are handled (standard, media validation, Laravel validation).
  • [ ] Media validation errors are surfaced to the user with actionable messages.
  • [ ] Resubmission flow works (user re-uploads after resubmission_requested).
  • [ ] Duplicate detection is handled.
  • [ ] Rate limiting is handled with Retry-After backoff.
  • [ ] Switch API keys from sk_test_ to sk_live_ and secret keys accordingly.
  • [ ] Payment method is configured in the dashboard.

Next Steps

  • Webhooks — Full webhook reference with signature verification examples.
  • Error Handling — Complete error code catalog and best practices.
  • Verification Flow — Understand the full verification lifecycle.

ProofAge Developer Documentation