fix: deduplicate webhook — skip bot response on Meta retry
saveConversation() now returns bool; BotRouter only runs when the message_id is new (INSERT IGNORE rowCount>0). Prevents WhatsApp webhook retries from triggering duplicate bot responses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
16a03b546d
commit
904a1be6ea
+14
-9
@@ -328,10 +328,10 @@ class WpWebhook
|
||||
$body = $msg['text']['body'] ?? '';
|
||||
self::log('MSG', "[TEXT] {$ctx['from']} ({$ctx['name']}): $body");
|
||||
self::saveWebhookLog('messages', $ctx['from'], $ctx['name'], 'text', $body);
|
||||
self::saveConversation($ctx, $body);
|
||||
$isNew = self::saveConversation($ctx, $body);
|
||||
self::forwardToCompany($ctx, $body);
|
||||
|
||||
if (self::$currentCompany !== null) {
|
||||
if ($isNew && self::$currentCompany !== null) {
|
||||
BotRouter::route(self::$currentCompany, $ctx, $body, 'text');
|
||||
}
|
||||
}
|
||||
@@ -345,9 +345,9 @@ class WpWebhook
|
||||
$preview = $caption ?: "[$type id:$mediaId]";
|
||||
self::log('MSG', "[" . strtoupper($type) . "] {$ctx['from']} | id=$mediaId mime=$mime caption=$caption");
|
||||
self::saveWebhookLog('messages', $ctx['from'], $ctx['name'], $type, $preview);
|
||||
self::saveConversation($ctx, $preview, $mediaId);
|
||||
$isNew = self::saveConversation($ctx, $preview, $mediaId);
|
||||
self::forwardToCompany($ctx, $preview, $mediaId);
|
||||
if (self::$currentCompany !== null) {
|
||||
if ($isNew && self::$currentCompany !== null) {
|
||||
BotRouter::route(self::$currentCompany, $ctx, $caption, $type);
|
||||
}
|
||||
}
|
||||
@@ -378,10 +378,10 @@ class WpWebhook
|
||||
$replyId = $reply['id'] ?? '';
|
||||
self::log('MSG', "[INTERACTIVE/$iType] {$ctx['from']} | reply=" . json_encode($reply));
|
||||
self::saveWebhookLog('messages', $ctx['from'], $ctx['name'], 'interactive', $preview);
|
||||
self::saveConversation($ctx, $preview);
|
||||
$isNew = self::saveConversation($ctx, $preview);
|
||||
self::forwardToCompany($ctx, $preview);
|
||||
|
||||
if (self::$currentCompany !== null && $replyId !== '') {
|
||||
if ($isNew && self::$currentCompany !== null && $replyId !== '') {
|
||||
BotRouter::route(self::$currentCompany, $ctx, $replyId, 'interactive');
|
||||
}
|
||||
}
|
||||
@@ -394,10 +394,10 @@ class WpWebhook
|
||||
$replyId = $payload ?: $text;
|
||||
self::log('MSG', "[BUTTON] {$ctx['from']} | text=$text payload=$payload");
|
||||
self::saveWebhookLog('messages', $ctx['from'], $ctx['name'], 'button', $preview);
|
||||
self::saveConversation($ctx, $preview);
|
||||
$isNew = self::saveConversation($ctx, $preview);
|
||||
self::forwardToCompany($ctx, $preview);
|
||||
|
||||
if (self::$currentCompany !== null && $replyId !== '') {
|
||||
if ($isNew && self::$currentCompany !== null && $replyId !== '') {
|
||||
BotRouter::route(self::$currentCompany, $ctx, $replyId, 'button');
|
||||
}
|
||||
}
|
||||
@@ -481,7 +481,7 @@ class WpWebhook
|
||||
|
||||
// ─── Guardar en base de datos ─────────────────────────────────────────────
|
||||
|
||||
private static function saveConversation(array $ctx, string $content, ?string $mediaId = null): void
|
||||
private static function saveConversation(array $ctx, string $content, ?string $mediaId = null): bool
|
||||
{
|
||||
try {
|
||||
$companyId = self::$currentCompany['id'] ?? null;
|
||||
@@ -503,9 +503,14 @@ class WpWebhook
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$convId = (int) db()->lastInsertId();
|
||||
self::saveNotification($convId, $ctx['from'], $content);
|
||||
return true;
|
||||
}
|
||||
// Mensaje duplicado (webhook retry de Meta) — no procesar de nuevo
|
||||
self::log('INFO', "Mensaje duplicado ignorado: {$ctx['message_id']} from={$ctx['from']}");
|
||||
return false;
|
||||
} catch (\PDOException $e) {
|
||||
self::log('ERROR', 'DB saveConversation: ' . $e->getMessage());
|
||||
return true; // En caso de error DB, dejar pasar para no silenciar mensajes reales
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user