<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammad Yusuf Abubakar</title>
    <description>The latest articles on DEV Community by Muhammad Yusuf Abubakar (@indiesolovibe).</description>
    <link>https://dev.to/indiesolovibe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4069617%2F8357866b-dcb5-447d-b728-52709e244514.jpg</url>
      <title>DEV Community: Muhammad Yusuf Abubakar</title>
      <link>https://dev.to/indiesolovibe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/indiesolovibe"/>
    <language>en</language>
    <item>
      <title>Add Face Liveness Detection to Any App in 10 Lines of Code (Free Tier Available)</title>
      <dc:creator>Muhammad Yusuf Abubakar</dc:creator>
      <pubDate>Sun, 09 Aug 2026 08:41:46 +0000</pubDate>
      <link>https://dev.to/indiesolovibe/add-face-liveness-detection-to-any-app-in-10-lines-of-code-free-tier-available-1h92</link>
      <guid>https://dev.to/indiesolovibe/add-face-liveness-detection-to-any-app-in-10-lines-of-code-free-tier-available-1h92</guid>
      <description>&lt;p&gt;Most biometric verification tools are either expensive, require a hardware SDK, or lock you into a proprietary platform. FaceID API is a free-tier REST API that adds face liveness detection to any web or mobile app. Here's exactly how it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How the session model works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your API key never appears in frontend code or in a URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your backend
   ↓ POST /widget-session (X-API-Key: your_secret_key)
FaceID Worker
   ↓ Returns { session_id, widget_url }
Your frontend
   ↓ Opens widget_url in an iframe (no key in URL)
User completes liveness
   ↓ postMessage({ type: 'FACEID_RESULT', result: {...} })
Your frontend
   ↓ Receives the result — no raw biometric data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js backend&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1: get a session (server-side only — never in the browser)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://face-worker.faceidentity.workers.dev/widget-session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FACEID_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;person_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// your user's unique ID&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// shown on the success screen&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 'register' | 'verify' | 'authenticate'&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;widget_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// send widget_url to your frontend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 2: frontend — open the widget&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widget_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// session token in the URL, not the API key&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;camera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cssText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;position:fixed;inset:0;width:100%;height:100%;border:none;z-index:9999&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: receive the result&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FACEID_RESULT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;person_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// action: 'registered' | 'verified' | 'authenticated'&lt;/span&gt;
    &lt;span class="c1"&gt;// match: true | false (for verify/authenticate)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python backend&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_verification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;authenticate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://face-worker.faceidentity.workers.dev/widget-session&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FACEID_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;person_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;widget_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Three modes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;register&lt;/code&gt; — first-time enrollment. Returns &lt;code&gt;action: 'registered'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify&lt;/code&gt; — 1:1 match, requires &lt;code&gt;person_id&lt;/code&gt;. Returns &lt;code&gt;match: true/false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;authenticate&lt;/code&gt; — 1:N search across your project, no &lt;code&gt;person_id&lt;/code&gt; needed. Returns &lt;code&gt;match: true&lt;/code&gt; with &lt;code&gt;person_id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; if found.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What happens during liveness&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The widget runs a 4-direction head challenge (up, down, left, right) plus a mouth-open check. MediaPipe FaceMesh tracks 468 facial landmarks in real time in the browser; face-api.js generates the 128-float descriptor only after every challenge passes. That descriptor — a mathematical representation, not a photo — is the only thing that ever reaches the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get started&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faceidentity.site" rel="noopener noreferrer"&gt;faceidentity.site&lt;/a&gt; — 200 enrollments/month free, no credit card.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How I Built an Enterprise Biometric API From a $0 Budget and an Android Phone</title>
      <dc:creator>Muhammad Yusuf Abubakar</dc:creator>
      <pubDate>Sun, 09 Aug 2026 08:30:45 +0000</pubDate>
      <link>https://dev.to/indiesolovibe/how-i-built-an-enterprise-biometric-api-from-a-0-budget-and-an-android-phone-4ppo</link>
      <guid>https://dev.to/indiesolovibe/how-i-built-an-enterprise-biometric-api-from-a-0-budget-and-an-android-phone-4ppo</guid>
      <description>&lt;p&gt;I don't own a laptop. I don't have a funded startup. I'm a self-taught developer from Nigeria, and six months ago I decided to build the kind of face-verification technology used in banking and identity-verification apps — using nothing but my Android phone.&lt;/p&gt;

