Webhook Verification

The SDK provides utilities to verify webhook signatures, ensuring that incoming webhook payloads genuinely originated from Felloh.


Verifying Signatures

Felloh signs every webhook request with an HMAC-SHA256 signature sent in the X-Signature header. The SDK provides two static methods on the Webhooks class to verify this signature:

  • Webhooks::verifyWebhookSignature — returns true or false
  • Webhooks::assertWebhookSignature — throws FellohWebhookSignatureError if invalid

Both use timing-safe comparison to prevent timing attacks.

Parameters

  • Name
    payloadrequired
    Type
    string
    Description

    The raw request body. Must not be parsed or modified — use the raw body as received.

  • Name
    signaturerequired
    Type
    string
    Description

    The value of the X-Signature header from the webhook request.

  • Name
    secretrequired
    Type
    string
    Description

    Your webhook signing secret from the Felloh dashboard.

Boolean Check

use Felloh\Webhooks;

$isValid = Webhooks::verifyWebhookSignature(
    payload: $rawRequestBody,
    signature: $_SERVER['HTTP_X_SIGNATURE'],
    secret: 'your-webhook-secret',
);

if (!$isValid) {
    http_response_code(401);
    echo 'Invalid signature';
    exit;
}

// Process the webhook...

Assert (throws on failure)

use Felloh\Webhooks;
use Felloh\Exceptions\FellohWebhookSignatureError;

try {
    Webhooks::assertWebhookSignature(
        payload: $rawRequestBody,
        signature: $_SERVER['HTTP_X_SIGNATURE'],
        secret: 'your-webhook-secret',
    );
} catch (FellohWebhookSignatureError $e) {
    http_response_code(401);
    echo 'Invalid signature';
    exit;
}

// Signature is valid, process the webhook...

Laravel Example

When using Laravel, use $request->getContent() to get the raw request body for signature verification.

Laravel Webhook Handler

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Felloh\Webhooks;
use Felloh\Exceptions\FellohWebhookSignatureError;

Route::post('/webhooks/felloh', function (Request $request) {
    try {
        Webhooks::assertWebhookSignature(
            payload: $request->getContent(),
            signature: $request->header('X-Signature'),
            secret: config('services.felloh.webhook_secret'),
        );
    } catch (FellohWebhookSignatureError $e) {
        return response('Invalid signature', 401);
    }

    $event = $request->json()->all();
    logger()->info('Webhook received', $event);

    return response('OK', 200);
});

Symfony Example

When using Symfony, use $request->getContent() to get the raw request body.

Symfony Webhook Handler

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Felloh\Webhooks;
use Felloh\Exceptions\FellohWebhookSignatureError;

#[Route('/webhooks/felloh', methods: ['POST'])]
public function handleWebhook(Request $request): Response
{
    try {
        Webhooks::assertWebhookSignature(
            payload: $request->getContent(),
            signature: $request->headers->get('X-Signature'),
            secret: $_ENV['FELLOH_WEBHOOK_SECRET'],
        );
    } catch (FellohWebhookSignatureError $e) {
        return new Response('Invalid signature', 401);
    }

    $event = json_decode($request->getContent(), true);
    // Process the webhook...

    return new Response('OK', 200);
}