Contact & Lead Intake Pipeline
The lead capture engine in contact-handler.php handles inbound business inquiries from prospective energy cooperatives and commercial clients. It operates asynchronously via AJAX and integrates with Cloudflare Turnstile, Supabase REST, and Google Chat.
1. End-to-End Processing Workflow
sequenceDiagram
autonumber
actor Client as Visitor
participant Frontend as "Browser Form (JS)"
participant Handler as contact-handler.php
participant Turnstile as Cloudflare Turnstile API
participant Supabase as "Supabase (website_form_messages)"
participant GChat as Google Chat Webhook
Client->>Frontend: Submits contact form
Frontend->>Handler: POST /contact-handler.php (name, email, message, cf-turnstile-response)
Handler->>Handler: Validate inputs & email format
Handler->>Turnstile: Verify captcha token with secret key
Turnstile-->>Handler: Challenge confirmed (success: true)
Handler->>Supabase: POST to /rest/v1/website_form_messages
Supabase-->>Handler: 201 Created
Handler->>GChat: POST Google Chat Card JSON
GChat-->>Handler: 200 OK
Handler-->>Frontend: { success: true, message: "Dziękujemy za kontakt!" }
Frontend-->>Client: Displays animated success alert & resets inputs
2. Supabase Storage Schema
Inbound contact messages are stored in the website_form_messages table within the Planovi production Supabase database:
| Column | Type | Description |
|---|---|---|
id | uuid | Unique primary key (auto-generated) |
name | text | Sender’s full name or organization |
email | text | Verified sender contact email address |
message | text | Inquiry details and project requirements |
lang | text | Origin language (pl or en) |
ip_address | text | Client remote IP (from HTTP_CF_CONNECTING_IP) |
created_at | timestamptz | UTC submission timestamp |
Database Insertion Routine:
$supabaseUrl = rtrim(site_config('supabase.url'), '/');$supabaseKey = site_config('supabase.key');$supabaseTable = site_config('supabase.table', 'website_form_messages');
$dbPayload = json_encode([ 'name' => $name, 'email' => $email, 'message' => $message, 'lang' => $lang, 'ip_address' => $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? ''], JSON_UNESCAPED_UNICODE);
$ch = curl_init($supabaseUrl . '/rest/v1/' . $supabaseTable);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $dbPayload);curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'apikey: ' . $supabaseKey, 'Authorization: Bearer ' . $supabaseKey, 'Prefer: return=minimal']);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_TIMEOUT, 10);$dbResult = curl_exec($ch);curl_close($ch);3. Google Chat Real-Time Notifications
To ensure zero delay in responding to prospective clients, every validated inquiry immediately constructs an interactive Google Chat webhook payload:
$chatWebhookUrl = site_config('google_chat.webhook_url');
if (!empty($chatWebhookUrl)) { $cardPayload = json_encode([ 'cardsV2' => [ [ 'cardId' => 'contact-lead-' . time(), 'card' => [ 'header' => [ 'title' => '⚡ Nowe Zapytanie: PLANOVI', 'subtitle' => 'Formularz kontaktowy (Język: ' . strtoupper($lang) . ')', 'imageUrl' => 'https://planovi.app/assets/logo.png', 'imageType' => 'CIRCLE' ], 'sections' => [ [ 'widgets' => [ [ 'decoratedText' => [ 'topLabel' => 'Od kogo', 'text' => '<b>' . htmlspecialchars($name) . '</b>' ] ], [ 'decoratedText' => [ 'topLabel' => 'Email zwrotny', 'text' => '<a href="mailto:' . htmlspecialchars($email) . '">' . htmlspecialchars($email) . '</a>' ] ], [ 'decoratedText' => [ 'topLabel' => 'Treść wiadomości', 'text' => nl2br(htmlspecialchars($message)), 'wrapText' => true ] ] ] ] ] ] ] ] ], JSON_UNESCAPED_UNICODE);
$ch = curl_init($chatWebhookUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $cardPayload); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=UTF-8']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_exec($ch); curl_close($ch);}4. Error Responses & Client Feedback
The script returns standard HTTP status codes:
200 OK: Successfully submitted and recorded.400 Bad Request: Missing required fields or invalid email syntax (contact.err_fields).403 Forbidden: Cloudflare Turnstile token validation failure (contact.err_captcha).500 Internal Server Error: Downstream database connectivity failure (contact.err_server).