getContent(); $bodySize = strlen($body); if ($bodySize > (int) config('webhooks.max_body_bytes', 1024 * 1024)) { throw new HttpException(413, 'The webhook request body is too large.'); } $webhookRequest = DB::transaction(function () use ($endpoint, $request, $body, $bodySize): WebhookRequest { $lockedEndpoint = WebhookEndpoint::query() ->whereKey($endpoint->getKey()) ->lockForUpdate() ->first(); if (! $lockedEndpoint instanceof WebhookEndpoint || ! $lockedEndpoint->acceptsRequests()) { throw (new ModelNotFoundException)->setModel(WebhookEndpoint::class, [$endpoint->getKey()]); } $webhookRequest = WebhookRequest::create([ 'webhook_endpoint_id' => $lockedEndpoint->getKey(), 'method' => Str::upper($request->method()), 'request_uri' => $request->getRequestUri(), 'headers' => $request->headers->all(), 'query_parameters' => $request->query(), 'body' => $body, 'json_payload' => $this->parseJsonPayload($body, $request->header('Content-Type')), 'content_type' => $request->header('Content-Type'), 'body_size' => $bodySize, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'received_at' => now(), ]); $lockedEndpoint->forceFill([ 'last_request_at' => $webhookRequest->received_at, ])->save(); $this->pruneExcessRequests($lockedEndpoint); return $webhookRequest; }); WebhookRequestReceived::dispatch( (string) $endpoint->getKey(), (string) $webhookRequest->getKey(), ); return $webhookRequest; } /** * @return array|null */ private function parseJsonPayload(string $body, ?string $contentType): ?array { if ($body === '' || $contentType === null) { return null; } $normalizedContentType = Str::lower($contentType); if (! Str::contains($normalizedContentType, ['application/json', '+json'])) { return null; } try { $payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException) { return null; } return is_array($payload) ? $payload : null; } private function pruneExcessRequests(WebhookEndpoint $endpoint): void { $requestIds = $endpoint->requests() ->select('id') ->orderByDesc('received_at') ->orderByDesc('id') ->skip((int) config('webhooks.max_requests_per_endpoint', 1000)) ->take(1_000_000_000) ->pluck('id'); if ($requestIds->isNotEmpty()) { WebhookRequest::query()->whereKey($requestIds)->delete(); } } }