Webhooks — PHP SDK

Verify and handle webhooks

// Get the raw POST body and signature header
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIMIZ_SIGNATURE'];
$webhookSecret = getenv('SIMIZ_WEBHOOK_SECRET');

try {
    $event = $simiz->webhooks->constructEvent(
        $payload,
        $signature,
        $webhookSecret
    );

    switch ($event->type) {
        case 'payment.succeeded':
            $payment = $event->data;
            // Update order status
            break;

        case 'payment.failed':
            // Notify customer
            break;

        case 'refund.succeeded':
            // Process refund
            break;
    }

    http_response_code(200);
    echo json_encode(['received' => true]);

} catch (\Simiz\Exception\SignatureVerificationException $e) {
    http_response_code(401);
    echo 'Invalid signature';
}

Laravel example

// routes/api.php
Route::post('/webhooks/simiz', [WebhookController::class, 'handle']);

// app/Http/Controllers/WebhookController.php
class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        $payload = $request->getContent();
        $signature = $request->header('X-Simiz-Signature');

        try {
            $event = app(SimizClient::class)->webhooks->constructEvent(
                $payload,
                $signature,
                config('services.simiz.webhook_secret')
            );

            match ($event->type) {
                'payment.succeeded' => $this->handlePaymentSuccess($event->data),
                'payment.failed' => $this->handlePaymentFailure($event->data),
                default => null,
            };

            return response()->json(['received' => true]);
        } catch (SignatureVerificationException $e) {
            return response('Invalid signature', 401);
        }
    }
}

Manual verification

Signature format: X-Simiz-Signature: t=<timestamp>,v1=<hmac_sha256_hex>The timestamp protects against replay attacks. Signatures older than 5 minutes are rejected.
function verifyWebhook(string $payload, string $signatureHeader, string $secret, int $toleranceSeconds = 300): bool
{
    // Parse signature header: t=<timestamp>,v1=<hash>
    $parts = explode(',', $signatureHeader);
    $timestamp = null;
    $hash = null;

    foreach ($parts as $part) {
        if (str_starts_with($part, 't=')) {
            $timestamp = (int) substr($part, 2);
        } elseif (str_starts_with($part, 'v1=')) {
            $hash = substr($part, 3);
        }
    }

    if ($timestamp === null || $hash === null) {
        return false;
    }

    // Check timestamp is within tolerance (default: 5 minutes)
    $now = time();
    if (abs($now - $timestamp) > $toleranceSeconds) {
        return false;
    }

    // Calculate expected signature
    $signaturePayload = $timestamp . '.' . $payload;
    $expectedSignature = hash_hmac('sha256', $signaturePayload, $secret);

    // Timing-safe comparison
    return hash_equals($hash, $expectedSignature);
}