soap/laravel-omise-webhooks
Laravel package for receiving and handling Omise payment webhooks. It registers a webhook endpoint, verifies incoming events, and dispatches them to your handlers so you can react to charges, refunds, and other Omise events without writing boilerplate.
The v1.2.0 release adds Laravel 13 support. If you're on Laravel 10–12 nothing changes; if you're planning a Laravel 13 upgrade this package will no longer block you. The remaining diffs are Dependabot bumps to fetch-metadata (CI only, no runtime impact).
Minimal setup:
composer require soap/laravel-omise-webhooks
php artisan vendor:publish --tag="omise-webhooks-config"
Then:
.env (OMISE_PUBLIC_KEY, OMISE_SECRET_KEY, and the webhook secret if the config exposes one). Never commit these — the config should read from env() only.php artisan route:list --path=omise to confirm the webhook endpoint the package auto-registers. Paste that full URL into the Omise dashboard (Settings → Webhooks), test mode first.php artisan vendor:publish --tag="omise-webhooks-migrations" then php artisan migrate).ngrok/expose and trigger a test event from the Omise dashboard.First real use case: listen for charge.complete (or charge.create) and flip an order to paid. Start by dumping the event payload in a throwaway listener to see exactly what Omise sends before writing business logic.
Listen to the package event, don't parse the request yourself. The value of this package is that it normalises the payload and dispatches a Laravel event. Register a listener in EventServiceProvider (or use Event::listen) and keep the controller thin:
Event::listen(OmiseWebhookReceived::class, MarkOrderPaid::class);
Resolve your domain object via metadata. When creating the charge, stash your own identifier in the Omise metadata array (['order_id' => $order->id]). On the webhook, read it back rather than trying to match on amount or email — both are unreliable for reconciliation.
Always queue the listener. Implement ShouldQueue and return 200 from the endpoint immediately. Omise retries on non-2xx and on timeouts; a slow listener turns one event into five.
Store every webhook before acting on it. Persist the raw payload (or use the package's built-in model if it has one) in the same transaction that guards idempotency:
$record = OmiseWebhook::firstOrCreate(['omise_event_id' => $payload['id']]);
if (! $record->wasRecentlyCreated) return;
This gives you replay capability and a forensic trail when something goes wrong at 2am.
Disable auto-routes when you need control. If the config allows it, turn off the package's route registration and define your own, so you can attach throttle, IP allow-listing, or a tenant-scoped prefix. Keep the package's verification middleware in the chain regardless.
Test with fixtures, not the network. Grab a real payload from the Omise dashboard, drop it in tests/Fixtures/, and POST it at your route in a feature test. Use Event::fake() to assert the package event was dispatched, and Queue::fake() to assert your job was pushed — two separate tests, so a broken listener doesn't hide a broken endpoint.
pending before redirecting, treat the webhook as the source of truth, and never mark paid from the return URL alone.currency before formatting, and store the raw amount.api middleware group or be excluded from VerifyCsrfToken, otherwise Omise gets a 419 and retries forever.charge.complete before charge.create. Make your listener tolerant of a missing parent record — upsert rather than findOrFail.illuminate/* requirement; if your app pins laravel/framework to ^11.0, composer update is a no-op. Run composer why-not soap/laravel-omise-webhooks if the upgrade doesn't take. Laravel 13 also raises the PHP floor, so check php -v before bumping.Omise-Webhook-Signature header at debug level behind a config flag. Webhook bugs are almost always "the payload wasn't what I assumed," and you can't reproduce without the original body.How can I help you explore Laravel packages today?