diff --git a/docs.htm b/docs.htm index a5741d8..273137c 100644 --- a/docs.htm +++ b/docs.htm @@ -1,587 +1,293 @@ - - - Webhook Email Forwarder - Documentation - - - + + +Webhook → Email | <?php echo $_SERVER['HTTP_HOST']; ?> + + -
- -
-

Webhook Email Forwarder

-

A simple, elegant solution for debugging webhooks by forwarding them directly to your email

-

Version 1.0 | Made with ❤️ by Andy Dixon

-
+
- -
-

Overview

-

This webhook service captures incoming HTTP requests and forwards complete request details (headers, body, metadata) to your specified email address.

- -
-
-
-
- -
Email Delivery
-

Receive webhook data directly in your inbox

-
-
-
-
-
-
- -
Secure
-

Email validation and XSS protection built-in

-
-
-
-
-
-
- -
Instant
-

Real-time webhook processing and forwarding

-
-
-
-
-
+ - -
-

How It Works

-
    -
  1. You configure a third-party service to send webhooks to this URL with your email encoded in the path
  2. -
  3. When a webhook request arrives, the service captures all request data
  4. -
  5. The complete request details are formatted into a readable HTML email
  6. -
  7. The email is sent to your specified address
  8. -
  9. A confirmation response is returned to the webhook sender
  10. -
+
+
// no signup. no dashboard. no, seriously.
+

Webhooks.
Straight to your inbox.

+

Put your email in the URL. Point a webhook at it. Every header, every byte of body, every superglobal shows up as an email. Done. Go do the next thing.

-
- Perfect for: Debugging webhooks, testing integrations, monitoring API callbacks, and understanding third-party service payloads. -
-
+
+ + +
+ https:///you%40example.com + +
+

Yes the @ becomes %40. Yes that's the whole trick. Add /github, /json, etc. on the end for the fancy parsers — more on that below, if you scroll that far.

+
+
- -
-

Usage

- -

Basic URL Format

-

The URL format is simple - just append your URL-encoded email address to the domain:

-
-https:///{email-address} -
+
+

Three steps. Two, really.

+

The third one is just "wait for an email", which you were going to do anyway.

+
+
Encode your email@ → %40. The box above already did it for you. Ok moving on.
+
Paste the URL into whateverStripe, GitHub, Grafana, that cron job, a curl you're about to forget you wrote.
+
Check your inboxFull request, nicely formatted. Subject starts with ‼️ so it's filterable. You're welcome.
+
-

URL Encoding Your Email

-

The @ symbol must be URL-encoded as %40:

- -
-
-
-
- Original Email -
-
- john@example.com -
-
-
-
-
-
- Webhook URL -
-
- /john%40example.com -
-
-
-
+

The fastest possible test

+
curl -X POST https:///you%40example.com \
+  -d '{"test": "hello, is this thing on"}'
+

(the email will arrive before you've alt-tabbed back. probably. depends on your mail server. anyway.)

+
-

Example Requests

- -

cURL - POST Request with JSON

-
-curl -X POST https:///test%40example.com \ +
+

Examples, because reading is hard

+

Any method, any content type. GET, POST, PUT, PATCH, DELETE, something you made up. JSON, form data, multipart, XML, plain text, a string of emoji. It all gets forwarded.

+
+
# JSON
+curl -X POST https:///you%40example.com \
   -H "Content-Type: application/json" \
   -d '{"event": "user.created", "user_id": 12345}'
-            
-

cURL - GET Request with Parameters

-
-curl https:///test%40example.com?event=test&id=123 -
+# GET with query string (yes, this works too) +curl "https:///you%40example.com?event=test&id=123" -

JavaScript - Fetch API

-
-fetch('https:///test%40example.com', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - event: 'order.completed', - order_id: 'ORD-789', - total: 99.99 - }) -}); -
+
fetch('https:///' + encodeURIComponent('you@example.com'), {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json' },
+  body: JSON.stringify({ event: 'order.completed', order_id: 'ORD-789', total: 99.99 })
+});
-

Python - Requests Library

-
-import requests +
import requests
+from urllib.parse import quote
 
-response = requests.post(
-    'https:///test%40example.com',
-    json={'event': 'payment.received', 'amount': 250.00},
-    headers={'X-Custom-Header': 'MyValue'}
-)
-print(response.text)
-            
+requests.post( + f'https:///{quote("you@example.com")}', + json={'event': 'payment.received', 'amount': 250.00}, + headers={'X-Custom-Header': 'MyValue'}, +) +
+
+

