A webhook is a notification with a sender that has a timeout and a retry policy you do not control.
public function handle(Request $request): Response
{
$this->verifySignature($request); // fast, and first
ReceivedWebhook::create([
'provider' => 'payments',
'payload' => $request->getContent(),
]);
ProcessWebhook::dispatch($id);
return response()->noContent(); // under 100ms
}
Doing the work inline means the provider’s timeout becomes your latency budget, and exceeding it triggers a retry that arrives while the first is still running. Persisting the raw payload before processing is what makes the event recoverable when the handler has a bug — the work can be replayed from the stored body without asking the provider to resend. Returning 200 for anything you have durably stored, including a payload you cannot parse, is correct: a parse error is your problem, not a reason for the provider to retry.