&lt;p&gt;This is the story of FaceID API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FaceID is a production-ready biometric face verification API. Any developer can add face liveness detection to a web or mobile app in under 10 minutes. It handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 4-direction head challenge plus a mouth-open check for liveness&lt;/li&gt;
&lt;li&gt;A 128-float mathematical face descriptor — no photo is ever stored&lt;/li&gt;
&lt;li&gt;Three modes: Register (enroll), Verify (1:1 match), Authenticate (1:N search)&lt;/li&gt;
&lt;li&gt;Full project isolation, so each developer's user data stays separate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing runs on Cloudflare Workers with Cloudflare D1 as the database. No server to manage, no Docker, no monthly VPS bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User camera (browser)
      ↓
MediaPipe FaceMesh (runs locally — nothing sent yet)
      ↓
face-api.js generates a 128-float descriptor
      ↓
Cloudflare Worker (edge, 300+ global locations)
      ↓
Cloudflare D1 (SQLite at the edge)
      ↓
Result: { match: true/false, person_id, name }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The photo never leaves the device — that's not a marketing line, it's how the math works. A 128-float descriptor is a point in 128-dimensional space. You can't reverse it into a face image any more than you can reverse a hash back into the original text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The security model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before publishing, I read every "vibe-coded apps have vulnerabilities" thread I could find. Here's what's actually implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-time widget tokens with a 3-minute TTL. Your API key lives on your server — you call &lt;code&gt;/widget-session&lt;/code&gt;, get back a single-use URL, and embed that in an iframe. The key never touches a browser.&lt;/li&gt;
&lt;li&gt;Admin access is controlled by a hardcoded email in a Cloudflare environment secret, not a database column — so it can't be escalated through a SQL injection.&lt;/li&gt;
&lt;li&gt;HMAC-SHA256 signature verification on every billing webhook; replayed requests are rejected.&lt;/li&gt;
&lt;li&gt;Suspending a project invalidates all active sessions immediately.&lt;/li&gt;
&lt;li&gt;Rate limiting per IP, per endpoint (10 registrations/min, 30 verifications/min).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It isn't a certified, third-party-audited system, and I want to be upfront about that. But every protection in it was built because I understood the specific attack it closes off, not copied from a template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built it with&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Termux (terminal emulator on Android)&lt;/li&gt;
&lt;li&gt;proot-distro (Ubuntu environment inside Termux)&lt;/li&gt;
&lt;li&gt;Wrangler CLI (Cloudflare's deployment tool)&lt;/li&gt;
&lt;li&gt;Python 3 for file operations&lt;/li&gt;
&lt;li&gt;One Android phone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the complete hardware list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only successful new face enrollments count against your quota. Verifications and authentications are always free and unlimited — so a user logging in twice a day costs you nothing extra.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 200 enrollments/month, 1 project&lt;/li&gt;
&lt;li&gt;Starter: 2000 enrollments/month, 3 projects — $9/month&lt;/li&gt;
&lt;li&gt;Pro: 20,000 enrollments/month, unlimited projects — $29/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sign in at &lt;a href="https://faceidentity.site" rel="noopener noreferrer"&gt;faceidentity.site&lt;/a&gt; with Google — free, no card required. Integration is about 10 lines of backend code in JavaScript, Python, PHP, Go, or Dart; the widget handles the camera, UI, and liveness challenges.&lt;/p&gt;

&lt;p&gt;If you're a developer in Africa, South Asia, or anywhere resources are limited, I hope this shows the barrier to building real things is lower than people tell you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Muhammad Abubakar Yusuf, &lt;a href="mailto:faceiddevelopersupport@gmail.com"&gt;faceiddevelopersupport@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>api</category>
      <category>mobile</category>
      <category>security</category>
    </item>
  </channel>
</rss>
