Servidor Express.js de produccion para recibir notificaciones asincronas de la API de Google Gemini en el pipeline de contenido masivo de CULTIVA IA.
| Evento | API | Estado resultado | Accion CULTIVA IA |
|---|---|---|---|
| batch.succeeded | Batch API | succeeded | Actualiza DB + Notifica cliente con output count |
| batch.failed | Batch API | failed | Registra error + Alerta al cliente con mensaje |
| batch.cancelled | Batch API | cancelled | Marca trabajo cancelado en DB |
| batch.expired | Batch API | expired | Notifica cliente: trabajo expirado (>24h) |
| video.generated | Veo API | video_ready | Guarda URI del video + Notifica cliente |
| interaction.completed | Interactions API | interaction_done | Almacena resultado del agente |
| interaction.requires_action | Interactions API | requires_action | Encola function-call para worker |
| interaction.failed | Interactions API | interaction_failed | Log de error estructurado |
| interaction.cancelled | Interactions API | interaction_cancelled | Marca cancelacion en DB |
// 1. Validar cabeceras requeridas if (!webhookId || !webhookSignature.includes(',')) return { ok: false, reason: 'missing_headers' }; // 2. Anti-replay: ventana de ±5 min const diff = currentTime - parseInt(webhookTimestamp); if (diff > 300 || diff < -300) return { ok: false, reason: 'timestamp_out_of_range' }; // 3. Construir contenido firmado const signedContent = `${webhookId}.${webhookTimestamp}.${payloadStr}`; // 4. Decodificar secreto (quitar prefijo whsec_) const secretBytes = Buffer.from( secret.slice(6), 'base64' ); // 5. Comparacion timing-safe (anti timing-attack) crypto.timingSafeEqual(sigBuf, expectedBuf);
2026-06-16T09:14:03Z INFO webhook.received {"eventType":"batch.succeeded"}2026-06-16T09:14:03Z INFO [JOB] batch_4f8a → succeeded {"outputCount":247}2026-06-16T09:14:03Z INFO [NOTIFY] → cliente_42: Batch completado (247 outputs)2026-06-16T09:17:51Z INFO webhook.received {"eventType":"video.generated"}2026-06-16T09:17:51Z INFO video.generated vid_veo_8c2d ready {"uri":"gs://veo/..."}2026-06-16T09:22:10Z WARN webhook.rejected {"reason":"timestamp_out_of_range"}2026-06-16T09:28:44Z WARN interaction.requires_action {"action":"function_call"}
express.raw() para preservar el body sin parsear para la firma.jobs, notifications, uptime. Apto para load balancers y k8s liveness probes.# Instalar dependencias npm install express # Arrancar el servidor en una terminal GEMINI_WEBHOOK_SECRET=whsec_test node resultado.js # En otra terminal: tunel publico a localhost:3001 npx hookdeck-cli listen 3001 gemini --path /webhooks/gemini # Output del tunel: ⣾ Listening on: https://abc123.hookdeck.io/webhooks/gemini # Usar esta URL en la configuracion del webhook de Gemini API