Update README for JSON response, all four parsers, and email redesign
This commit is contained in:
@@ -5,7 +5,8 @@ A simple, elegant PHP solution for debugging webhooks by forwarding them directl
|
|||||||
## 🌟 Features
|
## 🌟 Features
|
||||||
|
|
||||||
- **Email Delivery**: Receive complete webhook data directly in your inbox
|
- **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
|
- **Secure**: Built-in email validation and XSS protection
|
||||||
- **Instant**: Real-time webhook processing and forwarding
|
- **Instant**: Real-time webhook processing and forwarding
|
||||||
- **Simple**: No database or complex setup required
|
- **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:
|
Replace `@` with `%40` in your email address:
|
||||||
- `test@example.com` becomes `test%40example.com`
|
- `test@example.com` becomes `test%40example.com`
|
||||||
|
|
||||||
|
The landing page has a URL builder that does this for you.
|
||||||
|
|
||||||
### 2. Configure Webhook
|
### 2. Configure Webhook
|
||||||
Use one of the following URL formats as your webhook endpoint:
|
Use one of the following URL formats as your webhook endpoint:
|
||||||
|
|
||||||
@@ -46,12 +49,55 @@ Receive webhook data instantly in your email!
|
|||||||
**With parser:**
|
**With parser:**
|
||||||
```
|
```
|
||||||
📤 GitHub push - username/repo
|
📤 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
|
## 🎨 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
|
### Available Parsers
|
||||||
|
|
||||||
@@ -59,23 +105,45 @@ Parsers format webhooks from specific services into beautiful, readable emails.
|
|||||||
Formats GitHub webhook events with full event details.
|
Formats GitHub webhook events with full event details.
|
||||||
|
|
||||||
**Supported events:**
|
**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
|
- Pull requests
|
||||||
- Issues
|
- Issues
|
||||||
- Releases
|
- Releases
|
||||||
- And more...
|
- Everything else in a clean generic layout
|
||||||
|
|
||||||
**Example URL:**
|
**Example URL:**
|
||||||
```
|
```
|
||||||
https://your-domain.com/test%40example.com/github
|
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:**
|
**What you get:**
|
||||||
- Event-specific formatting
|
- Firing or resolved status in the subject and header
|
||||||
- Commit details with author info
|
- Group labels, common labels, and common annotations
|
||||||
- Pull request/issue metadata
|
- Each alert as its own card with labels, annotations, start and end times, and a link to the generator
|
||||||
- Clean, scannable layout
|
|
||||||
- Relevant emojis for quick identification
|
**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`)
|
#### Webex Interact (`/wxinteract`)
|
||||||
Formats Webex Interact SMS API webhook events into clean, readable emails.
|
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:**
|
**What you get:**
|
||||||
- Event-specific formatting with relevant emojis
|
- 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
|
- Contact and campaign information
|
||||||
- Error details for failed messages
|
- Error details for failed messages
|
||||||
- Click tracking details for shortlinks
|
- 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`
|
**Test samples available in:** `wxinteract_test_samples.json`
|
||||||
|
|
||||||
|
See also the [Webex Interact Guide](WEBEX_INTERACT_GUIDE.md).
|
||||||
|
|
||||||
### Creating Your Own Parser
|
### 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
|
## 💡 Usage Examples
|
||||||
|
|
||||||
### Standard Webhook (Raw Data)
|
|
||||||
|
|
||||||
### cURL - POST Request with JSON
|
### cURL - POST Request with JSON
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://your-domain.com/test%40example.com \
|
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
|
### cURL - GET Request with Parameters
|
||||||
```bash
|
```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 API
|
||||||
```javascript
|
```javascript
|
||||||
fetch('https://your-domain.com/test%40example.com', {
|
fetch('https://your-domain.com/' + encodeURIComponent('test@example.com'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify({ event: 'order.completed', order_id: 'ORD-789', total: 99.99 })
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
event: 'order.completed',
|
|
||||||
order_id: 'ORD-789',
|
|
||||||
total: 99.99
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Python - Requests Library
|
### Python - Requests Library
|
||||||
```python
|
```python
|
||||||
import requests
|
import requests
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
response = requests.post(
|
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},
|
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
|
### With GitHub Parser
|
||||||
@@ -172,9 +235,11 @@ Commits: 3
|
|||||||
Pusher: developerUsername
|
Pusher: developerUsername
|
||||||
|
|
||||||
Commits:
|
Commits:
|
||||||
abc1234 Fix bug in authentication by John Doe
|
abc1234 John Doe
|
||||||
def5678 Update dependencies by Jane Smith
|
Fix bug in authentication
|
||||||
ghi9012 Add new feature by Bob Johnson
|
|
||||||
|
def5678 Jane Smith
|
||||||
|
Update dependencies
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pull Request:**
|
**Pull Request:**
|
||||||
@@ -187,6 +252,29 @@ State: open
|
|||||||
Branch: feature-auth → main
|
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
|
### With Webex Interact Parser
|
||||||
|
|
||||||
Configure your Webex Interact API callback URL with:
|
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
|
https://your-domain.com/yourname%40company.com/wxinteract
|
||||||
```
|
```
|
||||||
|
|
||||||
When SMS events occur, you'll receive formatted emails like:
|
|
||||||
|
|
||||||
**Outbound SMS - Submitted:**
|
**Outbound SMS - Submitted:**
|
||||||
```
|
```
|
||||||
📤 Webex Interact: Outbound SMS - Submitted
|
📤 Webex Interact: Outbound SMS - Submitted
|
||||||
@@ -210,26 +296,6 @@ Message Content:
|
|||||||
Hello! Your appointment is confirmed for tomorrow at 2 PM.
|
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
|
## 📦 Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
@@ -245,25 +311,27 @@ git clone https://github.com/andydixon/webhook.git
|
|||||||
|
|
||||||
## 🔧 Requirements
|
## 🔧 Requirements
|
||||||
|
|
||||||
- PHP 7.0 or higher
|
- PHP 8.0 or higher
|
||||||
- Web server (Apache, Nginx, etc.)
|
- Web server (Apache, Nginx, etc.)
|
||||||
- PHP `mail()` function enabled
|
- PHP `mail()` function enabled
|
||||||
- Outbound SMTP/email capability
|
- Outbound SMTP/email capability
|
||||||
|
|
||||||
## 📧 What You Receive
|
## 📧 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 Timestamp**: Exact date and time when the webhook was received
|
||||||
- **Request Information**: IP address, HTTP method, and Content-Type
|
- **Request Information**: IP address, HTTP method, and Content-Type
|
||||||
- **Request Headers**: All HTTP headers sent with the request
|
- **Request Headers**: All HTTP headers sent with the request
|
||||||
- **Request Body**: Complete raw body content (JSON, XML, form data, etc.)
|
- **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
|
## 🔒 Security Features
|
||||||
|
|
||||||
- **Email Validation**: Only valid email addresses are accepted
|
- **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
|
- **No Data Storage**: Webhook data is forwarded immediately and not stored
|
||||||
- **Error Logging**: Failed email attempts are logged for monitoring
|
- **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
|
- **Debugging**: See exactly what data third-party services are sending
|
||||||
- **GitHub Notifications**: Get beautifully formatted emails for pushes, PRs, and issues
|
- **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
|
- **Integration Testing**: Test webhook integrations before implementing full handlers
|
||||||
- **Monitoring**: Monitor webhook activity and payload changes over time
|
- **Monitoring**: Monitor webhook activity and payload changes over time
|
||||||
- **Documentation**: Capture real examples for API documentation
|
- **Documentation**: Capture real examples for API documentation
|
||||||
@@ -296,22 +365,29 @@ Each webhook email contains:
|
|||||||
|
|
||||||
## 💡 Tips & Best Practices
|
## 💡 Tips & Best Practices
|
||||||
|
|
||||||
- **Use Parsers**: Use `/github`, `/stripe`, etc. for prettier emails from supported services
|
- **Use Parsers**: Use `/github`, `/grafana`, `/json`, or `/wxinteract` for prettier emails
|
||||||
- **Email Filtering**: Create email filters based on subject patterns ("📤 GitHub", "💳 Stripe") to organize webhooks
|
- **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
|
- **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
|
- **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)
|
- **Create Parsers**: Build custom parsers for your favorite services - see [Parser Guide](../PARSER_PROMPT.md)
|
||||||
|
|
||||||
## 📝 Error Handling
|
## 📝 Error Handling
|
||||||
|
|
||||||
### Invalid Email Address
|
### Invalid Email Address
|
||||||
If the email address in the URL is invalid or missing:
|
If the email address in the URL is invalid or missing, you get a `400 Bad Request` with a JSON body:
|
||||||
```
|
```json
|
||||||
HTTP 400 Bad Request
|
{
|
||||||
Invalid email address provided in URL path.
|
"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
|
## 📄 License
|
||||||
|
|
||||||
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
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
|
## 📜 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
|
- **2.0.0** - Parser System Release
|
||||||
- ✨ **NEW**: Custom parser support for formatted webhooks
|
- ✨ **NEW**: Custom parser support for formatted webhooks
|
||||||
- ✨ **NEW**: GitHub webhook parser with full event support
|
- ✨ **NEW**: GitHub webhook parser with full event support
|
||||||
|
- ✨ **NEW**: Webex Interact SMS parser
|
||||||
- ✨ **NEW**: Parser development guide (PARSER_PROMPT.md)
|
- ✨ **NEW**: Parser development guide (PARSER_PROMPT.md)
|
||||||
- Backward compatible with existing webhook URLs
|
- Backward compatible with existing webhook URLs
|
||||||
- Same security and styling standards
|
|
||||||
|
|
||||||
- **1.0.0** - Initial release
|
- **1.0.0** - Initial release
|
||||||
- Basic webhook forwarding functionality
|
- Basic webhook forwarding functionality
|
||||||
|
|||||||
Reference in New Issue
Block a user