System Architecture
Two-step verification combining video selfie analysis with optional card verification.
flowchart TB
subgraph Mobile["Mobile App"]
UI["Age Verification UI"]
Camera["Camera Capture"]
CardForm["Card Entry Form"]
end
subgraph Backend["MiFamilias Backend"]
API["API Gateway"]
Service["Age Verification Service"]
Sessions["Session Store"]
DB["DynamoDB"]
end
subgraph AWS["AWS Services"]
Rekognition["AWS Rekognition\n- Face Detection\n- Age Estimation\n- Liveness Check"]
end
subgraph Stripe["Stripe"]
SetupIntent["SetupIntent API\n- Card Verification\n- No Charge"]
end
UI --> Camera
Camera --> API
API --> Service
Service --> Sessions
Service --> Rekognition
Service --> SetupIntent
Service --> DB
UI --> CardForm
CardForm -.->|Direct to Stripe| SetupIntent
Video Selfie Verification Flow
Primary verification method using AWS Rekognition for age estimation.
sequenceDiagram
participant User
participant App
participant API
participant Service as Age Verification Service
participant Rekognition as AWS Rekognition
participant DB as DynamoDB
User->>App: Start verification
App->>API: POST /verification/age/session
API->>Service: createSession(userId)
Service-->>API: sessionId, expiresAt
API-->>App: Session created
App->>App: Show camera with liveness prompts
Note over App: "Look at camera"
Note over App: "Blink your eyes"
Note over App: "Turn head left/right"
App->>App: Capture 12 frames over 4 seconds
App->>API: POST /verification/age/video
Note right of App: sessionId + base64 frames
API->>Service: analyzeVideoFrames(sessionId, frames)
loop Each Frame
Service->>Rekognition: DetectFaces(frame)
Rekognition-->>Service: FaceDetails, AgeRange
end
Service->>Service: Aggregate age estimation
Service->>Service: Check liveness (blink, movement)
alt Age clearly 18+ AND liveness passed
Service-->>API: verified: true
API-->>App: Verification successful
App->>API: POST /verification/age/confirm
API->>Service: completeVideoVerification
Service->>DB: Update user profile
DB-->>Service: ageVerified: true
API-->>App: Confirmed
App-->>User: Age Verified!
else Age borderline (16-20) OR liveness uncertain
Service-->>API: requiresCardVerification: true
API-->>App: Card verification needed
App-->>User: Additional verification required
else Verification failed
Service-->>API: verified: false
API-->>App: Please try again
end
Card Verification Flow
Backup verification using Stripe SetupIntent (no charge).
sequenceDiagram
participant User
participant App
participant API
participant Service as Age Verification Service
participant Stripe
participant DB as DynamoDB
Note over User,App: After video analysis requires card verification
User->>App: Proceed with card verification
App->>API: POST /verification/age/card
Note right of App: sessionId
API->>Service: createCardVerification(sessionId)
Service->>Stripe: Create SetupIntent
Note right of Service: payment_method_types: ['card']
Note right of Service: usage: 'off_session'
Stripe-->>Service: clientSecret, setupIntentId
Service-->>API: clientSecret
API-->>App: Show card form
App->>App: Display Stripe Elements
User->>App: Enter card details
Note over User,App: Card data goes directly to Stripe
App->>Stripe: Confirm SetupIntent
Note right of App: $0 authorization only
Stripe-->>App: SetupIntent succeeded
App->>API: POST /verification/age/confirm
Note right of App: sessionId, setupIntentId
API->>Service: confirmCardVerification
Service->>Stripe: Retrieve SetupIntent
Stripe-->>Service: status: succeeded
Service->>DB: Update user profile
Note right of Service: ageVerified: true
Note right of Service: method: video_and_card
DB-->>Service: Updated
Service-->>API: verified: true
API-->>App: Verification complete
App-->>User: Age Verified!
Liveness Detection
Multi-frame analysis to ensure real person, not a photo.
flowchart TB
subgraph Input["Video Frames (12 frames)"]
F1["Frame 1"]
F2["Frame 2"]
F3["..."]
F12["Frame 12"]
end
subgraph Checks["Liveness Checks"]
C1["Single Face Check\n- No multiple faces\n- Consistent face ID"]
C2["Eye Blink Detection\n- EyesOpen varies\n- At least one blink"]
C3["Mouth Movement\n- MouthOpen varies\n- Natural movement"]
C4["Head Pose Variation\n- Yaw > 5 degrees\n- Pitch > 5 degrees"]
end
subgraph Result["Liveness Result"]
Pass["PASS\n2+ checks passed\nNo multiple faces"]
Fail["FAIL\nLess than 2 checks\nor multiple faces"]
end
F1 & F2 & F3 & F12 --> C1 & C2 & C3 & C4
C1 -->|Single face| Pass
C1 -->|Multiple faces| Fail
C2 & C3 & C4 -->|2+ passed| Pass
C2 & C3 & C4 -->|Less than 2| Fail
Decision Flow
How verification outcome is determined.
flowchart TB
Start([Video Submitted]) --> FaceDetect{Face Detected?}
FaceDetect -->|No| Retry[Ask to retry]
FaceDetect -->|Yes| LivenessCheck{Liveness Passed?}
LivenessCheck -->|No| CardRequired[Require Card Verification]
LivenessCheck -->|Yes| AgeCheck{Age Range?}
AgeCheck -->|Low >= 18| Verified[Verified via Video Only]
AgeCheck -->|Low 16-17| CardRequired
AgeCheck -->|Low < 16| Blocked[Cannot Verify - Too Young]
CardRequired --> CardVerify{Card Valid?}
CardVerify -->|Yes| VerifiedCard[Verified via Video + Card]
CardVerify -->|No| Retry
Verified --> UpdateDB[(Update User Profile)]
VerifiedCard --> UpdateDB
style Verified fill:#2ecc71
style VerifiedCard fill:#2ecc71
style Blocked fill:#e74c3c
style Retry fill:#f39c12
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /verification/age/status | Get user's current verification status |
| POST | /verification/age/session | Create new verification session (30 min expiry) |
| POST | /verification/age/video | Submit video frames for analysis |
| POST | /verification/age/card | Create Stripe SetupIntent for card verification |
| POST | /verification/age/confirm | Confirm and persist verification |
Data Retention
| Data Type | Stored? | Duration | Notes |
|---|---|---|---|
| Verification Status | Yes | Permanent | ageVerified, method, verifiedAt on user profile |
| Video Frames | No | - | Analyzed and discarded immediately |
| Biometric Data | No | - | Rekognition processes but doesn't retain |
| Card Details | No | - | Handled entirely by Stripe |
| Session Data | Temporary | 30 minutes | In-memory, expires automatically |
Debug Flags
Environment variables for development testing:
| Variable | Effect |
|---|---|
AGE_VERIFY_BYPASS_REKOGNITION=true |
Skip AWS Rekognition, return mock age 25-35, liveness passes |
AGE_VERIFY_BYPASS_STRIPE=true |
Skip Stripe verification, auto-succeed card check |
AGE_VERIFY_FORCE_CARD=true |
Always require card verification step (for testing) |
Third-Party Services
| Service | Purpose | Data Handling |
|---|---|---|
| AWS Rekognition | Face detection, age estimation, attribute analysis | Processes frames, doesn't retain biometric data |
| Stripe | Payment method verification ($0 authorization) | PCI-DSS Level 1 certified, we never see card numbers |
Regulatory Compliance
| Region | Requirement | Our Implementation |
|---|---|---|
| Australia | 16+ for social media (Social Media Ban Act) | Video + card verification for borderline ages |
| UK | Age-appropriate design (Online Safety Act) | Age estimation with fallback verification |
| EU | GDPR parental consent for under-16 | Guardian verification for minors |
| Global | COPPA compliance (under-13 protection) | Block under-13 accounts |