/email%40domain.com * * @author Dixon * @version 1.0 */ // Prevent any output buffering issues if (ob_get_level()) { ob_end_clean(); } // Retrieve the complete request URI from the server superglobal $requestUri = $_SERVER['REQUEST_URI'] ?? ''; // Parse the URL to extract only the path component, excluding any query strings // This ensures that any GET parameters don't interfere with email extraction $path = parse_url($requestUri, PHP_URL_PATH); // Remove leading and trailing forward slashes from the path // Example: '/email%40domain.com/' or '/email%40domain.com/github' $pathParts = explode('/', trim($path, '/')); // Extract email and optional parser $emailEncoded = $pathParts[0] ?? ''; $parserName = $pathParts[1] ?? null; // Decode the URL-encoded email address (e.g., %40 becomes @) // Example: 'email%40domain.com' becomes 'email@domain.com' $email = urldecode($emailEncoded); if(empty($email)) { include'docs.htm'; die(); } // Validate the extracted email address using PHP's built-in filter // This ensures the email is in a valid format before proceeding if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { // Set HTTP response code to 400 Bad Request http_response_code(400); // Return error message to the client echo 'Invalid email address provided in URL path.'; // Terminate script execution exit; } // Capture the current date and time when the webhook request was received // Format: YYYY-MM-DD HH:MM:SS (24-hour format) $dateReceived = date('Y-m-d H:i:s'); // Retrieve the IP address of the client making the request // Note: This may be a proxy IP if behind a load balancer or CDN $ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'Unknown'; // Capture the HTTP method used for this request (GET, POST, PUT, DELETE, etc.) $requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'Unknown'; // Retrieve the Content-Type header sent by the client // Use null coalescing operator to provide a default if not set $contentType = $_SERVER['CONTENT_TYPE'] ?? 'N/A'; // Retrieve all HTTP headers sent with the request // getallheaders() returns an associative array of all headers $headersArray = getallheaders(); // Initialize an empty string to store formatted headers $headersText = ''; // Iterate through each header and format it as "Key: Value" foreach ($headersArray as $key => $value) { // Concatenate each header on a new line $headersText .= htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . ': ' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . "\n"; } // Read the raw request body from the input stream // php://input allows access to the raw POST data regardless of Content-Type $rawBody = file_get_contents('php://input'); // Sanitise the raw body for HTML output to prevent XSS attacks $rawBodySafe = htmlspecialchars($rawBody, ENT_QUOTES, 'UTF-8'); // Compile all PHP superglobal variables into an array for debugging purposes // This provides visibility of all data received with the request $variables = [ '_GET' => $_GET, '_POST' => $_POST, '_REQUEST' => $_REQUEST, '_FILES' => $_FILES, ]; // Convert the variables array to a human-readable string format // The second parameter (true) makes print_r return the output instead of printing it $variablesText = print_r($variables, true); // Sanitise the variables text for HTML output $variablesTextSafe = htmlspecialchars($variablesText, ENT_QUOTES, 'UTF-8'); // Sanitise individual metadata fields for HTML output $dateReceivedSafe = htmlspecialchars($dateReceived, ENT_QUOTES, 'UTF-8'); $ipAddressSafe = htmlspecialchars($ipAddress, ENT_QUOTES, 'UTF-8'); $requestMethodSafe = htmlspecialchars($requestMethod, ENT_QUOTES, 'UTF-8'); $contentTypeSafe = htmlspecialchars($contentType, ENT_QUOTES, 'UTF-8'); // Check if a parser is specified and exists $html = ''; $subject = ''; if ($parserName && function_exists($parserName . 'Parse')) { $parserFunction = $parserName . 'Parse'; $parsed = $parserFunction($rawBody, $headersArray, [ 'date' => $dateReceived, 'ip' => $ipAddress, 'method' => $requestMethod, 'contentType' => $contentType ]); if ($parsed !== false) { $html = $parsed['html']; $subject = $parsed['subject']; } } // If no parser or parser failed, use default format if (empty($html)) { // Construct the HTML email body using a heredoc string for better readability // This creates a styled HTML email with all webhook details $html = << Webhook Request Received

⚡ Webhook Request Received

$dateReceivedSafe