Pro tip you'll ignore and then come back for: use plus addressing. you+stripe%40example.com, you+github%40example.com. Now your inbox filters do the sorting so you don't have to.

+
+ -
- Important: Make sure to URL-encode your email address. Most programming languages and tools have built-in functions for this (e.g., encodeURIComponent() in JavaScript, urllib.parse.quote() in Python). -
-
+
+

Parsers. Ooh, shiny.

+

Add one word to the end of the URL and the email stops being a wall of raw JSON and starts being something you can actually read at 3am. Parser doesn't understand the payload? It falls back to the raw format. Nothing gets lost.

+
+
🐙
/github

Push, pull request, issue and release events get their own layout with commits, authors, branches and links. Everything else still renders, just less fancy. Subjects like 📤 GitHub push - you/repo.

+
📈
/grafana

Grafana and Prometheus Alertmanager alerts. Firing, resolved, labels, annotations. Finally know which thing is on fire.

+
🧩
/json

Any JSON at all, rendered as tables. Nested objects get split out into their own tables with path-based titles so you can find data.items[3].sku without squinting.

+
💬
/wxinteract

Webex Interact SMS events. Inbound messages, delivery receipts, the lot, with the event type in the subject line.

+
- -
-

What to Expect

- -

Immediate HTTP Response

-

When your webhook is successfully received, you'll get an immediate response:

-
-
- HTTP 200 OK -
-
-
-Webhook received and forwarded to: test@example.com +

Setting up GitHub, since that's the one everyone asks about

+
    +
  1. Repo → Settings → Webhooks → Add webhook
  2. +
  3. Payload URL: https:///you%40example.com/github
  4. +
  5. Content type: application/json
  6. +
  7. Pick events (or "Send me everything", live a little)
  8. +
  9. Save. Push something. Refresh inbox. Refresh again. There it is.
  10. +
+
-Request data: -Array -( - [event] => test - [id] => 123 -) -
-
- +
+

What actually lands in your inbox

+

One HTML email per request. Gmail-safe styling. All of the following, every time, whether you asked for it or not:

+ +

And the sender gets

+
{
+    "ok": true,
+    "message": "Webhook received and forwarded to you@example.com",
+    "forwarded_to": "you@example.com",
+    "parser": "default",
+    "subject": "‼️ Webhook Request Received - 2026-09-11 17:15:42",
+    "received_at": "2026-09-11T17:15:42+00:00",
+    "request": { "method": "POST", "content_type": "application/json", "ip": "203.0.113.7", "headers": 9, "body_bytes": 40 },
+    "data": {
+        "query": { "src": "curl" },
+        "form": {},
+        "json": { "event": "user.created", "user_id": 12345 },
+        "files": []
+    }
+}
+

JSON bodies get decoded and echoed back under data.json, so you can eyeball what the server actually understood. Form fields land in form, query string in query. No print_r in sight.

+

Any 200 is enough for most services to mark the delivery as "succeeded" and stop retrying. Which is the point.

+
-

Email Contents

-

Within moments, you'll receive a styled HTML email containing:

- -
-
-
-
-
Request Timestamp
-

Exact date and time when the webhook was received

-
-
-
-
-
-
-
Request Information
-

IP address, HTTP method (GET/POST/etc), and Content-Type

-
-
-
-
-
-
-
Request Headers
-

All HTTP headers sent with the request

-
-
-
-
-
-
-
Request Body
-

Complete raw body content (JSON, XML, form data, etc.)

-
-
-
-
-
-
-
PHP Variables
-

Parsed GET, POST, REQUEST, and FILES superglobals for easy access

-
-
-
-
+
+

Caveats, small print, that sort of thing

+
+

Anyone with the URL can email you. There's no auth. There's no secret. It's your email address in a URL. Use it for debugging and testing. Do not point production payment data at it. Don't make me put a bigger warning here.

+
+

Stuff that's true

+ +

Good for

+
+ debugging a webhook you didn't write + seeing what Stripe actually sends + capturing real payloads for docs + testing before the handler exists + "is it even firing?" + alerts in your inbox without a whole thing +
+
-
- Subject Line: Emails arrive with the subject "‼️ Webhook Request Received - [timestamp]" for easy filtering and identification. -
- + - -
-

Error Handling

- -

Invalid Email Address

-

If the email address in the URL is invalid or missing, you'll receive:

-
-
- HTTP 400 Bad Request -
-
-
-Invalid email address provided in URL path. -
-
-
+
-
- Validation: The service validates email addresses using PHP's built-in FILTER_VALIDATE_EMAIL filter to ensure proper formatting. -
- - - -
-

Security Features

