Building AI-Signed: A Website Trust Verification Platform

How we designed and built a SaaS platform that runs 43 automated checks to score, grade, and verify website trustworthiness for both humans and AI agents.

The internet has a trust problem. Phishing sites mimic legitimate businesses. AI-generated content floods the web with pages that look professional but have zero accountability behind them. And now, as AI agents begin browsing the web autonomously, the question of "can I trust this website?" isn't just a human concern anymore — it's a machine concern too.

That's the problem we set out to solve with AI-Signed (opens in new tab). It's a website trust verification platform that runs 43 automated checks across 5 categories, produces a trust score and letter grade, and issues embeddable SVG badges that both humans and AI agents can verify programmatically. This post is a deep technical walkthrough of how we built it.

The Problem: Trust Is Hard to Verify at Scale

When a visitor lands on a website, they make snap judgments about legitimacy. Does it have HTTPS? Does the content look real? Is there a privacy policy? These are reasonable heuristics, but they're unreliable — a well-crafted phishing page can check every one of those boxes.

The deeper signals of trust — valid SSL certificate chains, proper DNS configuration, security headers, DMARC email authentication, structured data, consistent business identity — require technical expertise to evaluate. No casual visitor is going to open browser DevTools and inspect CSP headers before entering their credit card number.

We wanted to automate that entire evaluation. Run every check a skilled security engineer would run, weight the results according to their actual importance, and distill the output into a single score that anyone can understand. Then make that score verifiable — not just a self-reported claim, but something backed by an API that returns the raw check data.

Architecture Overview

AI-Signed is a full-stack TypeScript application. We chose a modern stack that prioritizes type safety across every layer, from database queries to API responses to form validation.

Next.js 15 TypeScript (strict) React 19 Prisma 6 PostgreSQL 17 Auth.js v5 Stripe Tailwind CSS 3 Zod Nodemailer 8 Vitest 4 Node 22

Next.js 15 with the App Router gave us the ability to build server components, API routes, and static pages in a single project. We run it in standalone mode for Docker deployment, which produces a minimal output directory with only the files the production server actually needs.

Prisma 6 handles all database access through 11 models — User, Site, Scan, ScanCheck, TrustScore, Subscription, ApiKey, and others. Every query is fully typed. There's no raw SQL anywhere in the codebase, which means schema changes propagate type errors immediately rather than surfacing as runtime crashes in production.

The Scanning Engine: 43 Checks in Parallel

The core of AI-Signed is its scanning engine. When a user submits a domain for verification, we dispatch 8 specialized scanners that collectively run 43 individual checks. Here's how they break down:

  • SSL/TLS Scanner — 7 checks: certificate validity, chain completeness, expiration window, protocol version, key strength, HSTS presence, and certificate transparency logs
  • DNS Scanner — 7 checks: A/AAAA records, MX configuration, SPF, DKIM, DMARC, DNSSEC, and CAA records
  • Security Headers Scanner — 6 checks: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and Strict-Transport-Security
  • Content Quality Scanner — 7 checks: meta descriptions, Open Graph tags, structured data, privacy policy presence, terms of service, contact information, and content length
  • Reputation Scanner — 5 checks: domain age, WHOIS consistency, Google Safe Browsing status, blacklist presence, and historical scan trends
  • Performance Scanner — 3 checks: response time, redirect chains, and page size
  • AI Readiness Scanner — 7 checks: llms.txt, robots.txt AI directives, structured data richness, API availability, machine-readable content, ai-plugin.json, and .well-known/security.txt
  • Identity Scanner — 4 checks: consistent business name across pages, physical address presence, phone number presence, and domain-email match

The critical architectural decision was running all 8 scanners concurrently using Promise.allSettled() rather than Promise.all(). This distinction matters significantly in production.

const scanResults = await Promise.allSettled([
  sslScanner.scan(domain),
  dnsScanner.scan(domain),
  headersScanner.scan(domain),
  contentScanner.scan(domain),
  reputationScanner.scan(domain),
  performanceScanner.scan(domain),
  aiReadinessScanner.scan(domain),
  identityScanner.scan(domain),
]);

With Promise.all(), if any single scanner throws — say, a DNS timeout on a slow resolver — the entire scan fails. With Promise.allSettled(), every scanner runs to completion regardless of whether its siblings succeed or fail. We then iterate the results, extract values from fulfilled promises, and assign zero scores to rejected ones. A flaky third-party API call in the reputation scanner never takes down the entire scan.

