OAuth 2.0 API Reference
Manual OAuth implementation for any backend language (Python, PHP, Ruby, Go, Java, etc.)
When to use Manual OAuth
Use this approach if you're building with a backend framework and want full control over the OAuth flow.
OAuth Flow Overview
- 1User clicks "Sign in with Onboarder" in your app
- 2Your app redirects to
/oauth/authorizewith client_id and flow_id - 3User completes verification on Onboarder's hosted UI
- 4Onboarder redirects back with authorization code
- 5Your backend exchanges code for access_token
- 6Fetch verified user data from
/oauth/userinfo
Base URL
# Productionhttps://api.onboarder.com/api/v1
# Sandbox (for testing)https://api.onboarder.com/api/v1Step 1: Authorization Request
Redirect the user to Onboarder's authorization endpoint to begin verification.
GET/oauth/authorizeQuery Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| client_id | string | ✅ Yes | Your platform's client ID from dashboard |
| flow_id | UUID | ✅ Yes | Verification flow ID (defines requirements) |
| redirect_uri | URL | ✅ Yes | Callback URL (must match dashboard config) |
| response_type | string | ✅ Yes | Must be "code" (authorization code flow) |
| state | string | ⚠️ Recommended | Random string for CSRF protection |
| scope | string | ❌ No | Space-separated scopes (e.g., "profile email phone") |
| code_challenge | string | ❌ No | PKCE challenge (recommended for public clients) |
| code_challenge_method | string | ❌ No | "S256" or "plain" (use with code_challenge) |
Example Request
https://api.onboarder.com/api/v1/oauth/authorize?client_id=plt_sandbox_abc123&flow_id=550e8400-e29b-41d4-a716-446655440000&redirect_uri=https://yourapp.com/callback&response_type=code&state=random_csrf_token_xyz&scope=profile email phoneImportant: flow_id is required
The flow_id parameter specifies which verification requirements the user must complete. Create verification flows in your dashboard.
Step 2: Handle Callback
After the user completes verification, Onboarder redirects back to your redirect_uri with these query parameters:
| Parameter | Description |
|---|---|
| code | Authorization code (valid for 10 minutes) |
| state | Same state value you sent (validate this!) |
Example callback URL:
https://yourapp.com/callback?code=auth_code_abc123xyz&state=random_csrf_token_xyzSecurity: Always validate the state parameter
Compare the returned state with what you sent to prevent CSRF attacks. If they don't match, reject the request.
Step 3: Exchange Code for Access Token
Exchange the authorization code for an access token. This MUST be done on your backend to keep client_secret secure.
POST/oauth/tokenRequest Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| grant_type | string | ✅ Yes | Must be "authorization_code" |
| code | string | ✅ Yes | Authorization code from callback |
| client_id | string | ✅ Yes | Your platform client ID |
| client_secret | string | ✅ Yes | Your platform client secret (NEVER expose!) |
| redirect_uri | URL | ✅ Yes | Same redirect_uri from authorization step |
| code_verifier | string | ❌ No | PKCE verifier (if you sent code_challenge) |
Response
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "refresh_token_here", "scope": "profile email phone"}Code Example
import requests
# Exchange code for tokenresponse = requests.post( 'https://api.onboarder.com/api/v1/oauth/token', json={ 'grant_type': 'authorization_code', 'code': auth_code, 'client_id': 'plt_sandbox_abc123', 'client_secret': 'secret_xyz', # Keep this secret! 'redirect_uri': 'https://yourapp.com/callback' })
tokens = response.json()access_token = tokens['access_token']Step 4: Get Verified User Data
Use the access token to fetch verified user information.
GET/oauth/userinfoHeaders
Authorization: Bearer YOUR_ACCESS_TOKENResponse
{ "sub": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "email_verified": true, "phone": "+1234567890", "phone_verified": true, "name": "John Doe", "given_name": "John", "family_name": "Doe", "picture": "https://...", "verifications": { "passport": { "verified": true, "verified_at": "2025-01-15T10:00:00Z", "document_number": "X12345678", "country": "US", "expiry_date": "2030-01-15" }, "liveness": { "verified": true, "verified_at": "2025-01-15T10:01:00Z" } }}Code Example
import requests
# Get user inforesponse = requests.get( 'https://api.onboarder.com/api/v1/oauth/userinfo', headers={'Authorization': f'Bearer {access_token}'})
user_info = response.json()print(f"User: {user_info['name']}")print(f"Email verified: {user_info['email_verified']}")print(f"Phone verified: {user_info['phone_verified']}")Security Best Practices
1. Always use the state parameter
Generate a random, unguessable string for each authorization request and validate it in the callback to prevent CSRF attacks.
2. Never expose client_secret
Keep client_secret on your backend only. Never include it in frontend code, mobile apps, or version control.
3. Use PKCE for mobile/public clients
For mobile apps or SPAs, implement PKCE (Proof Key for Code Exchange) by sending code_challenge and code_verifier to prevent authorization code interception.
4. Always use HTTPS in production
OAuth requires HTTPS to prevent token interception. Onboarder will reject non-HTTPS redirect URIs in production.
5. Store tokens securely
Use httpOnly cookies for web apps. For mobile apps, use platform-specific secure storage (Keychain on iOS, Keystore on Android).
Error Codes
| Error | Description |
|---|---|
| invalid_request | Missing or invalid required parameters |
| unauthorized_client | Invalid client_id or client_secret |
| access_denied | User denied authorization request |
| invalid_grant | Invalid or expired authorization code |
| invalid_scope | Requested scope is invalid or not allowed |
| server_error | Internal server error |