- - -
- Privacy Note: Anyone with your webhook URL can send data to your email address. Only use this service for testing and debugging purposes. Do not use for sensitive production data without additional authentication. -
-
- - -
-

Supported Features

- -
-
-
HTTP Methods
-
    -
  • GET
  • -
  • POST
  • -
  • PUT
  • -
  • PATCH
  • -
  • DELETE
  • -
  • Any custom method
  • -
-
-
-
Content Types
-
    -
  • application/json
  • -
  • application/x-www-form-urlencoded
  • -
  • multipart/form-data
  • -
  • text/plain
  • -
  • text/xml
  • -
  • Any custom content type
  • -
-
-
-
- - -
-

GitHub Webhook Parser

-

Get beautifully formatted emails specifically designed for GitHub webhooks with enhanced event details.

- -

How to Use

-

Simply add /github to the end of your webhook URL:

-
-https:///{email-address}/github -
- -

URL Format Examples

-
-
-
-
- Standard Webhook -
-
- /john%40example.com -
-
-
-
-
-
- GitHub Webhook -
-
- /john%40example.com/github -
-
-
-
- -

Supported GitHub Events

-

The GitHub parser provides enhanced formatting for these event types:

- -
-
-
-
-
📤 Push Events
-

Displays branch, commit count, and individual commit messages with authors.

-
-
-
-
-
-
-
🔀 Pull Request Events
-

Shows PR number, title, state, branches, and direct link to the PR.

-
-
-
-
-
-
-
📝 Issue Events
-

Displays issue number, title, state, and link to the issue.

-
-
-
-
-
-
-
🚀 Release Events
-

Shows release tag, name, and direct link to the release page.

-
-
-
-
- -
- All Events Supported: Even if an event isn't specifically formatted above, the parser will display it in a clean, structured format showing all webhook data. -
- -

Setting Up in GitHub

-

To configure a GitHub webhook:

-
    -
  1. Go to your repository settings → Webhooks → Add webhook
  2. -
  3. Set the Payload URL to: https:///your%40email.com/github
  4. -
  5. Set Content type to: application/json
  6. -
  7. Choose which events to receive (or select "Send me everything")
  8. -
  9. Click "Add webhook"
  10. -
- -
- Subject Line: GitHub webhook emails arrive with event-specific subject lines like "📤 GitHub push - username/repo" or "🔀 GitHub pull_request (opened) - username/repo" for easy filtering. -
- -
- Tip: Use email plus addressing like you+github%40example.com to easily filter GitHub webhooks in your inbox. -
-
- - -
-

Common Use Cases

- -
-
-
-
-
Debugging
-

See exactly what data third-party services are sending to your webhooks

-
-
-
-
-
-
-
Integration Testing
-

Test webhook integrations before implementing full handlers

-
-
-
-
-
-
-
Monitoring
-

Monitor webhook activity and payload changes over time

-
-
-
-
-
-
-
Documentation
-

Capture real examples for API documentation

-
-
-
-
-
-
-
Development
-

Quickly test webhook flows during development

-
-
-
-
-
-
-
Troubleshooting
-

Diagnose issues with webhook payloads and headers

-
-
-
-
-
- - -
-

Tips & Best Practices

- -
-
Email Filtering
-

Create an email filter for the subject line "‼️ Webhook Request Received" to organize incoming webhooks in your inbox.

-
- -
-
Use Plus Addressing
-

Use email plus addressing (e.g., yourname+github%40gmail.com) to track which services are sending webhooks.

-
- -
-
Timestamp in Emails
-

Check the "Date Received" field in emails to verify webhook timing and debug delivery delays.

-
- -
-
Parse the Body
-

The raw body section contains the exact payload - perfect for copying into your code for testing.

-
-
- - -
-

Quick Start

-

Get started in 3 simple steps:

- -
-
-
-
-
1
-
Encode Your Email
-

Replace @ with %40 in your email address

-
-
-
-
-
-
-
2
-
Configure Webhook
-

Use /{your-email} as the webhook URL

-
-
-
-
-
-
-
3
-
Check Your Inbox
-

Receive detailed webhook data instantly in your email

-
-
-
-
- -
-
Try it now!
-
-curl -X POST https:///YOUR-EMAIL-HERE \ - -d '{"test": "Hello World!"}' -
-
-
- - -
-

Built with PHP and ADHD medication

-

© 2025-2026 Andy Dixon |

