feat: log every incoming webhook request before validation
Saves a raw entry to webhook_logs immediately on any POST to the webhook, before HMAC check or JSON parsing. Shows HMAC status (ok/invalida/sin-firma). Live feed now shows all traffic including rejected and malformed payloads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
483cc4797e
commit
454ee297e2
@@ -83,6 +83,7 @@ class DashboardController
|
||||
.b-interactive{background:#ede7f6;color:#4527a0}
|
||||
.b-button{background:#e3f2fd;color:#0d47a1}
|
||||
.b-status{background:#f3f4f6;color:#6b7280}
|
||||
.b-raw{background:#fef9c3;color:#854d0e}
|
||||
.b-other{background:#f5f5f5;color:#555}
|
||||
</style>
|
||||
<div class="toolbar">
|
||||
@@ -104,7 +105,7 @@ class DashboardController
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ICONS = {text:'💬',image:'📷',audio:'🎵',video:'🎬',document:'📄',sticker:'🌞',reaction:'👍',location:'📍',interactive:'🔘',button:'🔲',status:'📊'};
|
||||
const ICONS = {text:'💬',image:'📷',audio:'🎵',video:'🎬',document:'📄',sticker:'🌞',reaction:'👍',location:'📍',interactive:'🔘',button:'🔲',status:'📊',raw:'🔌'};
|
||||
const BADGE = t => 'b-'+(ICONS[t]?t:'other');
|
||||
|
||||
let paused = false;
|
||||
|
||||
+39
-10
@@ -54,35 +54,64 @@ class WpWebhook
|
||||
|
||||
public static function receive(): void
|
||||
{
|
||||
// 0. Log raw request first (before any validation)
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$headers = getallheaders();
|
||||
self::debugLog('POST', json_encode(['headers' => $headers, 'body' => $rawBody], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 1. Validar body
|
||||
// Guardar raw body para los handlers
|
||||
self::$currentRaw = $rawBody ?: '';
|
||||
|
||||
// Guardar SIEMPRE una entrada raw en webhook_logs antes de cualquier validación
|
||||
self::saveRawIncoming($rawBody ?: '', $headers);
|
||||
|
||||
if ($rawBody === false || $rawBody === '') {
|
||||
self::respond(400, ['error' => 'Body vacío']);
|
||||
}
|
||||
|
||||
// 2. Guardar raw body para los handlers
|
||||
self::$currentRaw = $rawBody;
|
||||
|
||||
// 3. Verificar firma HMAC-SHA256 de Meta
|
||||
self::verifySignature($rawBody);
|
||||
|
||||
// 4. Decodificar JSON
|
||||
$payload = json_decode($rawBody, true);
|
||||
if (!is_array($payload)) {
|
||||
self::respond(400, ['error' => 'JSON inválido']);
|
||||
}
|
||||
|
||||
// 5. Procesar ANTES de responder
|
||||
self::processEvent($payload, $rawBody);
|
||||
|
||||
// 6. Responder 200 a Meta (menos de 20 seg)
|
||||
self::respond(200, ['status' => 'received']);
|
||||
}
|
||||
|
||||
private static function saveRawIncoming(string $rawBody, array $headers): void
|
||||
{
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$preview = mb_substr($rawBody, 0, 300);
|
||||
$sigOk = 'pendiente';
|
||||
$appSecret = env('WHATSAPP_APP_SECRET', '');
|
||||
$hmacEnabled = env('verify_hmac_signature', '1');
|
||||
|
||||
if ($hmacEnabled !== '0' && $appSecret !== '') {
|
||||
$sigHeader = $headers['X-Hub-Signature-256'] ?? $headers['x-hub-signature-256'] ?? '';
|
||||
if (strpos($sigHeader, 'sha256=') === 0) {
|
||||
$received = substr($sigHeader, 7);
|
||||
$expected = hash_hmac('sha256', $rawBody, $appSecret);
|
||||
$sigOk = hash_equals($expected, $received) ? 'ok' : 'invalida';
|
||||
} else {
|
||||
$sigOk = 'sin-firma';
|
||||
}
|
||||
} else {
|
||||
$sigOk = 'desactivada';
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("
|
||||
INSERT INTO webhook_logs
|
||||
(company_id, event_field, from_number, contact_name, message_type, message_preview, raw_payload)
|
||||
VALUES (NULL, 'raw', ?, ?, 'raw', ?, ?)
|
||||
");
|
||||
$stmt->execute([$ip, "HMAC:{$sigOk}", mb_substr($preview, 0, 500), $rawBody]);
|
||||
} catch (\PDOException $e) {
|
||||
self::log('ERROR', 'saveRawIncoming DB: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Procesamiento de eventos ─────────────────────────────────────────────
|
||||
|
||||
private static function processEvent(array $payload, string $raw): void
|
||||
|
||||
Reference in New Issue
Block a user