authepy.

Integration Guide

Developer Hub

Zero-Dependency REST.

Authepy is engineered as a stateless, headless utility. We handle cryptographic hashing, VIP transit lanes, and IP abuse routing via a pure REST API—keeping your `node_modules` clean and leaving session management entirely in your control.

Universal REST Access 99.99% Uptime
curl -X POST https://api.authepy.com/api/otp/request \
  -H "Authorization: Bearer ath_sec_live_..." \
  -H "Content-Type: application/json" \
  -d '{"email": "user@startup.com"}'

Select your environment

8 Architectures Indexed
js
Native Express

Node.js Backends

Direct REST integration for custom monolithic architectures. Handle raw rate-limiting and session cookies securely with standard secret keys.

Jump to Code →
N
Server Actions

Next.js Framework

Execute verification entirely on the server. Integrate directly with App Router Server Actions or wrap into Auth.js (NextAuth) Credentials provider.

Jump to Code →
Restricted Keys

React Engine (SPA)

Build 100% custom UI states without forcing a frontend SDK into your bundle. Verify safely in browser using domain-whitelisted API keys.

Jump to Code →
RN
Proxy Pattern

Mobile Apps

Secure React Native and Flutter implementations. Use the Backend Proxy Pattern to prevent API keys from being exposed in compiled binaries.

Jump to Code →
Ξ
EIP-4361

Web3 & DApps

Cryptographically bind off-chain emails to on-chain wallet signatures (SIWE) instantly. Keep your verification logic stateless and decentralized.

Jump to Code →
W
Native PHP

WordPress Core

Eliminate brute force attacks. Replace standard WordPress passwords with Authepy OTPs using native wp_remote_post functions and secure shortcodes.

Jump to Code →
<>
Drop-in Widget

Site Builders

Easily add secure Email Verification (OTP) to your Wix, Webflow, or Framer site without writing backend code. Protect from fake signups.

Jump to Code →
API
Webhooks

Zapier & Automations

Add secure Email Verification to Zapier, Make, and Bubble workflows. Stop fake emails from triggering your expensive automations.

Jump to Code →
py
FastAPI / Django

Python Ecosystem

Drop identity verification into your machine learning pipelines, FastAPI edge servers, or Django monoliths using the `requests` library.

Docs coming soon
Traditional Backend Integration

Node.js Backends: Bulletproof identity for APIs.

Build completely decoupled architectures. Integrate Authepy's headless OTP infrastructure directly into your Express.js, Python, or Go APIs using a single Standard Secret Key.

Standard Secrets
|
Decoupled Architecture

Architecture Pipeline

Your frontend client will communicate with your own Express backend. Your Express backend then acts as a secure proxy, attaching your Standard Secret Key to communicate with the Authepy Edge Router.

Zero CORS issues. Pure Server-to-Server.
Issue your own session JWTs upon success.
server.js Express Implementation
const express = require('express');
const app = express();
app.use(express.json());

const AUTHEPY_API = 'https://api.authepy.com/api';

// 1. Request an OTP
app.post('/api/auth/send-code', async (req, res) => {
  const { email } = req.body;
  try {
    const authepyRes = await fetch(`${AUTHEPY_API}/otp/request`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
      },
      body: JSON.stringify({ email })
    });
    const data = await authepyRes.json();
    if (!authepyRes.ok) return res.status(400).json({ error: data.error });

    return res.status(200).json({ success: true, requestId: data.requestId });
  } catch (error) {
    return res.status(500).json({ error: "Internal server error." });
  }
});

// 2. Verify the OTP
app.post('/api/auth/verify-code', async (req, res) => {
  const { requestId, userGuess } = req.body;
  try {
    const authepyRes = await fetch(`${AUTHEPY_API}/otp/verify`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
      },
      body: JSON.stringify({ requestId, userGuess })
    });
    const data = await authepyRes.json();
    if (!authepyRes.ok) return res.status(400).json({ error: data.error });

    return res.status(200).json({ success: true, message: "User verified!" });
  } catch (error) {
    return res.status(500).json({ error: "Internal server error." });
  }
});

app.listen(3000, () => console.log('Auth server running'));
Next.js App Router Native

Zero-trust auth for the Edge.

Drop Authepy directly into your Next.js Server Actions. Execute cryptographic OTP verification entirely on Vercel or Cloudflare's Edge network—keeping your client bundle at exactly 0kB.

