Skip to content

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:

ColumnTypeDescription
iduuidUnique primary key (auto-generated)
nametextSender’s full name or organization
emailtextVerified sender contact email address
messagetextInquiry details and project requirements
langtextOrigin language (pl or en)
ip_addresstextClient remote IP (from HTTP_CF_CONNECTING_IP)
created_attimestamptzUTC 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).