From bcfaf6229350c7652856570030afb2e915435bc3 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:22:34 -0500 Subject: [PATCH] fix: deduplicar webhooks duplicados con INSERT IGNORE + UNIQUE index en message_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - conversations.message_id ahora tiene UNIQUE index (aplicado en BD prod) - saveMessage() usa INSERT IGNORE: si el mismo wamid llega dos veces en paralelo solo una llamada procesa el mensaje y llama al bot - La segunda llamada recibe rowCount=0 y hace continue sin pasar al bot - Elimina race condition que enviaba bienvenida + términos simultáneamente --- api/webhook.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/api/webhook.php b/api/webhook.php index dc987c9..6599837 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -368,8 +368,14 @@ class WhatsAppWebhook { // Filtrar campos según columnas existentes para evitar errores en esquemas antiguos $saveData = $this->filterColumns('conversations', $saveData); - // Guardar mensaje y conservar el id de la conversación para posibles encolamientos + // Guardar mensaje con INSERT IGNORE para deduplicar webhooks duplicados. + // Si el message_id ya existe (segunda llamada paralela), rowCount=0 y saltamos el bot. $conversationId = $this->saveMessage($saveData); + if (!$conversationId) { + // Mensaje ya procesado por una llamada concurrente, ignorar + error_log("[webhook] Duplicate webhook ignorado para message_id={$messageId}"); + continue; + } // Si el media se encoló (no se pudo descargar), actualizar media_queue con el conversation_id if (!empty($mediaUrl) && empty($localFile) && in_array($messageType, ['image', 'audio', 'video', 'document', 'sticker'])) { @@ -469,7 +475,16 @@ class WhatsAppWebhook { } private function saveMessage($messageData) { - return $this->db->insert('conversations', $messageData); + // INSERT IGNORE: si message_id ya existe (webhook duplicado), no inserta y devuelve 0 + $cols = implode(', ', array_map(fn($c) => "`{$c}`", array_keys($messageData))); + $placeholders = implode(', ', array_map(fn($c) => ":{$c}", array_keys($messageData))); + $sql = "INSERT IGNORE INTO `conversations` ({$cols}) VALUES ({$placeholders})"; + $stmt = $this->db->getConnection()->prepare($sql); + $stmt->execute($messageData); + if ($stmt->rowCount() === 0) { + return 0; // duplicado + } + return $this->db->getConnection()->lastInsertId(); } /**