The Problem: Every client integration was reconstructed from fragments. Missing one step → silent 500.
The Solution: One checklist, one env-var contract, one verification command. Run the verifier after every deploy.
Every new client goes through these 5 steps. Missing one will break the site.
{CLIENT_UPPER}_GHL_PIT (e.g., EDEN_GHL_PIT, BTP_GHL_PIT)vercel env onlyLocation IDs map client names to GHL locations. Edit scripts/ghl/lead.js:
const LOCATION_IDS = {
eden: "YOUR_GHL_LOCATION_ID",
btp: "...",
// ... add your client
};
Get the location ID from GHL → Settings → Location ID. Exact string, no quotes.
Add a form with data-ghl-client attribute:
<form action="/api/lead/" method="POST" data-ghl-client="eden" > <input name="firstName" type="text" required> <input name="lastName" type="text" required> <input name="email" type="email" required> <input name="phone" type="tel"> <input name="message" type="text"> <input type="hidden" name="funnel" value="website"> <button type="submit">Submit</button> </form>
Important: Form fields are sent nested under fields to /api/lead/.
If the client wants Conversation AI:
{CLIENT_UPPER}_GHL_PIT}public/assets/chat.js, add to CLIENTS:
CLIENTS.eden = {
locationId: "YOUR_GHL_LOCATION_ID",
conversationId: "YOUR_CONVO_ID",
};
<script src="/assets/chat.js" data-client="eden"></script>
After deploy, run:
node scripts/ghl/verify-integration.sh eden
The script will:
{CLIENT}_GHL_PIT exists in Vercel/api/lead/contactId)All concept sites send the same payload shape to /api/lead/:
{
"client": "eden",
"funnel": "website",
"fields": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+61412345678",
"message": "Optional message"
}
}
Key detail: Form <input> names go into the nested fields object. The client and funnel stay at the top level.
lead.js validates:
firstName, lastName, email are requiredphone is optional (but recommended)message is optionalSet these for every client:
| Env Var | Type | Source | Example |
|---|---|---|---|
{CLIENT_UPPER}_GHL_PIT |
string | GHL Location Settings | pit_abc123... |
LOCATION_IDS in lead.js is hardcoded (not env vars) because location IDs are public and change rarely.
data-ghl-client doesn't match a key in LOCATION_IDS{CLIENT_UPPER}_GHL_PIT is not set in Vercel (use vercel env list to check)CLIENTS map in public/assets/chat.jsnode scripts/ghl/verify-integration.sh eden --chatlead.js returned { ok: true, contactId } — if not, PIT is deadscripts/ghl/lead.js — form submission handler (env: {CLIENT}_GHL_PIT)public/assets/chat.js — widget loader (env: {CLIENT}_GHL_PIT, static: CLIENTS map)scripts/ghl/verify-integration.sh — one-command verificationvercel env add {CLIENT_UPPER}_GHL_PIT <token>LOCATION_IDS in lead.jsdata-ghl-client="{client}"CLIENTS in chat.jsgit pushnode scripts/ghl/verify-integration.sh {client}Ship when verifier is green. That's the only gate.