PHP

PHP Webhook Receiver with Signature Verify

Build a secure PHP webhook receiver that validates incoming payloads by verifying the signature provided by the sender, ensuring data integrity and authenticity.

<?php

// --- Configuration ---
$secret = getenv('WEBHOOK_SECRET') ?: 'your_super_secret_key'; // Use environment variable for production!
$logFile = __DIR__ . '/webhook.log';
$allowedMethods = ['POST']; // Webhooks typically use POST

// --- Function to log events ---
function logMessage($message) {
    global $logFile;
    file_put_contents($logFile, date('[Y-m-d H:i:s] ') . $message . PHP_EOL, FILE_APPEND);
}

logMessage('Webhook receiver started for ' . $_SERVER['REQUEST_URI']);

// --- Validate Request Method ---
if (!in_array($_SERVER['REQUEST_METHOD'], $allowedMethods)) {
    header('HTTP/1.1 405 Method Not Allowed');
    logMessage('Error: Method ' . $_SERVER['REQUEST_METHOD'] . ' not allowed.');
    exit('Method Not Allowed');
}

// --- Get Raw Payload ---
$payload = file_get_contents('php://input');
if ($payload === false) {
    header('HTTP/1.1 400 Bad Request');
    logMessage('Error: Could not read raw payload.');
    exit('Bad Request');
}

// --- Signature Verification (Example for 'X-Hub-Signature' like GitHub/Stripe) ---
// The exact header name and algorithm (e.g., sha1, sha256) depend on the webhook provider.
$signatureHeader = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? null;

if ($signatureHeader) {
    // Example: For GitHub, header format is 'sha1=hex_digest'
    // For Stripe, header format is 'v1=hex_digest' with timestamp
    // For this example, let's assume it's directly the hex digest, or 'sha256=hex_digest'
    $parts = explode('=', $signatureHeader, 2);
    $algo = count($parts) > 1 ? $parts[0] : 'sha256'; // Default to sha256 if no algo specified
    $providedSignature = count($parts) > 1 ? $parts[1] : $signatureHeader;

    $expectedSignature = hash_hmac($algo, $payload, $secret);

    // Use hash_equals for timing attack resistance
    if (!hash_equals($expectedSignature, $providedSignature)) {
        header('HTTP/1.1 401 Unauthorized');
        logMessage('Error: Invalid signature. Expected ' . $expectedSignature . ', Got ' . $providedSignature);
        exit('Invalid Signature');
    }
    logMessage('Signature verified successfully.');
} else {
    // Depending on your webhook provider, signature might be optional or in a different header.
    // For critical webhooks, it should always be required.
    logMessage('Warning: No signature header found. Processing without verification (DANGEROUS for production!).');
    // If signature is mandatory, uncomment the following:
    // header('HTTP/1.1 401 Unauthorized');
    // exit('Signature Required');
}

// --- Process the Payload ---
$data = json_decode($payload, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    header('HTTP/1.1 400 Bad Request');
    logMessage('Error: Invalid JSON payload. ' . json_last_error_msg());
    exit('Invalid JSON');
}

logMessage('Payload received and decoded successfully: ' . json_encode($data));

// --- Your application logic goes here ---
// Example:
if (isset($data['event_type']) && $data['event_type'] === 'order.created') {
    logMessage('New order created: ' . $data['order_id']);
    // Trigger internal processes, update database, send notifications, etc.
} else {
    logMessage('Unhandled event type or payload structure.');
}

// --- Respond to webhook sender ---
header('Content-Type: application/json');
echo json_encode(['status' => 'success', 'message' => 'Webhook received and processed.']);
logMessage('Webhook processed and response sent.');

?>
How it works: This PHP snippet demonstrates how to implement a secure webhook receiver. It safely reads the incoming raw JSON payload, verifies its authenticity by comparing a calculated HMAC signature against the one provided in the request headers (e.g., `X-Hub-Signature`), and then processes the decoded data. Crucially, it uses `hash_equals` for timing-attack safe comparison and logs all major steps, making it a robust foundation for integrating with external services via webhooks. Remember to secure `WEBHOOK_SECRET` using environment variables.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs