TokenMismatchException, showing up as a 419 status code, means the CSRF token sent with a request doesn't match what the server expects — for an AJAX request specifically, this almost always comes down to one of three causes, each with its own fix.
Cause 1: the CSRF token isn't being sent with the AJAX request at all
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
Standard HTML form submissions include the CSRF token automatically via the @csrf Blade directive — a raw AJAX request needs the token attached manually, via either the X-CSRF-TOKEN header (as above) or as a field in the request body, since Laravel checks both.
Using fetch() instead of jQuery
fetch('/api/orders', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
Cause 2: the page has been open long enough that the session (and its token) expired
Laravel's CSRF token is tied to the user's session, and a session has a configured lifetime (SESSION_LIFETIME in .env) — a page left open in a browser tab well beyond that lifetime will have a stale token that no longer matches the server's regenerated session, triggering this exception on the next AJAX call.
A practical fix: refresh the token periodically, or on a specific trigger
setInterval(function () {
fetch('/csrf-token')
.then(response => response.json())
.then(data => {
document.querySelector('meta[name="csrf-token"]').content = data.token;
});
}, 1000 * 60 * 30); // refresh every 30 minutes
Route::get('/csrf-token', fn () => response()->json(['token' => csrf_token()]));
For a page that's realistically left open for long stretches (a dashboard, an admin panel), periodically re-fetching a fresh token this way prevents the session-expiry version of this error from happening mid-session.
Cause 3: the route is excluded from CSRF verification when it shouldn't be, or vice versa
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'webhooks/*',
]);
})
A genuine webhook endpoint receiving requests from an external service (which can't supply a Laravel session's CSRF token) legitimately needs to be excluded — but excluding a route by accident, or forgetting to exclude one that genuinely needs it, is worth checking specifically when this error appears on a route you didn't expect it on.
Handling the 419 gracefully on the front end, as a fallback
$.ajax({
url: '/api/orders',
method: 'POST',
data: data,
error: function (xhr) {
if (xhr.status === 419) {
alert('Your session has expired. Please refresh the page and try again.');
}
}
});
Even with the periodic token refresh in place, handling a 419 gracefully in the AJAX error callback (rather than a generic, unhelpful failure) gives the user a clear, actionable message instead of a confusing silent failure.