A new Laravel 13 2,157 application has routes/web.php and routes/console.php; php artisan install:api adds routes/api.php under /api with the stateless api group, and installs Sanctum 2,988 (Sanctum Tokens). withRouting() in bootstrap/app.php (bootstrap/app.php) names each file, adds the /up health route, and runs a then: closure for extra files. Payment webhooks arrive without a CSRF token, so they get their own:
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: function () {
Route::middleware('api')
->prefix('webhooks')
->name('webhooks.')
->group(base_path('routes/webhooks.php'));
},
)routes/webhooks.php holds Route::post('/payments', ...)->name('payments'), which logs the event and returns response()->noContent(), served as /webhooks/payments and named webhooks.payments:
$ curl -i -X POST -d event=paid -d order_id=1 http://127.0.0.1:8303/webhooks/payments HTTP/1.1 204 No Content Cache-Control: no-cache, private
Only the web group runs sessions and the PreventRequestForgery check. A real webhook also verifies the sender's signature in a middleware (Writing a Middleware Class). Passing using: instead of web: makes your closure load every route file itself.