-
- - - + diff --git a/index.php b/index.php index ea801e2..1acf153 100755 --- a/index.php +++ b/index.php @@ -1,324 +1,204 @@ -/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 Variables' => $_GET, - 'POST Variables' => $_POST, - 'Raw Request Body' => $_REQUEST, - 'Uploaded 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'); - -// Load parser helpers -require_once __DIR__ . '/parsers/helpers.php'; - -// Check if a parser is specified and load it -$html = ''; -$subject = ''; -if ($parserName) { - $parserFile = __DIR__ . '/parsers/' . $parserName . '.php'; - if (file_exists($parserFile)) { - require_once $parserFile; - $parserFunction = $parserName . 'Parse'; - - if (function_exists($parserFunction)) { - $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"; -} - -// 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"; +/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); + header('Content-Type: application/json; charset=UTF-8'); + echo json_encode([ + 'ok' => false, + 'error' => 'invalid_email', + 'message' => 'Invalid email address provided in URL path.', + 'hint' => 'Encode @ as %40, e.g. /you%40example.com', + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), "\n"; + 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 Variables' => $_GET, + 'POST Variables' => $_POST, + 'Raw Request Body' => $_REQUEST, + 'Uploaded 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 = json_encode($variables, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + +// 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'); + +// Load parser helpers +require_once __DIR__ . '/parsers/helpers.php'; + +// Check if a parser is specified and load it +$html = ''; +$subject = ''; +if ($parserName) { + $parserFile = __DIR__ . '/parsers/' . $parserName . '.php'; + if (file_exists($parserFile)) { + require_once $parserFile; + $parserFunction = $parserName . 'Parse'; + + if (function_exists($parserFunction)) { + $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 + $emailTitle = "⚡ Webhook Request Received"; + $emailStamp = "$dateReceivedSafe"; + $body = << +
+
📋 Request Information
+
+ + + +
+
+ + +
+
📨 Request Headers
+
$headersText
+
+ + +
+
📦 Request Body
+
$rawBodySafe
+
+ + +
+
🔧 Parsed Variables
+
$variablesTextSafe
+
+ + +HTML; + $html = emailShell($emailTitle, $emailStamp, $body, "This webhook was automatically forwarded to your email address."); + + // Construct the email subject line with an attention-grabbing emoji and timestamp + $subject = "‼️ Webhook Request Received - $dateReceived"; +} + +// 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"; // Keep both the visible sender and SMTP envelope sender on the public domain. $envelopeSender = 'no-reply@dixon.cx'; $emailHeaders .= "From: Webhook Call <$envelopeSender>\r\n"; @@ -326,20 +206,37 @@ $emailHeaders .= "From: Webhook Call <$envelopeSender>\r\n"; // Send the email using PHP's built-in mail function // The -f option prevents sendmail from deriving www-data@. $mailSent = mail($email, $subject, $html, $emailHeaders, '-f' . $envelopeSender); - -// 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); + +// 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"); +} + +// JSON acknowledgement back to the webhook sender +header('Content-Type: application/json; charset=UTF-8'); + +// Decode JSON bodies so the caller sees what we understood, not a raw string +$bodyJson = ($rawBody !== '' && str_contains($contentType, 'json')) ? json_decode($rawBody, true) : null; + +echo json_encode([ + 'ok' => true, + 'message' => "Webhook received and forwarded to $email", + 'forwarded_to' => $email, + 'parser' => $parserName ?: 'default', + 'subject' => $subject, + 'received_at' => date('c'), + 'request' => [ + 'method' => $requestMethod, + 'content_type' => $contentType, + 'ip' => $ipAddress, + 'headers' => count($headersArray), + 'body_bytes' => strlen($rawBody), + ], + 'data' => [ + 'query' => (object) $_GET, + 'form' => (object) $_POST, + 'json' => $bodyJson, + 'files' => array_keys($_FILES), + ], +], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), "\n"; diff --git a/parsers/github.php b/parsers/github.php index 146ccfc..a112127 100644 --- a/parsers/github.php +++ b/parsers/github.php @@ -106,7 +106,7 @@ HTML;
- +
@@ -139,7 +139,7 @@ HTML;
- +
@@ -167,7 +167,7 @@ HTML;
- +
@@ -183,177 +183,9 @@ HTML; $actionDisplay = $actionSafe ? " ($actionSafe)" : ''; - $html = << - - - - - GitHub Webhook - $eventSafe - - - -
-
-

$eventIcon GitHub: $eventSafe$actionDisplay

-

$dateSafe