RSC Native
|
Zero Dependencies
|
Edge Runtime
app/actions/auth.ts "use server"
import { cookies } from 'next/headers';
const AUTHEPY_API = 'https://api.authepy.com/api';

// 1. Dispatch the OTP
export async function sendOTP(prevState: any, formData: FormData) {
  const email = formData.get('email');

  const res = await fetch(`${AUTHEPY_API}/otp/request`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
    },
    body: JSON.stringify({ email })
  });

  const data = await res.json();
  if (data.success) return { step: 'verify', email, requestId: data.requestId };
  return { error: data.error };
}

// 2. Verify the User's Input
export async function verifyOTP(prevState: any, formData: FormData) {
  const requestId = formData.get('requestId');
  const userGuess = formData.get('code');

  const res = await fetch(`${AUTHEPY_API}/otp/verify`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
    },
    body: JSON.stringify({ requestId, userGuess })
  });

  const data = await res.json();
  if (data.success) {
    const cookieStore = await cookies();
    cookieStore.set('auth_session', 'secure-jwt', { httpOnly: true, secure: true });
    return { success: true };
  }
  return { error: data.error || "Invalid verification code", step: 'verify', requestId };
}
app/login/page.tsx "use client"
import { useActionState } from 'react';
import { sendOTP, verifyOTP } from '../actions/auth';

export default function Login() {
  const [state, action, isPending] = useActionState(sendOTP, null);
  const [verifyState, verifyAction, isVerifyPending] = useActionState(verifyOTP, null);

  if (verifyState?.success) {
    return <h2>Authentication Successful! Redirecting...</h2>;
  }

  if (state?.step === 'verify' || verifyState?.step === 'verify') {
    return (
      <form action={verifyAction} className="flex flex-col gap-4 max-w-sm">
        <input type="hidden" name="requestId" value={state?.requestId || verifyState?.requestId || ''} />
        <input name="code" placeholder="000000" maxLength={6} required />
        <button type="submit" disabled={isVerifyPending}>
          {isVerifyPending ? 'Verifying...' : 'Verify Code'}
        </button>
        {verifyState?.error && <p style={{ color: 'red' }}>{verifyState.error}</p>}
      </form>
    );
  }

  return (
    <form action={action} className="flex flex-col gap-4 max-w-sm">
      <input name="email" type="email" placeholder="name@company.com" required />
      <button disabled={isPending}>{isPending ? 'Sending OTP...' : 'Login'}</button>
      {state?.error && <p style={{ color: 'red' }}>{state.error}</p>}
    </form>
  );
}
React Client Environment

Headless identity for React SPAs.

Build 100% custom UI states securely in the browser. Using Authepy's Restricted Keys, you can verify emails directly from your React frontend—no backend proxy required.

Vite Ready
|
Restricted Keys

Restricted Origin Shielding

The most critical rule of frontend development is that you cannot hide secrets in the browser. If you put a Standard Key in your React code, malicious actors may steal it.

Authepy solves this with Restricted Keys (rk_live_...). These keys are cryptographically bound to your specific domain names. Even if stolen, our Edge Router instantly rejects any request that doesn't originate from your whitelisted website.

✓ Auto-Fill UX Enabled

Add autoComplete="one-time-code" to input fields to trigger native keyboard suggestions.

src/components/Login.jsx React Component
import React, { useState } from 'react';
const AUTHEPY_API = 'https://api.authepy.com/api';
const RESTRICTED_API_KEY = 'YOUR_RESTRICTED_KEY_HERE';

export default function SPALogin() {
  const [email, setEmail] = useState('');
  const [code, setCode] = useState('');
  const [requestId, setRequestId] = useState(null);
  const [status, setStatus] = useState('idle');

  const handleSendOTP = async (e) => {
    e.preventDefault();
    setStatus('loading');
    const response = await fetch(`${AUTHEPY_API}/otp/request`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${RESTRICTED_API_KEY}`
      },
      body: JSON.stringify({ email })
    });
    const data = await response.json();
    if (data.success) {
      setRequestId(data.requestId);
      setStatus('verify');
    }
  };

  const handleVerifyOTP = async (e) => {
    e.preventDefault();
    setStatus('loading');
    const response = await fetch(`${AUTHEPY_API}/otp/verify`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${RESTRICTED_API_KEY}`
      },
      body: JSON.stringify({ requestId, userGuess: code })
    });
    const data = await response.json();
    if (data.success) setStatus('success');
  };

  if (status === 'success') return <div>Logged in!</div>;
  if (status === 'verify') return ( /* OTP Code Input View */ );
  return ( /* Email Input Initial Form View */ );
}
Mobile App Architectures