📋 Request Information
📨 Request Headers
$headersText
📦 Request Body
$rawBodySafe
🔧 PHP Variables
$variablesTextSafe
HTML; // Construct the email subject line with an attention-grabbing emoji and timestamp $subject = "‼️ Webhook Request Received - $dateReceived"; } /** * Helper function to render nested arrays/objects as HTML tables * * @param array $data The data to render * @param int $depth Current recursion depth * @return string HTML representation of the data */ function renderDataAsTable($data, $depth = 0) { if ($depth > 10) { return '
Max depth reached
'; } $html = ''; if (!is_array($data)) { $safe = htmlspecialchars((string)$data, ENT_QUOTES, 'UTF-8'); return "
$safe
"; } // Check if it's an indexed array of similar items (like commits) $isIndexedArray = array_keys($data) === range(0, count($data) - 1); if ($isIndexedArray && count($data) > 0) { // Render as a list of items foreach ($data as $index => $item) { if (is_array($item)) { $html .= '
'; $html .= '
Item ' . ($index + 1) . '
'; $html .= '
'; foreach ($item as $key => $value) { $keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); if (is_array($value)) { $html .= ''; } else { $valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); $html .= ''; } } $html .= '
'; $html .= '
'; } else { $valueSafe = htmlspecialchars((string)$item, ENT_QUOTES, 'UTF-8'); $html .= '
• ' . $valueSafe . '
'; } } } else { // Render as key-value table $html .= '
'; foreach ($data as $key => $value) { $keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); if (is_array($value) && !empty($value)) { // Close current table and start a subsection $html .= '
'; $html .= '
'; $html .= '
' . $keySafe . '
'; $html .= renderDataAsTable($value, $depth + 1); $html .= '
'; $html .= '
'; } else if (is_array($value) && empty($value)) { $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; } else { $valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; } } $html .= '
'; } return $html; } /** * GitHub Webhook Parser * Parses GitHub webhook events into a formatted email * * @param string $rawBody The raw request body * @param array $headers The request headers * @param array $metadata Request metadata (date, ip, method, contentType) * @return array|false Array with 'html' and 'subject' keys, or false on failure */ function githubParse($rawBody, $headers, $metadata) { $payload = json_decode($rawBody, true); if (!$payload) { return false; } // Determine event type from headers $event = $headers['X-GitHub-Event'] ?? $headers['X-Github-Event'] ?? 'Unknown'; $delivery = $headers['X-GitHub-Delivery'] ?? $headers['X-Github-Delivery'] ?? 'Unknown'; // Extract common data $action = $payload['action'] ?? null; $repository = $payload['repository']['full_name'] ?? 'Unknown'; $sender = $payload['sender']['login'] ?? 'Unknown'; // Sanitize for HTML $eventSafe = htmlspecialchars($event, ENT_QUOTES, 'UTF-8'); $actionSafe = $action ? htmlspecialchars($action, ENT_QUOTES, 'UTF-8') : null; $repoSafe = htmlspecialchars($repository, ENT_QUOTES, 'UTF-8'); $senderSafe = htmlspecialchars($sender, ENT_QUOTES, 'UTF-8'); $dateSafe = htmlspecialchars($metadata['date'], ENT_QUOTES, 'UTF-8'); $ipSafe = htmlspecialchars($metadata['ip'], ENT_QUOTES, 'UTF-8'); // Build event-specific content $eventContent = ''; $eventIcon = '🔔'; switch ($event) { case 'push': $eventIcon = '📤'; $ref = htmlspecialchars($payload['ref'] ?? 'Unknown', ENT_QUOTES, 'UTF-8'); $commits = $payload['commits'] ?? []; $commitCount = count($commits); $eventContent = <<
Push Details
HTML; if ($commitCount > 0) { $eventContent .= '
Commits
'; foreach (array_slice($commits, 0, 10) as $commit) { $message = htmlspecialchars($commit['message'] ?? '', ENT_QUOTES, 'UTF-8'); $author = htmlspecialchars($commit['author']['name'] ?? 'Unknown', ENT_QUOTES, 'UTF-8'); $sha = htmlspecialchars(substr($commit['id'] ?? '', 0, 7), ENT_QUOTES, 'UTF-8'); $eventContent .= "
$sha $message by $author
"; } $eventContent .= '
'; } break; case 'pull_request': $eventIcon = '🔀'; $pr = $payload['pull_request'] ?? []; $prNumber = htmlspecialchars($pr['number'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $prTitle = htmlspecialchars($pr['title'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $prUrl = htmlspecialchars($pr['html_url'] ?? '#', ENT_QUOTES, 'UTF-8'); $prState = htmlspecialchars($pr['state'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $prBase = htmlspecialchars($pr['base']['ref'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $prHead = htmlspecialchars($pr['head']['ref'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $eventContent = <<
Pull Request Details
HTML; break; case 'issues': $eventIcon = '📝'; $issue = $payload['issue'] ?? []; $issueNumber = htmlspecialchars($issue['number'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $issueTitle = htmlspecialchars($issue['title'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $issueUrl = htmlspecialchars($issue['html_url'] ?? '#', ENT_QUOTES, 'UTF-8'); $issueState = htmlspecialchars($issue['state'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $eventContent = <<
Issue Details
HTML; break; case 'release': $eventIcon = '🚀'; $release = $payload['release'] ?? []; $tagName = htmlspecialchars($release['tag_name'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $releaseName = htmlspecialchars($release['name'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $releaseUrl = htmlspecialchars($release['html_url'] ?? '#', ENT_QUOTES, 'UTF-8'); $eventContent = <<
Release Details
HTML; break; default: // Generic event display - render as structured tables $eventContent = '
Event Details
'; $eventContent .= renderDataAsTable($payload); $eventContent .= '
'; } $actionDisplay = $actionSafe ? " ($actionSafe)" : ''; $html = << GitHub Webhook - $eventSafe

$eventIcon GitHub: $eventSafe$actionDisplay

$dateSafe

📋 Repository Information
$eventContent
HTML; $subject = "$eventIcon GitHub $eventSafe$actionDisplay - $repoSafe"; return [ 'html' => $html, 'subject' => $subject ]; } /** * Webex Interact Webhook Parser * Parses Webex Interact SMS webhook events into a formatted email * * @param string $rawBody The raw request body * @param array $headers The request headers * @param array $metadata Request metadata (date, ip, method, contentType) * @return array|false Array with 'html' and 'subject' keys, or false on failure */ function wxinteractParse($rawBody, $headers, $metadata) { $payload = json_decode($rawBody, true); if (!$payload || !isset($payload['data'])) { return false; } $data = $payload['data']; $status = $data['status'] ?? 'Unknown'; // Sanitize common fields $statusSafe = htmlspecialchars($status, ENT_QUOTES, 'UTF-8'); $dateSafe = htmlspecialchars($metadata['date'], ENT_QUOTES, 'UTF-8'); $ipSafe = htmlspecialchars($metadata['ip'], ENT_QUOTES, 'UTF-8'); // Determine event type and icon $eventIcon = '📱'; $eventTitle = 'SMS Event'; $eventContent = ''; switch ($status) { case 'submitted': $eventIcon = '📤'; $eventTitle = 'Outbound SMS - Submitted'; $eventContent = buildOutboundSmsSubmittedContent($data); break; case 'delivered': $eventIcon = '✅'; $eventTitle = 'Outbound SMS - Delivered'; $eventContent = buildOutboundSmsDeliveredContent($data); break; case 'failed': $eventIcon = '❌'; $eventTitle = 'Outbound SMS - Failed'; $eventContent = buildOutboundSmsFailedContent($data); break; case 'shortlink_clicked': $eventIcon = '🔗'; $eventTitle = 'Shortlink Clicked'; $eventContent = buildShortlinkClickedContent($data); break; case 'received': $eventIcon = '📥'; $eventTitle = 'Inbound SMS Received'; $eventContent = buildInboundSmsContent($data); break; case 'opt_out': $eventIcon = '🚫'; $eventTitle = 'SMS Opt Out Received'; $eventContent = buildOptOutContent($data); break; case 'contact_created': case 'contact_updated': $eventIcon = '👤'; $eventTitle = 'Contacts Callback'; $eventContent = buildContactsCallbackContent($data); break; default: // Unknown event type - display all data $eventIcon = '📱'; $eventTitle = "SMS Event: $statusSafe"; $eventContent = '
Event Details
'; $eventContent .= renderDataAsTable($data); $eventContent .= '
'; } $eventTitleSafe = htmlspecialchars($eventTitle, ENT_QUOTES, 'UTF-8'); $html = << Webex Interact - $eventTitleSafe

$eventIcon Webex Interact: $eventTitleSafe

$dateSafe

📋 Event Information
$eventContent
HTML; $subject = "$eventIcon Webex Interact: $eventTitle"; return [ 'html' => $html, 'subject' => $subject ]; } /** * Build content for Outbound SMS Submitted event */ function buildOutboundSmsSubmittedContent($data) { $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $message = htmlspecialchars($data['message'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $submittedAt = htmlspecialchars($data['submitted_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
📤 SMS Details
💬 Message Content
$message
HTML; } /** * Build content for Outbound SMS Delivered event */ function buildOutboundSmsDeliveredContent($data) { $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $deliveredAt = htmlspecialchars($data['delivered_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
✅ Delivery Details
HTML; } /** * Build content for Outbound SMS Failed event */ function buildOutboundSmsFailedContent($data) { $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $failedAt = htmlspecialchars($data['failed_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $errorCode = htmlspecialchars($data['error_code'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $errorMessage = htmlspecialchars($data['error_message'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
❌ Failure Details
⚠️ Error Information
Error Code: $errorCode
Error Message: $errorMessage
HTML; } /** * Build content for Shortlink Clicked event */ function buildShortlinkClickedContent($data) { $shortlink = htmlspecialchars($data['shortlink'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $originalUrl = htmlspecialchars($data['original_url'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $clickedAt = htmlspecialchars($data['clicked_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $phoneNumber = htmlspecialchars($data['phone_number'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $userAgent = htmlspecialchars($data['user_agent'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $ipAddress = htmlspecialchars($data['ip_address'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
🔗 Click Details
🌐 Browser Information
$userAgent
HTML; } /** * Build content for Inbound SMS Received event */ function buildInboundSmsContent($data) { $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $message = htmlspecialchars($data['message'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $receivedAt = htmlspecialchars($data['received_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $keyword = htmlspecialchars($data['keyword'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
📥 Inbound SMS Details
💬 Message Content
$message
HTML; } /** * Build content for SMS Opt Out event */ function buildOptOutContent($data) { $phoneNumber = htmlspecialchars($data['phone_number'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $optOutAt = htmlspecialchars($data['opt_out_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $keyword = htmlspecialchars($data['keyword'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); return <<
🚫 Opt Out Details
HTML; } /** * Build content for Contacts Callback event */ function buildContactsCallbackContent($data) { $contactId = htmlspecialchars($data['contact_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $phoneNumber = htmlspecialchars($data['phone_number'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $firstName = htmlspecialchars($data['first_name'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $lastName = htmlspecialchars($data['last_name'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $email = htmlspecialchars($data['email'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $status = htmlspecialchars($data['status'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $createdAt = htmlspecialchars($data['created_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $updatedAt = htmlspecialchars($data['updated_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); $customFieldsHtml = ''; if (isset($data['custom_fields']) && is_array($data['custom_fields']) && !empty($data['custom_fields'])) { $customFieldsHtml = '
🏷️ Custom Fields
'; foreach ($data['custom_fields'] as $key => $value) { $keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); $valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); $customFieldsHtml .= <<
HTML; } $customFieldsHtml .= '
'; } return <<
👤 Contact Details
$customFieldsHtml HTML; } // Build email headers to ensure proper HTML rendering and sender information // MIME-Version declares email format capabilities $emailHeaders = "MIME-Version: 1.0\r\n"; // Content-type specifies HTML email with UTF-8 character encoding $emailHeaders .= "Content-type: text/html; charset=UTF-8\r\n"; // From header sets the sender address to a no-reply address at wh.dixon.cx $emailHeaders .= "From: no-reply@wh.dixon.cx\r\n"; // Send the email using PHP's built-in mail function // Parameters: recipient, subject, message body, headers $mailSent = mail($email, $subject, $html, $emailHeaders); // Check if the email was sent successfully if (!$mailSent) { // Log error or handle failure (note: mail() return value is unreliable on some systems) error_log("Failed to send webhook email to: $email"); } // Set the Content-Type header for the HTTP response to plain text header('Content-Type: text/plain; charset=UTF-8'); // Return a simple acknowledgement to the webhook sender // This confirms the webhook was received and processed echo "Webhook received and forwarded to: $email\n\n"; // Include a sanitised copy of the REQUEST data in the response for debugging echo "Request data:\n"; print_r($_REQUEST);