Laravel Cashier webhook diagnostic
Answer eight questions about your billing webhook and get a ranked list of blockers and risks — signature mismatch, CSRF, wrong mode, missing events, dead queue workers, duplicate deliveries and drifted subscription state.
About the laravel cashier webhook diagnostic
Billing webhook bugs are rarely one bug. They are a short list of independent things that must all be true at once — the endpoint must be reachable, the signature must verify, CSRF must not intercept it, the mode must match, the right events must be subscribed, a worker must be consuming the queue, and the handler must survive a duplicate delivery. Miss any one and subscription state drifts.
This diagnostic asks about each condition separately and ranks what it finds. Blockers stop delivery outright and are usually visible in the provider dashboard. Risks are worse in practice: everything looks healthy while state quietly diverges from the provider.
When to use it
- Subscriptions cancel in the payment provider but stay active in your application, or the reverse.
- Your endpoint shows 4xx or 5xx responses in the provider's delivery log and you need to narrow down which layer is rejecting it.
- You just moved from test to live mode and webhooks stopped working.
- You are reviewing someone else's billing integration before taking it over and want a structured checklist rather than an impression.
What it does not do
- It does not connect to your payment provider or read your code — the answers come from you, so the output is only as accurate as your observations.
- It does not repair drifted subscription rows. It tells you that reconciliation is required and what to reconcile against.
- It is provider-shaped around Cashier's Stripe driver; the Paddle driver has a different event vocabulary though the same failure classes.
How the result is produced
Each question maps to one independent failure class. A "no" produces a finding at the severity that class deserves: delivery, signature, CSRF, mode, events and workers are blockers because nothing downstream can work without them, while idempotency and state drift are risks because they degrade data rather than stop it.
"Not sure" is treated as a check rather than a pass, so unverified assumptions still appear in the report. The required-event list is the set Cashier relies on to keep local subscription state accurate.
Real inputs and the exact output
Every example below was run through this laravel cashier webhook diagnostic and copied verbatim.

Example 1: Endpoint returns 419 on every delivery
The provider log shows 419 for every event. Nothing reaches the Cashier controller, so no amount of handler debugging helps.
419 is never a billing problem. It is Laravel's CSRF middleware rejecting a POST that legitimately carries no token, and the fix is one line in `bootstrap/app.php`.
Answers
Provider shows 2xx: No
Signing secret matches: Yes
CSRF excluded: No
Mode matches: Yes
Required events enabled: Yes
Queue worker running: YesFindings
[BLOCKER] The endpoint is not returning 2xx
Fix: read the response body in the provider event log. 419 means CSRF.
[BLOCKER] Webhook route is behind CSRF verification
Fix: exclude the path from CSRF validation —
$middleware->validateCsrfTokens(except: ['stripe/*'])Example 2: Healthy 200s, nothing processed
Every delivery is green in the dashboard, but subscriptions never change state locally. Webhook handling is queued and no worker consumes that queue.
A 200 with a dead worker is the most misleading state in billing: every external signal says healthy while subscription data silently diverges. Fixing delivery does not repair the rows that already drifted.
Answers
Provider shows 2xx: Yes
Queue worker running: No
Handlers idempotent: Not sure
Local state matches: NoFindings
[BLOCKER] Webhook handling is queued but no worker is running
Fix: confirm a worker consumes that queue and that
php artisan queue:restart runs on every deploy.
[RISK] Handlers may not be idempotent
Fix: store the provider event id; write with updateOrCreate().
[RISK] Local subscription state has already drifted
Fix: replay the missed events for the affected window, then
reconcile against the provider as the source of truth.How it works
- Open your payment provider's endpoint page and check the recent deliveries and response codes.
- Answer the eight questions with what you actually observe — "not sure" is a valid answer and is treated as a check, not a pass.
- Read the findings in order: blockers stop delivery entirely, risks corrupt state quietly.
- Apply each fix, then replay one event from the provider dashboard to confirm.
- Reconcile any subscription rows that drifted while the endpoint was broken.
Frequently asked questions
- Why does my Stripe webhook return 419 in Laravel?
- 419 is Laravel's CSRF token mismatch response. Webhook POSTs carry no CSRF token, so the route must be excluded from CSRF verification — in `bootstrap/app.php` on current Laravel, or the `VerifyCsrfToken::$except` array on older versions.
- Why does Cashier report an invalid webhook signature?
- Almost always because the configured signing secret belongs to a different endpoint or a different mode, or because a cached config file still holds the old value. Copy the secret from the exact endpoint that is calling you, then run `php artisan config:clear`.
- Which webhook events does Laravel Cashier need?
- At minimum the subscription created, updated and deleted events, customer updated and deleted, invoice payment succeeded, invoice payment action required, and automatic payment-method updates. Subscribing to a subset is the usual cause of subscription state drifting out of sync.
- My endpoint returns 200 but nothing happens — why?
- If webhook handling is queued and no worker is consuming that queue, the provider sees a healthy 200 while nothing is ever processed. This is the most misleading failure mode in billing because every external signal looks correct.
- Do I need to handle duplicate webhook deliveries?
- Yes. Providers retry on timeout, so the same event can arrive more than once. Store the provider event id, ignore ids you have already processed, and write state with `updateOrCreate()` so a replay is a no-op rather than a double charge or duplicate email.
Want a real number instead of an estimate?
A fixed-price Laravel codebase and architecture audit gives you a written scope, a risk list and a delivery date before you commit to a build.
Related free tools
Laravel failed job decoder — Paste a row from your `failed_jobs` table and get the job class, queue, attempt count, a failure category and the specific fixes that apply — decoded locally, never unserialized.
Laravel .env generator — Build a correct .env for local, staging or production — with a freshly generated APP_KEY and the driver settings that match your stack.
Composer dependency conflict decoder — Paste Composer's "Your requirements could not be resolved" output and get the blocking package, the exact `why-not` commands to run, and the order to resolve the conflict in.
All free Dev Loader tools
Every tool below runs entirely in your browser — no account, no upload and no limits. 25 tools in total.
- Software development cost calculator
- Website cost calculator
- App development cost calculator
- Freelance rate calculator
- Cron expression generator
- Meta tag generator
- .htaccess redirect generator
- PHP formatter
- JSON to PHP array converter
- composer.json validator
- Laravel upgrade readiness scanner
- PHP version compatibility checker
- URL slug generator
- Unix timestamp converter
- Laravel .env generator
- PHP regex tester
- Laravel migration generator
- Laravel validation rule builder
- Laravel .gitignore generator
- Artisan command cheatsheet
- Carbon date format helper
- Composer dependency conflict decoder
- Laravel failed job decoder
- Laravel version and EOL risk checker
- Free tools hub