Secure mobile identity without exposed keys.

Mobile apps must never bundle secret API keys within their source code, as binary files (.apk or .ipa) are easily decompiled. Utilize the Backend Proxy Pattern to keep your application unhackable.

React Native / Flutter
|
Backend Proxy Pattern
proxy-server.js Server Handler
const express = require('express');
const app = express();
app.use(express.json());
const AUTHEPY_API = 'https://api.authepy.com/api';

app.post('/api/mobile/send-otp', async (req, res) => {
  const { email } = req.body;
  try {
    const authRes = await fetch(`${AUTHEPY_API}/otp/request`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
      },
      body: JSON.stringify({ email })
    });
    const data = await authRes.json();
    if (!data.success) return res.status(400).json({ error: data.error });
    return res.status(200).json({ success: true, requestId: data.requestId });
  } catch (error) {
    return res.status(500).json({ error: "Internal dispatch error." });
  }
});

app.post('/api/mobile/verify-otp', async (req, res) => {
  const { requestId, code } = req.body;
  try {
    const authRes = await fetch(`${AUTHEPY_API}/otp/verify`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
      },
      body: JSON.stringify({ requestId, userGuess: code })
    });
    const data = await authRes.json();
    if (!data.success) return res.status(400).json({ error: data.error });

    const mobileSessionToken = "generate_your_jwt_here";
    return res.status(200).json({ success: true, token: mobileSessionToken });
  } catch (error) {
    return res.status(500).json({ error: "Internal verification error." });
  }
});
MobileLoginScreen.js React Native Client
import React, { useState } from 'react';
import { View, TextInput, Button, Text, Alert } from 'react-native';
import * as Keychain from 'react-native-keychain';

export default function MobileLoginScreen() {
  const [email, setEmail] = useState('');
  const [code, setCode] = useState('');
  const [requestId, setRequestId] = useState(null);
  const [step, setStep] = useState('request');

  const YOUR_PROXY_URL = 'https://api.yourbackend.com/mobile';

  const handleRequestOTP = async () => {
    try {
      const res = await fetch(`${YOUR_PROXY_URL}/send-otp`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email })
      });
      const data = await res.json();
      if (data.success) {
        setRequestId(data.requestId);
        setStep('verify');
      } else {
        Alert.alert("Error", data.error);
      }
    } catch (err) { Alert.alert("Error", "Could not reach server."); }
  };

  const handleVerifyOTP = async () => {
    try {
      const res = await fetch(`${YOUR_PROXY_URL}/verify-otp`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ requestId, code })
      });
      const data = await res.json();
      if (data.success && data.token) {
        await Keychain.setGenericPassword('session', data.token);
        Alert.alert("Success", "Logged in securely!");
      } else {
        Alert.alert("Invalid Code", data.error);
      }
    } catch (err) { Alert.alert("Error", "Verification failed."); }
  };

  return (
    <View style={{ padding: 20 }}>
      {step === 'request' ? (
        <>
          <TextInput placeholder="name@company.com" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
          <Button title="Send Login Code" onPress={handleRequestOTP} />
        </>
      ) : (
        <>
          <Text>Enter the 6-digit code</Text>
          <TextInput placeholder="000000" value={code} onChangeText={setCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" />
          <Button title="Verify Code" onPress={handleVerifyOTP} />
        </>
      )}
    </View>
  );
}
Decentralized Architectures

Stateless Web2.5 Bridging.

Don't build centralized honeypots. Authepy allows DApps to verify Web2 emails completely off-chain, cryptographically binding the volatile result to a SIWE (Sign-In with Ethereum) payload in your own database using zero-dependency REST.

EIP-4361 Ready
|
EVM & SVM
|
Zero On-Chain State
api/verify-wallet.ts EIP-4361 Validation
import { SiweMessage } from 'siwe';

