Technical instructions for integrating external websites with the Velo Dashboard. Optimized for Server-to-Server communication.
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.
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.
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.
| Header | Required | Value |
|---|---|---|
| Content-Type | Yes | application/json |
| x-org-key | Yes | The Public Lead Key (e.g., org_abc123...). This identifies your organization and is found in Settings > Security. |
| Field | Type | Required | Description |
|---|---|---|---|
| name | String | No | Full name of the prospect. |
| phone | String | Yes* | Contact number. (*Required if email is not provided) |
| String | Yes* | Email address. (*Required if phone is not provided) | |
| message | String | No | The inquiry or context text. |
| source | String | No | Origin of the lead (Default: website). |
| _honeypot | String | No | Security Layer: Must remain empty to pass spam check. |
| _ts | Number | No | Security Layer: Current timestamp (ms) to detect fast bots. |
| cf-turnstile-response | String | No | Security Layer: The token from Cloudflare Turnstile. |
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" };
}
}Use this prompt to have your AI agent build a secure, server-side form for you:
{
"success": true,
"leadId": "uuid-of-the-saved-lead"
}{
"error": "Error message details"
}"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.
Velo uses a multi-layered defense to protect your leads:
org_ to be processed.