Skip to Content
Course content

179: Building a Webhook Receiver and Dispatcher

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 WebhookDispatcher class (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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.