Design for partial failure. In a system that makes dozens of external network calls, something will always go wrong. The question is whether a single failure cascades or degrades gracefully.

Scoring and Grading: Weighted Category Model

Raw check results are meaningless without a scoring model that reflects real-world importance. A missing X-Frame-Options header and a completely invalid SSL certificate are not equally concerning, but a naive system would weight them the same.

We settled on a weighted category model after extensive testing against known-good and known-bad sites:

  • Identity — 25% weight: Is there a verifiable business behind this site?
  • Technical Security — 25% weight: Are SSL, DNS, and security headers properly configured?
  • AI Readiness — 20% weight: Can AI agents discover, verify, and interact with this site?
  • Content Quality — 15% weight: Does the content meet professional standards?
  • Reputation — 15% weight: What does the domain's history and external signals say?

Identity and Technical Security each carry 25% because they represent the two foundational questions: "Who is behind this?" and "Is the connection secure?" AI Readiness gets a substantial 20% because the platform's core thesis is that websites need to be verifiable by machines, not just humans. Content Quality and Reputation round out the remaining 30%.

The final score maps to a letter grade: A+ (95+), A (90-94), A- (85-89), B+ (80-84), B (75-79), B- (70-74), C+ (65-69), C (60-64), C- (55-59), D (40-54), and F (below 40). Badge levels follow: Platinum for A+ and A, Gold for A- and B+, Silver for B and B-, Bronze for C+ through D, and no badge issued for F grades.

Domain Verification and Ownership

Anyone can scan any public domain — that's by design. But claiming ownership of a domain and displaying its trust badge requires verification. We use DNS TXT record verification, the same method used by Google Search Console, Stripe, and virtually every domain verification system in production.

The user adds a TXT record like ai-signed-verify=abc123 to their domain's DNS. Our system queries the domain's TXT records and checks for a match. This proves the person has administrative control over the domain's DNS — a strong signal of ownership that doesn't require changes to the website itself.

Security Throughout the Stack

Building a trust verification platform that is itself insecure would be an unforgivable irony. Security wasn't an afterthought — it shaped decisions at every layer.

Authentication and API Keys

User authentication runs on Auth.js v5 (the beta successor to NextAuth). For programmatic access, we provide API keys. Keys are generated server-side and shown to the user exactly once. What we store in PostgreSQL is the SHA-256 hash of the key, never the key itself. When an API request arrives, we hash the provided key and compare it against the stored hash. If our database is compromised, the attacker gets hashes, not usable keys.

Timing-Safe Comparisons

All secret comparisons — API key verification, HMAC token validation, CSRF checks — use constant-time comparison functions. A naive string comparison returns early on the first mismatched character, which leaks information about how many leading characters were correct through response time differences. Timing-safe comparison always examines every byte, making timing attacks impractical.

CSRF and Origin Validation

Every mutating endpoint validates the Origin header against a whitelist. Cross-origin POST requests are rejected before they reach any business logic. Combined with SameSite cookie attributes, this provides layered CSRF protection without relying on hidden form tokens that complicate API usage.

Email Enumeration Prevention

Login and password reset flows return identical responses regardless of whether the email exists in our system. An attacker probing for registered accounts gets no signal. The actual success or failure is communicated only through the email channel itself.

Rate Limiting

Every endpoint is rate-limited. The public trust API allows 10 requests per minute for unauthenticated callers and 100 per minute for authenticated users. Scan endpoints, authentication routes, and email-sending endpoints all have independent limits tuned to their expected usage patterns and abuse potential.

Embeddable Trust Badges

The public-facing output of a scan is an SVG trust badge. We chose SVG over PNG or HTML embeds for specific reasons: SVGs scale perfectly at any size, they're tiny (a few KB), they can be served with aggressive caching, and they render identically across all browsers and email clients.

Each badge encodes the domain name, trust grade, and score. The badge URL is deterministic — ai-signed.com/badge/example.com.svg — which means it can be embedded in any HTML page with a simple img tag. The badge links to a verification page where anyone (human or AI agent) can see the full scan breakdown.

For AI agents specifically, the badge URL is also discoverable through our public API, the site's llms.txt file, and the ai-plugin.json manifest. An LLM browsing the web can hit our API to verify any domain's trust score without rendering the badge visually.

Subscription Billing with Stripe