export async function verifyWallet(req, res) {
  const { message, signature } = req.body;
  try {
    const siweMessage = new SiweMessage(message);
    const fields = await siweMessage.verify({ signature });

    req.session.address = fields.data.address;
    req.session.siwe_verified = true;

    return res.json({ ok: true, address: fields.data.address });
  } catch (error) {
    return res.status(401).json({ error: "Invalid EIP-4361 signature." });
  }
}
api/bind-email.ts Cryptographic Identity Bind
import db from '@/lib/database';

export async function bindIdentity(req, res) {
  const { email, requestId, code } = req.body;
  const walletAddress = req.session.address;

  if (!req.session.siwe_verified) {
    return res.status(401).json({ error: "Wallet not authenticated." });
  }

  const response = await fetch('https://api.authepy.com/api/otp/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
    },
    body: JSON.stringify({ requestId, userGuess: code })
  });

  const otpCheck = await response.json();
  if (!otpCheck.success) {
    return res.status(400).json({ error: otpCheck.error || "Invalid OTP token." });
  }

  await db.users.upsert({
    where: { address: walletAddress },
    update: { email: email, is_verified: true },
    create: { address: walletAddress, email: email, is_verified: true }
  });

  return res.json({ ok: true, message: "Identity bound." });
}
WordPress Core Systems

Eliminate brute-force attacks on WordPress.

By replacing standard WordPress passwords with Authepy OTPs, you instantly neutralize brute-force attacks and credential stuffing bots using native `wp_remote_post` functions.

Native PHP Integration
|
wp_ajax Built-in
functions.php (AJAX Route Handles) PHP 8+ Core
<?php
$authepy_api_url = 'https://api.authepy.com/api';

// --- REQUEST OTP ---
add_action('wp_ajax_nopriv_authepy_request', 'authepy_request_otp');
add_action('wp_ajax_authepy_request', 'authepy_request_otp');

function authepy_request_otp() {
    global $authepy_api_url;
    $email = sanitize_email($_POST['email']);

    $response = wp_remote_post($authepy_api_url . '/otp/request', array(
        'headers' => array(
            'Authorization' => 'Bearer ' . AUTHEPY_SECRET_KEY,
            'Content-Type'  => 'application/json',
        ),
        'body' => wp_json_encode(array('email' => $email)),
    ));

    $body = json_decode(wp_remote_retrieve_body($response), true);
    if ( isset($body['success']) && $body['success'] === true ) {
        wp_send_json_success(array('requestId' => $body['requestId']));
    } else {
        wp_send_json_error(array('message' => $body['error']));
    }
}

// --- VERIFY OTP & LOGIN ---
add_action('wp_ajax_nopriv_authepy_verify', 'authepy_verify_otp');
add_action('wp_ajax_authepy_verify', 'authepy_verify_otp');

function authepy_verify_otp() {
    global $authepy_api_url;
    $request_id = sanitize_text_field($_POST['requestId']);
    $user_guess = sanitize_text_field($_POST['code']);
    $email = sanitize_email($_POST['email']);

    $response = wp_remote_post($authepy_api_url . '/otp/verify', array(
        'headers' => array(
            'Authorization' => 'Bearer ' . AUTHEPY_SECRET_KEY,
            'Content-Type'  => 'application/json',
        ),
        'body' => wp_json_encode(array('requestId' => $request_id, 'userGuess' => $user_guess)),
    ));

    $body = json_decode(wp_remote_retrieve_body($response), true);
    if ( isset($body['success']) && $body['success'] === true ) {
        $user = get_user_by('email', $email);
        if ( !$user ) {
            $random_password = wp_generate_password(12, false);
            $user_id = wp_create_user($email, $random_password, $email);
            $user = get_user_by('id', $user_id);
        }

        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID);
        wp_send_json_success(array('message' => 'Logged in successfully!'));
    } else {
        wp_send_json_error(array('message' => $body['error']));
    }
}
?>
functions.php (Shortcode Interface) Shortcode Extension
<?php
add_shortcode('authepy_login', 'render_authepy_login_form');

function render_authepy_login_form() {
    ob_start(); ?>
    <div id="authepy-wp-container">
        <input type="email" id="ath-email" placeholder="name@company.com" />
        <button onclick="requestAuthepyOTP()">Send Login Code</button>
    </div>

    <script>
        let globalRequestId = null;
        let globalEmail = null;
        const ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';

        function requestAuthepyOTP() {
            const email = document.getElementById('ath-email').value;
            const formData = new URLSearchParams();
            formData.append('action', 'authepy_request');
            formData.append('email', email);

            fetch(ajaxurl, { method: 'POST', body: formData })
                .then(res => res.json())
                .then(data => {
                    if (data.success) {
                        globalRequestId = data.data.requestId;
                        globalEmail = email;
                        // Display the OTP input field here
                    }
                });
        }
        
        function verifyAuthepyOTP() {
        }
    </script>
    <?php
    return ob_get_clean();
}
?>
No-Code Website Integrations

