VELO/Docs
v1 API

Universal Lead Capture

Technical instructions for integrating external websites with the Velo Dashboard. Optimized for Server-to-Server communication.

The Golden Rule: Server-Side Only

For production environments, DO NOT use client-side fetch. Use Next.js Server Actions or a dedicated backend proxy. This prevents CORS issues and keeps your Organization Keys hidden from public view.

Integration Overview

To capture leads from any website and route them to the correct Velo tenant workspace, you need to make an HTTP POST request to our centralized Edge Function.

POSThttps://fryqsyakqtnvprcyryvr.supabase.co/functions/v1/capture-lead

The "Missing Passport" Issue (Critical)

Supabase Edge Functions require a Passport (JWT in the Authorization header) by default.

1. The Passport

The Authorization header. Proves you are a logged-in user.

2. The Org Key

The x-org-key header. Proves which Velo tenant you belong to.

Since lead forms are public, visitors don't have a passport. Velo Admins must set verify_jwt: false in the Supabase function configuration to allow "Passport-free" submissions.

Request Headers

HeaderRequiredValue
Content-TypeYesapplication/json
x-org-keyYesThe Public Lead Key (e.g., org_abc123...). This identifies your organization and is found in Settings > Security.

Request Body (JSON)

FieldTypeRequiredDescription
nameStringNoFull name of the prospect.
phoneStringYes*Contact number. (*Required if email is not provided)
emailStringYes*Email address. (*Required if phone is not provided)
messageStringNoThe inquiry or context text.
sourceStringNoOrigin of the lead (Default: website).
_honeypotStringNoSecurity Layer: Must remain empty to pass spam check.
_tsNumberNoSecurity Layer: Current timestamp (ms) to detect fast bots.
cf-turnstile-responseStringNoSecurity Layer: The token from Cloudflare Turnstile.

Implementation Examples

1. Next.js Server Action (Recommended)

The most secure method. Hides your keys and bypasses CORS.

// src/app/actions/leads.ts
"use server"

export async function submitLeadAction(formData: any) {
  // Layer 3: Timing Check (Optional local check)
  const loadTime = parseInt(formData._ts || "0");
  if (Date.now() - loadTime < 1000) return { success: false, error: "Spam detected" };

  // Layer 3: Honeypot
  if (formData._honeypot) return { success: true }; // Silent ignore

  try {
    const response = await fetch('https://fryqsyakqtnvprcyryvr.supabase.co/functions/v1/capture-lead', {  
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-org-key': 'org_abc123...' // Keep this secret on the server!
      },
      body: JSON.stringify({
        ...formData,
        source: 'main_website'
      })
    });

    return await response.json();
  } catch (error) {
    return { success: false, error: "Connection failed" };
  }
}

2. AI IDE Agent Prompt (Cursor/Windsurf)

Use this prompt to have your AI agent build a secure, server-side form for you:

"Create a secure lead capture system for Velo. Implement a Next.js Server Action to handle the submission. The action must POST to 'https://fryqsyakqtnvprcyryvr.supabase.co/functions/v1/capture-lead' with header 'x-org-key: org_your_key'. Implement the 4-Layer Security Fortress: Include a '_honeypot' field, a '_ts' timestamp (ms), and 'cf-turnstile-response' for Cloudflare Turnstile. Ensure the server action validates that either phone or email is provided before sending to Velo."

Success Response

{
  "success": true,
  "leadId": "uuid-of-the-saved-lead"
}

Error Response

{
  "error": "Error message details"
}

Troubleshooting & Analogies

The Story: Post Box vs. Guard

"Imagine you built a Public Post Box (the API) for anyone to drop letters. But the building owner (Supabase) put a Security Guard at the door asking for an Employee ID (the Auth Header). Since regular people don't have Employee IDs, they are turned away before they even reach the box."

The Fix:

Set verify_jwt: false to remove the guard.

UNAUTHORIZED_NO_AUTH_HEADER

The guard is still there. Check your Supabase function settings.

SPAM_DETECTED

You submitted the form too fast (under 1s). Human check failed.

CAPTCHA_FAILED

Cloudflare Turnstile token is invalid or expired.

4-Layer Security Fortress

Velo uses a multi-layered defense to protect your leads:

  • Prefix Validation: Keys must start with org_ to be processed.
  • Domain Locking: Only requests from authorized domains are processed.
  • Anti-Spam: Honeypot and timing checks (>1s) block automated crawlers.
  • Bot Prevention: Native Turnstile/reCAPTCHA support on the backend.