›
byrcsc/laravel-payrex · 1.x
Verify PayRex webhook requests before dispatching Laravel events.
Laravel PayRex registers:
POST /payrex/webhookThe route is named payrex.webhook, so route('payrex.webhook') gives you the
absolute URL to register with PayRex. Point a PayRex webhook endpoint at that
URL and put its signing secret in the application environment:
PAYREX_WEBHOOK_SECRET=whsk_test_...The package verifies the signature against the untouched request body, parses the event, dispatches Laravel events, and returns:
{ "received": true, "id": "evt_..." }The webhook options live under webhooks in config/payrex.php:
'webhooks' => [
'enabled' => true,
'path' => 'payrex/webhook',
'tolerance' => 300,
'header' => 'Payrex-Signature',
'middleware' => [],
'events' => [
// event type => Laravel event class
],
],The matching environment variables are:
PAYREX_WEBHOOKS_ENABLED=true
PAYREX_WEBHOOK_PATH=payrex/webhook
PAYREX_WEBHOOK_TOLERANCE=300
PAYREX_WEBHOOK_HEADER=Payrex-SignatureThe route is deliberately outside Laravel's web and CSRF middleware. A
machine callback has no browser session; its signature authenticates it
instead.
Add rate limiting or application-specific middleware without removing signature verification:
'webhooks' => [
// ...
'middleware' => [
'throttle:60,1',
],
],The package always prepends VerifyPayrexSignature.
Payrex-Signature looks like t=1700000000,te=<hex>,li=<hex>: a Unix
timestamp, then the test-mode and live-mode signatures. Exactly one of the two
is populated, depending on which set of keys produced the event, and each is
HMAC-SHA256("{timestamp}.{payload}", secret) in lowercase hex. The package
checks both slots against the configured secret, so a test-mode secret can
never verify a live delivery. Mismatched modes are the usual cause of a 400 on
an otherwise correct endpoint.
The default tolerance is 300 seconds. A signed delivery whose timestamp is older than the tolerance is rejected; a timestamp in the future is not. Keep the server clock synchronized.
Set the value to 0 only when you intentionally want to disable timestamp
freshness checking:
PAYREX_WEBHOOK_TOLERANCE=0Freshness is not replay protection. An attacker cannot change a signed body, but a valid delivery can still be replayed within the window. PayRex also retries real deliveries. Deduplicate completed work using the event ID.
An absent, malformed, invalid, or stale signature raises
SignatureVerificationException and returns HTTP 400. A signed body that is
not a valid PayRex event raises InvalidPayloadException and also returns 400.
Monitor bursts of signature failures. They can indicate a wrong secret, clock drift, a proxy changing the raw body, or unsolicited traffic.
Disable the package route:
PAYREX_WEBHOOKS_ENABLED=falseThen verify and decode with parseEvent():
use ByRcsc\LaravelPayrex\Facades\Payrex;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/webhooks/payrex', function (Request $request) {
$event = Payrex::parseEvent(
payload: $request->getContent(),
header: $request->header('Payrex-Signature'),
);
// Dispatch or process the verified event.
return response()->json([
'received' => true,
'id' => $event->id,
]);
});Call getContent() before anything rewrites the body. parseEvent() uses the
configured signing secret and tolerance and returns a typed WebhookEvent.
When you own the route, you also own event dispatching, response timing,
middleware, and exception behavior.
Keep the HTTP path short. The package dispatches events synchronously, so make
your listeners implement ShouldQueue. A slow or failed delivery can be
retried by PayRex for up to three days with exponential backoff.