Email OTP Verification for Webflow, Wix & Framer

Stop fake users and bots from ruining your email list or accessing your hidden content. Copy and paste our visual widget code to verify real human emails in under 3 seconds.

The "Hold Your Hand" Setup Guide

  1. Generate a Restricted Key Log in to your dashboard, create a key under Restricted Domain Keys, type your website URL (e.g., mysite.com), and copy it.
  2. Place the Widget on your Page Add an HTML "Embed" element or "HTML iframe" block in Webflow or Wix, paste the layout snippet, and replace YOUR_RESTRICTED_KEY_HERE.
Visual Widget Embedding Object HTML / JS Object
<div id="authepy-widget" style="font-family: sans-serif; max-width: 320px; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
  <div id="ath-step-1">
    <h3>Secure Login</h3>
    <input type="email" id="ath-email" placeholder="name@company.com" style="width: 100%; padding: 10px; margin-bottom: 10px;" />
    <button onclick="authepySendOTP()" style="width: 100%; padding: 10px; background: #000; color: #fff;">Continue with Email</button>
    <p id="ath-error-1" style="color: red; font-size: 12px; margin-top: 8px;"></p>
  </div>

  <div id="ath-step-2" style="display: none;">
    <p>Enter the 6-digit code sent to your email.</p>
    <input type="text" id="ath-code" placeholder="000000" maxlength="6" style="width: 100%; padding: 10px; margin-bottom: 10px; text-align: center; letter-spacing: 5px;" />
    <button onclick="authepyVerifyOTP()" style="width: 100%; padding: 10px; background: #28a745; color: #fff;">Verify Code</button>
    <p id="ath-error-2" style="color: red; font-size: 12px; margin-top: 8px;"></p>
  </div>
</div>

<script>
  const AUTHEPY_API = 'https://api.authepy.com/api';
  const AUTHEPY_KEY = 'YOUR_RESTRICTED_KEY_HERE';
  let authepyRequestId = '';

  async function authepySendOTP() {
    const email = document.getElementById('ath-email').value;
    const err = document.getElementById('ath-error-1');
    if(!email) return;
    try {
      const res = await fetch(`${AUTHEPY_API}/otp/request`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${AUTHEPY_KEY}` },
        body: JSON.stringify({ email })
      });
      const data = await res.json();
      if (data.success) {
        authepyRequestId = data.requestId;
        document.getElementById('ath-step-1').style.display = 'none';
        document.getElementById('ath-step-2').style.display = 'block';
      } else {
        err.innerText = data.error;
      }
    } catch (e) { err.innerText = 'Network connection failed.'; }
  }

  async function authepyVerifyOTP() {
    const code = document.getElementById('ath-code').value;
    const err = document.getElementById('ath-error-2');
    if(!code || code.length < 5) return;
    try {
      const res = await fetch(`${AUTHEPY_API}/otp/verify`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${AUTHEPY_KEY}` },
        body: JSON.stringify({ requestId: authepyRequestId, userGuess: code })
      });
      const data = await res.json();
      if (data.success) {
        document.getElementById('authepy-widget').innerHTML = '<h3 style="color: green; text-align: center;">Verified Successfully</h3>';
      } else {
        err.innerText = data.error;
      }
    } catch (e) { err.innerText = 'Network connection failed.'; }
  }
</script>
No-Code & Automations

Email OTPs for Zapier, Make, & Bubble.

Every time a fake user triggers your workflow, it costs you money. Add Authepy to Zapier, Make, or Bubble to force users to verify their email before your expensive automations run.

Action Step Definition Primitives Matrix
Step 1: Request Endpoint
POST → https://api.authepy.com/api/otp/request
Step 2: Verification Endpoint
POST → https://api.authepy.com/api/otp/verify
Step 3: Conditional Logic Filter
Set the condition: Only continue if "success" (from the Authepy verification response step) (Boolean Is) True. If they typed the wrong code, the workflow stops immediately.