PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
179: Building a Webhook Receiver and Dispatcher
I've seen this exact scenario play out in dozens of code reviews. A developer is integrating a service like Stripe or GitHub, they set up their endpoint, they trigger a test event from the dashboard, and then they stare at their logs in total confusion because the script is acting like it received absolutely nothing.
// The "Why isn't this working?" code
$event = $_POST['event'];
if ($event === 'order.completed') {
updateOrder($$_POST['order_id']);
echo "Order updated!";
} else {
echo "Unknown event received.";
}
The vanishing POST data
If you try to run the code above with a modern webhook, it will almost certainly fail. Why? Because most webhooks don't send data as a standard form submission (application/x-www-form-urlencoded). Instead, they send a raw JSON string in the request body with a Content-Type: application/json header.
PHP's $_POST superglobal is only populated when the content type is a form. When a service sends JSON, $_POST stays empty. You aren't doing anything wrong with your logic; you're just looking in the wrong place for the data.
Reading the raw input stream
To get the actual payload, we have to go straight to the source: the PHP input stream. This is a read-only stream that allows you to grab the raw body of the HTTP request regardless of the content type.
// The fix: Reading from php://input
$rawPayload = file_get_contents('php://input');
$data = json_decode($rawPayload, true);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
die('Invalid JSON provided');
}
// Now we can access our data
$event = $data['event'] ?? null;
I always recommend adding that json_last_error() check. Webhooks can be messy, and if you try to access an array on a null result from a malformed JSON string, your script will crash, and the external service will just see a 500 error without knowing why.
Routing events through a dispatcher
Now, once you've captured the data, you shouldn't just pile 50 if/else statements into one file. That's a maintenance nightmare. Instead, you want a "Dispatcher"—a simple mechanism that maps an event name to a specific function or class.
Here is how I usually structure a lightweight dispatcher. I use an associative array where the keys are the event types and the values are the callable functions that should handle them.
class WebhookDispatcher {
protected $handlers = [];
public function registerHandler($event, callable $callback) {
$this->handlers[$event] = $callback;
}
public function dispatch($payload) {
$event = $payload['event'] ?? 'unknown';
if (isset($this->handlers[$event])) {
return call_user_func($this->handlers[$event], $payload);
}
// Log unknown events so you know when a service updates its API
error_log("Unhandled webhook event: " . $event);
return false;
}
}
// Usage
$dispatcher = new WebhookDispatcher();
// Register specific logic for specific events
$dispatcher->registerHandler('payment.success', function($data) {
echo "Processing payment for order " . $data['order_id'];
});
$dispatcher->registerHandler('payment.failed', function($data) {
echo "Notifying customer about failed payment";
});
$rawPayload = file_get_contents('php://input');
$data = json_decode($rawPayload, true);
$dispatcher->dispatch($data);
By separating the receiving (reading the stream) from the dispatching (routing to a handler), your code stays clean. If the service adds a new event type tomorrow, you just add one line to register a new handler instead of digging through a massive conditional block.
📋 Practical Task
Build a GitHub-style Repository Event Router
Your task is to create a webhook receiver that simulates handling GitHub repository events. You need to build a script that does the following:
- Reads a JSON payload from
php://input. - Implements a
WebhookDispatcherclass (similar to the one in the lesson) that can register handlers for different events. - Registers two specific handlers:
push: Should print "New commit detected in [repo_name]!"pull_request: Should print "A new PR was opened for [repo_name]!"
- Handles unknown events by printing "Event not supported" and logging the event type to a local file named
webhook_log.txt.
Testing Tip: Since you can't easily "send" a webhook to your local machine without a tool like ngrok, you can simulate the input by creating a separate test script that uses curl or by temporarily replacing file_get_contents('php://input') with a hardcoded JSON string like '{"event": "push", "repo_name": "my-cool-app"}' to verify your dispatcher logic works.
There are no comments for now.