diff --git a/README.md b/README.md index 5aaec0a..a1b1e62 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ A simple, elegant PHP solution for debugging webhooks by forwarding them directl ## 🌟 Features - **Email Delivery**: Receive complete webhook data directly in your inbox -- **Custom Parsers**: Beautifully formatted emails for GitHub and other services +- **Custom Parsers**: Beautifully formatted emails for GitHub, Grafana, Webex Interact, and any JSON payload +- **JSON Response**: Every request gets a structured JSON acknowledgement, not a wall of `print_r` - **Secure**: Built-in email validation and XSS protection - **Instant**: Real-time webhook processing and forwarding - **Simple**: No database or complex setup required @@ -22,6 +23,8 @@ This webhook service captures incoming HTTP requests and forwards complete reque Replace `@` with `%40` in your email address: - `test@example.com` becomes `test%40example.com` +The landing page has a URL builder that does this for you. + ### 2. Configure Webhook Use one of the following URL formats as your webhook endpoint: @@ -46,12 +49,55 @@ Receive webhook data instantly in your email! **With parser:** ``` 📤 GitHub push - username/repo -💳 Stripe payment.succeeded - $99.99 +🚨 Grafana Alert: firing - 2 alerts +📊 JSON Webhook - [timestamp] +📥 Webex Interact: Inbound SMS Received ``` +## 📬 The Response + +Every request is answered with `application/json`, pretty-printed. JSON bodies are decoded and echoed back under `data.json` so you can see exactly what the server understood. + +```json +{ + "ok": true, + "message": "Webhook received and forwarded to test@example.com", + "forwarded_to": "test@example.com", + "parser": "default", + "subject": "‼️ Webhook Request Received - 2026-09-12 17:18:02", + "received_at": "2026-09-12T17:18:02+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": [] + } +} +``` + +| Field | Meaning | +|-------|---------| +| `ok` | `true` on success, `false` on error | +| `parser` | The parser used, or `default` | +| `subject` | The subject line of the email that was sent | +| `request.headers` | Number of headers received | +| `request.body_bytes` | Raw body length | +| `data.query` / `data.form` | Query string and form fields (always objects, never `[]`) | +| `data.json` | Decoded body when the Content-Type is JSON, otherwise `null` | +| `data.files` | Names of uploaded file fields | + +Any 2xx is enough for most services to mark the delivery as succeeded and stop retrying. + ## 🎨 Webhook Parsers -Parsers format webhooks from specific services into beautiful, readable emails. +Parsers format webhooks from specific services into beautiful, readable emails. Add the parser name to the end of the URL. If a parser can't understand the payload, the email falls back to the standard raw format so nothing is lost. ### Available Parsers @@ -59,23 +105,45 @@ Parsers format webhooks from specific services into beautiful, readable emails. Formats GitHub webhook events with full event details. **Supported events:** -- Push events (with commit details) +- Push events (each commit as a card with a linked sha, author, and full multi-line message) - Pull requests - Issues - Releases -- And more... +- Everything else in a clean generic layout **Example URL:** ``` https://your-domain.com/test%40example.com/github ``` +Header names are matched case-insensitively, so deliveries over HTTP/2 (where headers arrive lowercase) work correctly. + +#### Grafana / Prometheus Alertmanager (`/grafana`) +Formats Alertmanager webhook payloads from Grafana or Prometheus. + **What you get:** -- Event-specific formatting -- Commit details with author info -- Pull request/issue metadata -- Clean, scannable layout -- Relevant emojis for quick identification +- Firing or resolved status in the subject and header +- Group labels, common labels, and common annotations +- Each alert as its own card with labels, annotations, start and end times, and a link to the generator + +**Example URL:** +``` +https://your-domain.com/test%40example.com/grafana +``` + +#### JSON (`/json`) +Renders any JSON body as tables. Nested objects are split into separate tables with path-based breadcrumbs (for example `order->items->[0]->tags`), so you can find a deeply nested value without squinting at raw JSON. + +**Handles:** +- Objects and arrays of objects +- Arrays of plain values, as one table under their breadcrumb +- Scalar bodies (a bare string, number, boolean, or `null`) +- Multi-line string values, with line breaks preserved + +**Example URL:** +``` +https://your-domain.com/test%40example.com/json +``` #### Webex Interact (`/wxinteract`) Formats Webex Interact SMS API webhook events into clean, readable emails. @@ -96,7 +164,7 @@ https://your-domain.com/test%40example.com/wxinteract **What you get:** - Event-specific formatting with relevant emojis -- Message content displayed in a clean message box +- Message content displayed in a message box, line breaks intact - Contact and campaign information - Error details for failed messages - Click tracking details for shortlinks @@ -104,16 +172,16 @@ https://your-domain.com/test%40example.com/wxinteract **Test samples available in:** `wxinteract_test_samples.json` +See also the [Webex Interact Guide](WEBEX_INTERACT_GUIDE.md). + ### Creating Your Own Parser -Want to add support for Stripe, GitLab, or other services? Check out the [**Parser Development Guide**](../PARSER_PROMPT.md) for detailed instructions on creating custom parsers. +A parser is one file in `parsers/` exposing `{name}Parse($rawBody, $headers, $metadata)` that returns `['html' => ..., 'subject' => ...]` or `false`. Build the body from `.section`, `table.metadata`, and `.data-box` blocks, escape free text with `esc()`, and wrap it with `emailShell()` from `parsers/helpers.php` so it picks up the shared design. See the [Parser Development Guide](../PARSER_PROMPT.md) for details. --- ## 💡 Usage Examples -### Standard Webhook (Raw Data) - ### cURL - POST Request with JSON ```bash curl -X POST https://your-domain.com/test%40example.com \ @@ -123,34 +191,29 @@ curl -X POST https://your-domain.com/test%40example.com \ ### cURL - GET Request with Parameters ```bash -curl https://your-domain.com/test%40example.com?event=test&id=123 +curl "https://your-domain.com/test%40example.com?event=test&id=123" ``` ### JavaScript - Fetch API ```javascript -fetch('https://your-domain.com/test%40example.com', { +fetch('https://your-domain.com/' + encodeURIComponent('test@example.com'), { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - event: 'order.completed', - order_id: 'ORD-789', - total: 99.99 - }) + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ event: 'order.completed', order_id: 'ORD-789', total: 99.99 }) }); ``` ### Python - Requests Library ```python import requests +from urllib.parse import quote response = requests.post( - 'https://your-domain.com/test%40example.com', + f'https://your-domain.com/{quote("test@example.com")}', json={'event': 'payment.received', 'amount': 250.00}, - headers={'X-Custom-Header': 'MyValue'} + headers={'X-Custom-Header': 'MyValue'}, ) -print(response.text) +print(response.json()) ``` ### With GitHub Parser @@ -172,9 +235,11 @@ Commits: 3 Pusher: developerUsername Commits: -abc1234 Fix bug in authentication by John Doe -def5678 Update dependencies by Jane Smith -ghi9012 Add new feature by Bob Johnson +abc1234 John Doe + Fix bug in authentication + +def5678 Jane Smith + Update dependencies ``` **Pull Request:** @@ -187,6 +252,29 @@ State: open Branch: feature-auth → main ``` +### With Grafana Parser + +Point a Grafana contact point or Alertmanager webhook receiver at: +``` +https://your-domain.com/yourname%40company.com/grafana +``` + +**Firing alert:** +``` +🚨 Grafana Alert: firing - 1 alert + +Status: 🔥 firing +Receiver: email +Alert Count: 1 + +Alert 1 - firing + alertname: HighCPU + instance: web-01 + severity: critical + summary: CPU over 90% for 5m + Started At: 2026-09-11T17:00:00Z +``` + ### With Webex Interact Parser Configure your Webex Interact API callback URL with: @@ -194,8 +282,6 @@ Configure your Webex Interact API callback URL with: https://your-domain.com/yourname%40company.com/wxinteract ``` -When SMS events occur, you'll receive formatted emails like: - **Outbound SMS - Submitted:** ``` 📤 Webex Interact: Outbound SMS - Submitted @@ -210,26 +296,6 @@ Message Content: Hello! Your appointment is confirmed for tomorrow at 2 PM. ``` -**SMS Opt Out:** -``` -🚫 Webex Interact: SMS Opt Out Received - -Phone Number: +61412345678 -Keyword: STOP -Opt Out At: 2026-01-22T18:15:00Z -``` - -**Shortlink Clicked:** -``` -🔗 Webex Interact: Shortlink Clicked - -Shortlink: https://wxint.co/abc123 -Original URL: https://example.com/special-offer -Phone Number: +61412345678 -Clicked At: 2026-01-22T17:30:45Z -IP Address: 203.0.113.45 -``` - ## 📦 Installation 1. Clone the repository: @@ -245,25 +311,27 @@ git clone https://github.com/andydixon/webhook.git ## 🔧 Requirements -- PHP 7.0 or higher +- PHP 8.0 or higher - Web server (Apache, Nginx, etc.) - PHP `mail()` function enabled - Outbound SMTP/email capability ## 📧 What You Receive -Each webhook email contains: +Every email shares one design: a gradient header with the event title and a timestamp pill, then a white card of sections. Metadata is rendered as real HTML tables so it lays out correctly in Gmail, Outlook, and Apple Mail. Code and raw payloads sit in dark code blocks. + +The standard (no parser) email contains: - **Request Timestamp**: Exact date and time when the webhook was received - **Request Information**: IP address, HTTP method, 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 +- **Parsed Variables**: GET, POST, REQUEST, and FILES superglobals as pretty-printed JSON ## 🔒 Security Features - **Email Validation**: Only valid email addresses are accepted -- **XSS Protection**: All output is sanitized using `htmlspecialchars()` +- **XSS Protection**: All output is escaped with `htmlspecialchars()`; free-text values go through `esc()`, which also preserves line breaks - **No Data Storage**: Webhook data is forwarded immediately and not stored - **Error Logging**: Failed email attempts are logged for monitoring @@ -287,6 +355,7 @@ Each webhook email contains: - **Debugging**: See exactly what data third-party services are sending - **GitHub Notifications**: Get beautifully formatted emails for pushes, PRs, and issues +- **Alerting**: Grafana and Prometheus alerts in your inbox without a whole notification stack - **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 @@ -296,22 +365,29 @@ Each webhook email contains: ## 💡 Tips & Best Practices -- **Use Parsers**: Use `/github`, `/stripe`, etc. for prettier emails from supported services -- **Email Filtering**: Create email filters based on subject patterns ("📤 GitHub", "💳 Stripe") to organize webhooks +- **Use Parsers**: Use `/github`, `/grafana`, `/json`, or `/wxinteract` for prettier emails +- **Email Filtering**: Create email filters based on subject patterns ("📤 GitHub", "🚨 Grafana") to organize webhooks - **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 to verify webhook timing and debug delivery delays +- **Timestamp in Emails**: Check the timestamp in the header to verify webhook timing and debug delivery delays - **Standard Format**: Use the standard format (no parser) when you need complete raw data for debugging - **Create Parsers**: Build custom parsers for your favorite services - see [Parser Guide](../PARSER_PROMPT.md) ## 📝 Error Handling ### Invalid Email Address -If the email address in the URL is invalid or missing: -``` -HTTP 400 Bad Request -Invalid email address provided in URL path. +If the email address in the URL is invalid or missing, you get a `400 Bad Request` with a JSON body: +```json +{ + "ok": false, + "error": "invalid_email", + "message": "Invalid email address provided in URL path.", + "hint": "Encode @ as %40, e.g. /you%40example.com" +} ``` +### Unknown Parser or Unparseable Payload +The request still succeeds. The email uses the standard raw format and the response reports the parser name you asked for. + ## 📄 License This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details. @@ -332,12 +408,21 @@ Give a ⭐️ if this project helped you! ## 📜 Version History +- **2.1.0** - JSON Response and Email Redesign + - ✨ **NEW**: Structured JSON response for every request, including errors + - ✨ **NEW**: Grafana / Prometheus Alertmanager parser (`/grafana`) + - ✨ **NEW**: Generic JSON parser (`/json`) with path breadcrumbs, scalar bodies, and arrays of plain values + - ✨ **NEW**: Redesigned landing page with a URL builder + - 🎨 One shared email design (`emailShell()`) across every template, built on real tables + - 🐛 Line breaks preserved in commit messages, SMS text, and JSON values + - 🐛 GitHub event headers matched case-insensitively (fixes "Unknown" events over HTTP/2) + - **2.0.0** - Parser System Release - ✨ **NEW**: Custom parser support for formatted webhooks - ✨ **NEW**: GitHub webhook parser with full event support + - ✨ **NEW**: Webex Interact SMS parser - ✨ **NEW**: Parser development guide (PARSER_PROMPT.md) - Backward compatible with existing webhook URLs - - Same security and styling standards - **1.0.0** - Initial release - Basic webhook forwarding functionality