Integrations · Updated 2026-10-20

Signup checks in FastAPI

An async FastAPI dependency that screens signups with httpx, reads the client IP correctly, and fails open.

# veqen_dep.py
import os
import httpx
from fastapi import HTTPException, Request
from pydantic import BaseModel, EmailStr

client = httpx.AsyncClient(
    base_url="https://veqen-app.com",
    headers={"Authorization": f"Bearer {os.environ['VEQEN_API_KEY']}"},
    timeout=4,
)


class SignupIn(BaseModel):
    email: EmailStr
    password: str
    phone: str | None = None


async def screen(body: SignupIn, request: Request) -> dict | None:
    ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip() or request.client.host
    try:
        response = await client.post("/v1/signup/check", json={"email": body.email, "ip": ip, "phone": body.phone})
        response.raise_for_status()
        result = response.json()
    except httpx.HTTPError:
        return None  # fail open
    if result["risk"]["verdict"] == "refuse":
        raise HTTPException(400, "Please use a permanent email address.")
    return result
# main.py
from fastapi import Depends, FastAPI
from veqen_dep import SignupIn, screen

app = FastAPI()


@app.post("/signup", status_code=201)
async def signup(body: SignupIn, check: dict | None = Depends(screen)):
    needs_verification = check is None or check["risk"]["verdict"] == "review"
    user = await create_user(body, needs_verification=needs_verification)
    return {"id": user.id, "verify_email": needs_verification}

Run uvicorn with --proxy-headers --forwarded-allow-ips=<your proxy> so request.client.host is the visitor when you're behind a load balancer.