Laravel Integration
This guide shows a full Laravel integration using push delivery with signature verification and queued job processing.
Controller
Section titled “Controller”<?phpnamespace App\Http\Controllers;
use Illuminate\Http\Request;use App\Jobs\ProcessWebhook;use App\Models\ProcessedWebhook;
class WebhookController extends Controller{ public function handle(Request $request) { if (!$this->verifySignature($request)) { abort(401, 'Invalid signature'); }
$payload = $request->json()->all(); $eventId = $payload['event_id'];
// Idempotency check if (ProcessedWebhook::where('event_id', $eventId)->exists()) { return response('OK', 200); }
// Dispatch for async processing ProcessWebhook::dispatch($payload);
return response('OK', 200); }
private function verifySignature(Request $request): bool { $timestamp = $request->header('X-Gateway-Timestamp'); $signature = $request->header('X-Gateway-Signature'); $body = $request->getContent(); $secret = config('services.transyt.delivery_secret');
$expected = hash_hmac('sha256', "{$timestamp}.{$body}", $secret);
return hash_equals($expected, $signature ?? ''); }}use App\Http\Controllers\WebhookController;
Route::post('/webhooks/transyt', [WebhookController::class, 'handle']) ->withoutMiddleware(['throttle:api']);Queued Job
Section titled “Queued Job”<?phpnamespace App\Jobs;
use App\Models\ProcessedWebhook;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;
class ProcessWebhook implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3; public array $backoff = [60, 300, 900];
public function __construct( private array $payload ) {}
public function handle(): void { $eventId = $this->payload['event_id']; $provider = $this->payload['provider']; $eventType = $this->payload['event_type']; $data = $this->payload['payload'];
// Idempotency check (in case of job retry) if (ProcessedWebhook::where('event_id', $eventId)->exists()) { return; }
ProcessedWebhook::create(['event_id' => $eventId]);
match ($provider) { 'stripe' => $this->handleStripe($eventType, $data), 'mailgun' => $this->handleMailgun($eventType, $data), default => logger()->warning("Unhandled provider: {$provider}"), }; }
private function handleStripe(string $eventType, array $data): void { match ($eventType) { 'charge.succeeded' => $this->processCharge($data), 'charge.failed' => $this->handleFailedCharge($data), 'customer.subscription.deleted' => $this->cancelSubscription($data), default => null, }; }
private function handleMailgun(string $eventType, array $data): void { match ($eventType) { 'delivered' => $this->markEmailDelivered($data), 'failed' => $this->handleEmailFailure($data), default => null, }; }}Migration
Section titled “Migration”<?phpuse Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema;
return new class extends Migration{ public function up(): void { Schema::create('processed_webhooks', function (Blueprint $table) { $table->id(); $table->string('event_id')->unique(); $table->timestamps(); }); }
public function down(): void { Schema::dropIfExists('processed_webhooks'); }};Configuration
Section titled “Configuration”return [ // ... 'transyt' => [ 'delivery_secret' => env('TRANSYT_DELIVERY_SECRET'), ],];Add to your .env:
TRANSYT_DELIVERY_SECRET=your-delivery-secretDisable CSRF for Webhook Route
Section titled “Disable CSRF for Webhook Route”Ensure the webhook route is excluded from CSRF verification. In Laravel 11+, API routes don’t have CSRF by default. For earlier versions:
protected $except = [ 'webhooks/transyt',];