-
+ $emailTitle = "$eventIcon GitHub: $eventSafe$actionDisplay"; + $emailStamp = "$dateSafe"; + $body = <<
📋 Repository Information
@@ -378,14 +210,8 @@ HTML;
$eventContent - - - - - HTML; + $html = emailShell($emailTitle, $emailStamp, $body, "This GitHub webhook was automatically forwarded to your email address."); $subject = "$eventIcon GitHub $eventSafe$actionDisplay - $repoSafe"; diff --git a/parsers/grafana.php b/parsers/grafana.php index 5e1e047..f39f543 100644 --- a/parsers/grafana.php +++ b/parsers/grafana.php @@ -76,7 +76,7 @@ HTML; $alertSummary .= <<
Alertmanager URL
-
$externalURLSafe
+
$externalURLSafe
HTML; } @@ -233,7 +233,7 @@ HTML; $alertsContent .= <<
Generator URL
-
$generatorURLSafe
+
$generatorURLSafe
HTML; } @@ -254,132 +254,9 @@ HTML; $alertsContent .= ''; } - $html = << - - - - - Grafana Alert - $statusSafe - - - -
-
-

$statusIcon Grafana Alert: $statusSafe

-

$dateSafe

-
+ $emailTitle = "$statusIcon Grafana Alert: $statusSafe"; + $emailStamp = "$dateSafe"; + $body = <<
- - - - - HTML; + $html = emailShell($emailTitle, $emailStamp, $body, "This Grafana/Prometheus alert was automatically forwarded to your email address."); // Create subject line $alertCountText = $alertCount === 1 ? '1 alert' : "$alertCount alerts"; diff --git a/parsers/helpers.php b/parsers/helpers.php index 23c5a06..883e1de 100644 --- a/parsers/helpers.php +++ b/parsers/helpers.php @@ -89,3 +89,78 @@ function renderDataAsTable($data, $depth = 0) { return $html; } + +/** + * Shared email chrome: one stylesheet, one header, one footer, for every template. + * + * @param string $title Header text (already HTML-safe) + * @param string $stamp Timestamp line (already HTML-safe) + * @param string $body Inner HTML made of .section / .metadata / .data-box blocks + * @param string $footer Footer sentence + * @return string Complete HTML document + */ +function emailShell($title, $stamp, $body, $footer = 'Automatically forwarded to your inbox.') { + $host = htmlspecialchars($_SERVER['HTTP_HOST'] ?? 'webhook', ENT_QUOTES, 'UTF-8'); + $plainTitle = strip_tags($title); + return << + + + + + +$plainTitle + + + +
+
+
+
+

$title

+

$stamp

+
+$body + +
+
+ + +HTML; +} diff --git a/parsers/json.php b/parsers/json.php index c10b1d9..54959e7 100644 --- a/parsers/json.php +++ b/parsers/json.php @@ -22,132 +22,9 @@ function jsonParse($rawBody, $headers, $metadata) { $contentTypeSafe = htmlspecialchars($metadata['contentType'], ENT_QUOTES, 'UTF-8'); // Start building HTML - $html = << - - - - - JSON Webhook Data - - - -
-
-

📊 JSON Webhook Data

-

$dateSafe

-
+ $emailTitle = "📊 JSON Webhook Data"; + $emailStamp = "$dateSafe"; + $body = <<
📋 Request Information
@@ -173,18 +50,12 @@ function jsonParse($rawBody, $headers, $metadata) { HTML; // Process the JSON data - $html .= renderJsonData($payload); + $body .= renderJsonData($payload); - $html .= << - - -
- - HTML; + $html = emailShell($emailTitle, $emailStamp, $body, "JSON data automatically parsed and formatted."); $subject = "📊 JSON Webhook - " . $metadata['date']; diff --git a/parsers/wxinteract.php b/parsers/wxinteract.php index 9107ccb..fdf4a1c 100644 --- a/parsers/wxinteract.php +++ b/parsers/wxinteract.php @@ -82,150 +82,9 @@ function wxinteractParse($rawBody, $headers, $metadata) { $eventTitleSafe = htmlspecialchars($eventTitle, ENT_QUOTES, 'UTF-8'); - $html = << - - - - - Webex Interact - $eventTitleSafe - - - -
-
-

$eventIcon Webex Interact: $eventTitleSafe

-

$dateSafe

-
+ $emailTitle = "$eventIcon Webex Interact: $eventTitleSafe"; + $emailStamp = "$dateSafe"; + $body = <<
📋 Event Information
@@ -242,14 +101,8 @@ function wxinteractParse($rawBody, $headers, $metadata) {
$eventContent - - - - - HTML; + $html = emailShell($emailTitle, $emailStamp, $body, "This Webex Interact webhook was automatically forwarded to your email address."); $subject = "$eventIcon Webex Interact: $eventTitle"; @@ -411,11 +264,11 @@ function buildShortlinkClickedContent($data) {