AI-Signed operates on a simple subscription model: $5.99 per month. We integrated Stripe for billing, which handles payment processing, subscription lifecycle management, invoice generation, and failed payment retries.

The Stripe webhook endpoint processes events like invoice.payment_succeeded, customer.subscription.deleted, and invoice.payment_failed. Each event updates the user's subscription status in our database. We verify every webhook signature to prevent forged events, and we process webhooks idempotently — processing the same event twice produces the same database state.

Email System and CAN-SPAM Compliance

AI-Signed sends transactional emails for account verification, password resets, scan completion notifications, and billing receipts. Every email includes a physical mailing address and an unsubscribe link — both required by CAN-SPAM.

The unsubscribe mechanism uses HMAC-based tokens. Each unsubscribe URL contains a token that is an HMAC of the user's email address, signed with a server secret. When a user clicks the link, we verify the HMAC before processing the unsubscribe. This prevents attackers from unsubscribing arbitrary email addresses by guessing URLs.

Cookie Consent and Analytics

We run self-hosted Umami analytics — no Google Analytics, no third-party tracking pixels. Even so, we implemented a cookie consent banner that gates the analytics script. The Umami tracking code only loads after the user explicitly consents. If they decline, no analytics data is collected at all. This isn't legally required for cookieless analytics like Umami, but it reflects the trust-first ethos of the product.

Data Model and Database Design

The Prisma schema defines 11 models that capture the full lifecycle of domain verification:

  • User — Account data, authentication, subscription status
  • Site — Registered domains with ownership verification status
  • Scan — Individual scan runs with timestamps and overall scores
  • ScanCheck — The 43 individual check results per scan, with pass/fail status and details
  • TrustScore — Computed category scores, weighted total, letter grade, and badge level
  • Subscription — Stripe subscription data, plan details, billing status
  • ApiKey — SHA-256 hashed API keys with usage tracking and rate limit metadata

A monthly cron job triggers automatic rescans of all verified domains, keeping trust scores current without requiring manual intervention. A separate purge job removes scan data older than 12 months, keeping the database lean while retaining enough history for trend analysis.

SEO and LLM Discoverability

For a platform whose entire value proposition is web trust, discoverability matters. We implemented dynamic Open Graph images generated per-page, a comprehensive sitemap, and JSON-LD structured data throughout.

On the LLM discoverability front, AI-Signed serves an llms.txt file (the emerging standard for helping language models understand a site), an ai-plugin.json manifest (originally designed for ChatGPT plugins but increasingly used for general AI agent discovery), and a .well-known/security.txt file. These files make it trivial for AI agents to discover the trust verification API and understand how to use it — which is exactly the behavior we want to encourage.

Infrastructure and Deployment

The production deployment runs on Docker Compose with two containers: the Next.js application and PostgreSQL 17. The application container is built from a multi-stage Dockerfile that produces a minimal production image using Next.js standalone output mode.

The containers run on a dedicated VM at 10.0.0.70 in our Proxmox cluster. An Apache reverse proxy on a separate VM handles TLS termination with Let's Encrypt certificates, then forwards traffic to the application container over the internal network. This separation means the application never handles raw TLS — it receives plain HTTP from a trusted internal proxy, simplifying the application configuration and certificate management.

What We Learned

Building AI-Signed reinforced several principles we apply across all our projects:

Type safety pays compound interest. TypeScript strict mode with Prisma's generated types and Zod validation means errors surface at build time, not in production. The upfront investment in type definitions eliminates entire categories of runtime bugs.

Partial failure handling is not optional. Any system that makes network calls to external services — DNS resolvers, SSL endpoints, third-party APIs — must handle partial failures gracefully. Promise.allSettled() should be the default for concurrent external calls, not Promise.all().

Security is architecture, not a feature. Hashing API keys, using timing-safe comparisons, preventing email enumeration — these aren't features you bolt on at the end. They're architectural decisions that shape how you build every endpoint from day one.

If you're building a product that evaluates trust, you'd better be trustworthy yourself. Every security decision in AI-Signed was made with the assumption that our own platform would be the first thing a skeptic scans.

AI-Signed is live at ai-signed.com (opens in new tab). If you're interested in how we can build something similar for your business — or if you want to verify your own site's trust score — get in touch.

All Posts Next: Building the AI-Signed Shopify App

Have a SaaS idea? Let's build it together.

Contact Us