query('hub.mode') === 'subscribe' && hash_equals((string) data_get($notificationChannel->credentials, 'webhook_verify_token'), (string) $request->query('hub.verify_token')), 403, ); return response($request->query('hub.challenge'), 200); } public function handle(Request $request, NotificationChannel $notificationChannel) { $valid = $this->validSignature($request, $notificationChannel); $payload = $request->all(); $updates = $this->updates($payload); $log = NotificationWebhookLog::create([ 'notification_channel_id' => $notificationChannel->id, 'provider' => $notificationChannel->provider, 'event_type' => data_get($payload, 'entry.0.changes.0.field') ?? data_get($payload, 'event'), 'provider_message_id' => data_get($updates, '0.id'), 'signature_valid' => $valid, 'payload' => $payload, ]); abort_unless($valid, 401); foreach ($updates as $update) { $delivery = NotificationDelivery::where('provider_message_id', $update['id'] ?? null)->first(); if (! $delivery) { continue; } $status = match ($update['status'] ?? null) { 'delivered' => 'delivered', 'read' => 'read', 'failed' => 'failed', default => 'sent', }; $delivery->update([ 'status' => $status, 'delivered_at' => $status === 'delivered' ? now() : $delivery->delivered_at, 'read_at' => $status === 'read' ? now() : $delivery->read_at, 'failed_at' => $status === 'failed' ? now() : $delivery->failed_at, 'error_code' => data_get($update, 'errors.0.code'), 'error_message' => data_get($update, 'errors.0.title'), ]); } $log->update(['processed_at' => now()]); return response()->json(['success' => true]); } private function validSignature(Request $request, NotificationChannel $channel): bool { if ($channel->channel === 'whatsapp_official') { $secret = data_get($channel->credentials, 'app_secret'); $signature = $request->header('X-Hub-Signature-256'); return $secret && $signature && hash_equals('sha256='.hash_hmac('sha256', $request->getContent(), $secret), $signature); } $secret = data_get($channel->credentials, 'webhook_secret'); return $secret && hash_equals((string) $secret, (string) $request->header('X-Webhook-Secret')); } private function updates(array $payload): array { $statuses = data_get($payload, 'entry.0.changes.0.value.statuses'); if (is_array($statuses)) { return $statuses; } return [[ 'id' => data_get($payload, 'message_id') ?? data_get($payload, 'id'), 'status' => data_get($payload, 'status') ?? data_get($payload, 'event'), 'errors' => data_get($payload, 'errors', []), ]]; } }