Frica ID Integration Handbook
Authoritative guides for single sign-on (OAuth 2.1 / OIDC), scoped machine-to-machine API keys, real-time webhooks, pre-signed cloud storage, and subscription entitlements.
1. Platform Architecture & Standards
Frica ID is an enterprise-grade digital identity, single sign-on (SSO), and developer platform designed specifically for the Pan-African and global ecosystem. It adheres to strict modern internet standards:
Enforces mandatory PKCE (RFC 7636) and Refresh Token Rotation (RTR). Deprecates legacy implicit and password grants.
RS256 asymmetric token signatures, Argon2id (RFC 9106) password hashing, and AES-256-GCM envelope encryption for secrets at rest.
Native support for Safaricom M-Pesa Daraja STK Push callbacks alongside international Stripe checkouts with real-time entitlement gating.
Pre-signed direct S3/R2 binary transfers keep media data off identity servers while strictly coordinating per-user quota allocation.
2. 5-Minute Quickstart
Register Developer Application
Navigate to the Developer Console and create an application. Provide your callback redirect URI (e.g. https://myapp.com/api/auth/callback) to obtain your client_id.
Generate an API Key (Optional)
For backend daemons or server-to-server microservices, create a scoped API key in the Developer Portal. Keys follow the pattern frik_live_... and are hashed with SHA-256 before storage.
Verify Health via cURL
Verify connectivity with the production API gateway:
curl -X GET "https://api.frica.id/healthz"
Expected response: {"status":"UP","timestamp":"2026-10-04T...","version":"1.0.0"}
Login with Frica ID for WordPress
Let visitors sign in to your WordPress site with their Frica account. The free, open-source plugin adds a login button, a shortcode and explicit account connection from WordPress Profile.
Requires WordPress 6.6+, PHP 8.0+ with OpenSSL, and HTTPS. The WordPress.org directory submission is awaiting review; install the packaged ZIP from GitHub Releases.
- Install the packaged plugin. Download the
frica-id-loginZIP from the release assets. In WordPress, open Plugins → Add New → Upload Plugin, install it and activate it. - Copy your site's callback. Open Settings → Frica ID and copy the exact callback URL shown there. The public callback uses WordPress's admin-post handler; visitors do not need administrator access.
- Register your WordPress app. Open Developer applications, create an app, then open its configuration. Add the copied URL under Allowed Redirect URIs and save. Use the standard public OAuth client with
openid profile email. - Enable Frica sign-in. Copy the app's Client ID into WordPress Settings → Frica ID and enable sign-in. The plugin uses PKCE and does not need a client secret, API key or KYC purchase.
- Choose registration behavior. To admit new members, enable the plugin's new-subscriber option and WordPress Settings → General → Anyone can register. New accounts receive the Subscriber role. Keep registration disabled when only existing members should connect.
- Test as a visitor. Sign out of WordPress and use Login with Frica ID. To connect an existing WordPress account, first sign in with its WordPress credentials, then use Profile → Frica ID connection. An email match does not automatically merge accounts.
https://example.com/wp-admin/admin-post.php?action=frica_id_callback
[frica_id_login]
What members will see
Members approve the requested Frica profile access and return to the same WordPress site. New WordPress usernames use the Public Frica ID, with a suffix when a local username is already taken. Existing generated usernames can upgrade on a later sign-in; custom usernames stay unchanged.
WordPress passwords and email recovery remain available. WordPress logout ends the local WordPress session. It does not sign the member out of Frica globally.
Troubleshooting
Callback mismatch: copy the exact URL from plugin Settings, including the action query parameter. Existing email: sign in to that WordPress account first and connect it from Profile. Verification failure: match the support reference in Settings → Frica ID → Last sign-in diagnostic and check its code, stage and reason. Share those safe fields with support; keep tokens, passwords and authorization codes private.
Exclude wp-login.php and wp-admin/admin-post.php from page caching. Test membership, security and MFA plugins on your site before opening sign-in to visitors. Multisite supports per-site activation and existing site members.
3. OAuth 2.1 & OpenID Connect (SSO)
Integrate single sign-on across mobile apps, SPAs, and web applications using standard OpenID Connect.
OpenID Connect Discovery
Frica ID exposes RFC 8414 discovery configuration at:
GET https://api.frica.id/.well-known/openid-configuration
| Endpoint Parameter | URI Value |
|---|---|
| issuer | https://api.frica.id |
| authorization_endpoint | https://frica.id/oauth/authorize |
| token_endpoint | https://api.frica.id/oauth/token |
| userinfo_endpoint | https://api.frica.id/oauth/userinfo |
| jwks_uri | https://api.frica.id/.well-known/jwks.json |
| introspection_endpoint | https://api.frica.id/oauth/introspect |
| revocation_endpoint | https://api.frica.id/oauth/revoke |
Authorization Code with PKCE Flow
Step 1: Construct the authorization URL and redirect the user:
https://frica.id/oauth/authorize? response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://myapp.com/callback &scope=openid%20profile%20email%20offline_access &state=random_state_string_xyz &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256
Step 2: Exchange the authorization code for RS256 Access & ID tokens:
curl -X POST "https://api.frica.id/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "code=fcode_abc123xyz" \ -d "redirect_uri=https://myapp.com/callback" \ -d "code_verifier=ORIGINAL_RANDOM_CODE_VERIFIER"
Refresh Token Rotation (RTR)
When renewing tokens, Frica ID invalidates the previous refresh token and issues a new one. This ensures instant revocation if token leakage is detected:
curl -X POST "https://api.frica.id/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "refresh_token=frt_current_refresh_token"
4. Scoped API Keys
For server-side scripts, cron jobs, and background workers, authenticate using HTTP header X-API-Key:
GET /v1/users/me HTTP/1.1 Host: api.frica.id X-API-Key: frik_live_a89bc213fe09...
API Scopes Reference
| Scope | Category | Granted Permission |
|---|---|---|
| users:read | Identity | Read user profiles, verification status, and public metadata |
| users:write | Identity | Update user profiles, preferences, and custom attributes |
| storage:read | Storage | List drive objects, view metadata, and generate pre-signed download URLs |
| storage:write | Storage | Initialize file uploads, commit parts, and move files to trash |
| billing:read | Billing | Query active subscriptions, usage quotas, and payment histories |
| kyc:write | KYC | Create hosted verification sessions |
| kyc:read | KYC | Read session status and application verification details |
| kyc:check | KYC | Request an attribute check; this does not establish identity approval |
| webhooks:manage | Developer | Register, test, and rotate webhook listener endpoints |
| * | Wildcard | Full administrative authority (Super Admin keys only) |
5. KYC & Live Verification
Create a session from your backend, redirect the applicant to Frica's hosted document and camera flow, and verify the outcome on your server. Passing liveness or returning to your application is not KYC approval.
For your own Frica ID profile, use Identity Verification in your account. The backend integration below is for developer applications; hosted-session fees are charged to the organization wallet.
1. Prepare your application
- Create an active application in the Developer Portal.
- Generate an API key with
kyc:writeandkyc:read; addkyc:checkonly for attribute checks. Store it asFRICA_KYC_API_KEYon your backend, never in browser or mobile bundles. - Fund the organization wallet. The current hosted-session creation fee is 0.45 FRICA, charged atomically with session creation. An insufficient or held balance prevents creation.
- Register an HTTPS webhook endpoint for the same application. Supply and securely retain your signing secret; endpoint registration returns only a masked preview.
2. Create and persist a hosted session
// Server only. Derive externalUserId from your authenticated account.
export async function createKycSession(externalUserId: string) {
const key = process.env.FRICA_KYC_API_KEY;
if (!key) throw new Error('Configure the server KYC API key');
const response = await fetch(
'https://api.frica.id/v1/developer/kyc/sessions',
{
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': key },
body: JSON.stringify({
requestedTier: 'TIER_2',
returnUrl: 'https://your-app.example/kyc/return',
cancelUrl: 'https://your-app.example/kyc/cancel',
customReference: 'your-stored-onboarding-reference',
customer: { externalUserId }
})
}
);
const body = await response.json();
if (!response.ok || body.success !== true || !body.data?.sessionUrl) {
throw new Error('KYC session creation was not confirmed; reconcile before retrying');
}
// Persist sessionId, applicationId and externalUserId in your backend.
// Keep sessionUrl/clientSecret confidential; send sessionUrl only to this applicant.
return body.data;
}REST responses use { success, requestId, timestamp, data }; read the session from data. Send sessionUrl only to its applicant. Hosted sessions expire after 24 hours. Each creation POST makes a new session and charge: customReference is correlation, not an idempotency key. Reconcile an uncertain response before creating another session.
3. Read status and confirm the decision
curl --fail-with-body \ "https://api.frica.id/v1/developer/kyc/sessions/YOUR_SESSION_ID" \ -H "X-API-Key: $FRICA_KYC_API_KEY" # Read verificationId from your signed submission event, then fetch details: curl --fail-with-body \ "https://api.frica.id/v1/developer/kyc/verifications/YOUR_VERIFICATION_ID" \ -H "X-API-Key: $FRICA_KYC_API_KEY"
| Session status | Application handling |
|---|---|
| ACTIVE | Await submission; do not grant verified access. |
| SUBMITTED | Keep the applicant pending while compliance review is required. |
| APPROVED / REJECTED | Fetch application-bound verification details on your backend before applying the decision. |
| EXPIRED / CANCELLED | Do not continue capture or treat the session as verified. |
The return URL may contain session_id, status, verification_id and custom_reference. These browser parameters are untrusted. Match applicationId and externalUserId to your stored session, verify signed events and fetch verification details with kyc:read. A failed lookup, missing record, unavailable claim or UNDER_REVIEW status must leave the applicant unverified.
4. Receive signed KYC events
Subscribe to kyc.session.created, kyc.submitted, kyc.approved and kyc.rejected. Event type is in X-Frica-Event. The signed body contains event-specific fields such as sessionId,verificationId, externalUserId and status; it is not an { event, data } envelope. Use the raw-body signature verifier below.
Persist accepted events before acknowledging with 2xx, deduplicate by X-Frica-Delivery-Id and business identifiers, and handle out-of-order notifications. Monitor failed deliveries in the Developer Portal and reconcile through authenticated reads; do not assume automatic redelivery. Never grant approval from the event header alone. The created event contains a secret-bearing hosted URL, so avoid logging raw bodies.
5. Custom capture and current limits
Prefer the hosted page. Custom capture starts with POST /v1/kyc/sessions/:sessionId/livenessand { clientSecret }. Send frames to POST /v1/kyc/sessions/:sessionId/liveness/:id/frameswith { clientSecret, sequence, image }. Follow the returned instruction, quality, progress and sequence. Keep one frame in flight; on a lost acknowledgement, retry the identical frame and sequence.
Liveness expires after 180 seconds and is bound to the hosted session. After complete: true, preserve the exact final selfie bytes. Submit to POST /v1/kyc/sessions/:sessionId/submit with the hosted clientSecret, legal name, date of birth, nationality, document type and number, country of issuance, frontDocumentDataUrl, optional backDocumentDataUrl, selfieDataUrl andlivenessData: { sessionId: livenessSessionId }. Use JPEG, PNG or WebP data URLs. Dates use YYYY-MM-DD; the current engine requires a minimum age of 18. The server proof is consumed once; client-supplied scores and gesture booleans are not proof.
Successful biometrics currently route to compliance review. Government document authenticity, OCR/MRZ and live sanctions/PEP screening are not supplied by these face models. The number-only check below currently returns verified: false; unperformed watchlist and MRZ checks return null. Do not interpret model scores as calibrated identity probabilities.
curl --fail-with-body -X POST \
"https://api.frica.id/v1/developer/kyc/check" \
-H "X-API-Key: $FRICA_KYC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Amina",
"lastName": "Example",
"dateOfBirth": "1995-06-15",
"documentType": "NATIONAL_ID",
"documentNumber": "YOUR_DOCUMENT_NUMBER",
"country": "KE"
}'Production setup and troubleshooting
Operators must deploy PostgreSQL, Redis and the private biometric worker, apply migrations, fund the organization wallet and enable the server wallet release gate. Configure KYC_BIOMETRIC_URL and a shared KYC_BIOMETRIC_TOKENof at least 32 characters on the server. Set KYC_BIOMETRIC_VALIDATED=true only after measured capture and attack-resistance acceptance testing. These controls belong on the server, not in public frontend environment variables.
For 400, check required fields, dates and expired or consumed proofs. For 401/403, check the session secret, API key and scopes. For 429, respect the retry delay. A 503 or unavailable camera must not become verification success. Use HTTPS camera permission and restart expired liveness rather than reusing its proof.
6. Webhooks & Real-Time Event Verification
Frica ID delivers secure HTTP POST notifications whenever critical state changes occur. Every payload is cryptographically signed using your secret key.
• X-Frica-Signature: Header formatted as t=UNIX_SECONDS,v1=HEX_HMAC_SHA256, calculated over ${timestamp}.${rawBody}.
• X-Frica-Timestamp: Unix epoch timestamp in seconds. Reject clock drift beyond 300 seconds and deduplicate deliveries to limit replay.
Node.js Raw-Body Signature Verifier
import { createHmac, timingSafeEqual } from 'node:crypto';
// Mount express.raw({ type: 'application/json', limit: '256kb' })
// on this webhook route BEFORE express.json(). Pass req.body here.
export function verifyFricaWebhook(
rawBody: Buffer,
signatureHeader: string,
timestampHeader: string,
webhookSecret: string
): boolean {
if (!Buffer.isBuffer(rawBody) || !webhookSecret) return false;
const parts = /^t=(\d+),v1=([a-f0-9]{64})$/i.exec(signatureHeader);
if (!parts || parts[1] !== timestampHeader) return false;
const timestamp = Number(timestampHeader);
if (!Number.isSafeInteger(timestamp) ||
Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) {
return false;
}
const expected = createHmac('sha256', webhookSecret)
.update(timestampHeader + '.')
.update(rawBody)
.digest();
const received = Buffer.from(parts[2], 'hex');
return received.length === expected.length &&
timingSafeEqual(received, expected);
}7. Unified Cloud Storage & Drive API
Frica ID uses pre-signed direct S3 uploads. Files are transferred directly from client to Cloudflare R2 / AWS S3 without bottlenecking the identity servers.
Upload Lifecycle
Step 1: Request pre-signed upload URL and reserve user quota:
curl -X POST "https://api.frica.id/v1/storage/upload/init" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filename": "annual-report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 10485760,
"service": "frica_drive"
}'Step 2: Stream binary file directly to S3 / Cloudflare R2:
curl -X PUT "https://r2.cloudflarestorage.com/frica-drive-production/usr_1/...?X-Amz-Signature=..." \ -H "Content-Type: application/pdf" \ --data-binary "@annual-report.pdf"
8. Entitlements & Multi-Provider Billing
Query current subscription tiers, feature flags, and storage caps for any authenticated user.
curl -X GET "https://api.frica.id/v1/entitlements/me" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Example JSON response:
{
"tier": "PRO",
"storageQuotaBytes": 107374182400,
"maxConcurrentSessions": 10,
"apiRateLimitRpm": 1200,
"features": {
"custom_domains": true,
"audit_export": true,
"priority_support": true,
"mpesa_direct_billing": true
}
}9. Multi-Language SDK Snippets
Choose your stack to view drop-in client code for making authenticated API calls and verifying webhooks:
// npm install @frica/id-sdk
import React from 'react';
import { createFricaClient, FricaAuthProvider, FricaSignInButton, useFricaAuth } from '@frica/id-sdk';
// 1. Initialize Client
const client = createFricaClient({
clientId: 'fcli_your_client_id',
redirectUri: 'https://myapp.com/auth/callback',
defaultScope: 'openid profile email phone'
});
// 2. Wrap App with Provider
export function App({ children }: { children: React.ReactNode }) {
return <FricaAuthProvider client={client}>{children}</FricaAuthProvider>;
}
// 3. Drop in Branded Button Component
export function LoginCard() {
const { user, isAuthenticated, logout } = useFricaAuth();
if (isAuthenticated && user) {
return (
<div>
<p>Logged in as {user.name} ({user.email})</p>
<button onClick={logout}>Sign Out</button>
</div>
);
}
return <FricaSignInButton theme="dark" size="md" />;
}10. Rate Limiting & Standards
API requests are monitored using distributed Redis sliding-window counters. Quota information is returned in standard HTTP response headers:
Total allowable requests per window
Requests remaining in current window
Epoch timestamp when limit resets
11. RFC 7807 Error Code Reference
All errors returned by api.frica.id strictly adhere to the RFC 7807 Problem Details specification:
{
"type": "https://id.frica.com/errors/status-401",
"title": "UNAUTHORIZED",
"status": 401,
"detail": "Invalid credentials or expired Bearer token",
"instance": "/users/me",
"requestId": "req_1791136234043",
"timestamp": "2026-10-04T20:00:00.000Z"
}| Status | Title | Resolution & Handling |
|---|---|---|
| 400 | BAD_REQUEST | Payload validation failed. Check required parameters and formats. |
| 401 | UNAUTHORIZED | Missing or expired access token. Execute token refresh or verify API key. |
| 403 | FORBIDDEN | Insufficient scope. Request additional scope permissions for client or key. |
| 404 | NOT_FOUND | Target user, object, or developer resource does not exist. |
| 429 | TOO_MANY_REQUESTS | Rate limit exceeded. Pause requests until Retry-After elapses. |
| 500 | INTERNAL_SERVER_ERROR | Server exception. Include the requestId when contacting Frica Support. |
Ready to start building?
Create your developer account, generate credentials, and start building with Frica ID.

