ViralHunt/Agentes/Webhooks

Las alertas de tu marca, donde viva tu equipo o tu agente

Un webhook es una URL tuya a la que ViralHunt llama en el momento en que pasa algo, en vez de que tú preguntes cada pocos minutos. Hoy se dispara con la vigilancia de marca: cuando una conversación que menciona una keyword de tu marca está ganando comentarios rápido, la alerta se envía por POST a tu URL en el mismo minuto en que el cron la encuentra. Apúntalo a un canal de Discord o Slack y el equipo lo ve ahí sin código; apúntalo a n8n, Make o Zapier y puede seguir cualquier cosa; apúntalo a tu propio agente y puede leer el hilo, redactar el comentario y dejarlo en el tablero antes de que nadie abra la app.

1Dale a la marca sus keywords en Proyectos. Sin keywords no se vigila nada, así que nada se dispara.
2Pega la URL de destino en la misma caja (un webhook de Discord o Slack, tu disparador de n8n, tu servidor) y elige el formato. Guarda.
3Pulsa Enviar prueba. Llega una alerta de muestra; si no llega, el mensaje bajo el botón dice por qué.
Las redesReddit · Quora · XInstagram · TikTokBlueskyposts que mencionantus keywordsViralHuntbusca las keywordscada pocas horascada 30 min: qué hilosganaron 10+ comentarios en 8 h→ un POST firmado por corridaCanal de Discord o Slackun mensaje por corrida, sin códigon8n · Make · Zapier→ Telegram, Sheets, correo, un modeloTu agente→ lee el hilo, propone el comentario

El cuadro completo: se buscan tus keywords en las redes, los hilos que suben se vuelven una alerta cada 30 minutos, y la alerta va a donde apuntaste el webhook.

1. Qué se dispara, y cuándo

EventoCuándoCada cuántoQué lleva
brand.alerts Un post que menciona una keyword de la marca (en Reddit, Quora, X, Instagram, TikTok o Bluesky) ganó 10 comentarios o más en las últimas 8 horas, o nació con 50 o más. Cada hilo se reporta una vez por marca. El cron de alertas corre cada 30 minutos. Un POST por corrida y por marca, con todas las alertas nuevas de esa corrida dentro (de una a diez). Los hilos: título, enlace, autor o comunidad, comentarios antes y ahora, la ganancia, las horas, si nació caliente, y un enlace a tu radar filtrado por la marca.
brand.test Pulsaste Enviar prueba (o llamaste a webhook_test). Una vez, en ese momento. Una alerta de muestra, misma forma. Tu código debe reconocer event: brand.test y no hacer nada con ella.

Las mismas alertas llegan también por correo y push a los owners y admins que tengan "Alertas de marca" activo en Notificaciones, y un resumen diario (los hilos más comentados del día) llega a las 8:00 en la zona horaria del proyecto. El webhook añade el lado máquina: tu canal o tu agente las recibe en el mismo momento. Nada más dispara un webhook todavía: ni un post publicado, ni un borrador nuevo. Cuando eso salga será un segundo evento en esta tabla, con las mismas cabeceras y firma.

2. Configúralo

En la app (dos minutos)

  1. Proyectos → tu marca → el botón de keywords ("N keywords tracked" o "Add the keywords this brand owns").
  2. Escribe las keywords (una por línea; se aceptan #hashtags) y marca las redes.
  3. En Webhook for this brand's agent pega la URL. El selector de formato reconoce solo una URL de Discord o Slack ("Auto"); para tu propio servidor o una automatización deja Auto o elige JSON.
  4. Save keywords. El secreto aparece bajo el campo: cópialo a la configuración de tu servidor (para Discord o Slack nunca hace falta).
  5. Enviar prueba. Verde significa que el receptor contestó 2xx. Ámbar o rojo dice qué contestó y qué revisar.

Solo un owner o un admin puede poner o cambiar un webhook; los miembros ven el campo sin editar. Un webhook por marca (proyecto): una agencia con cinco marcas tiene cinco URL, o una sola que lee X-VH-Project y reparte.

Desde la API (para un agente que se configura solo)

Las mismas dos operaciones existen como llamadas a la API, con un token de owner o admin (API Access). format es json, discord, slack o auto; url: null borra el webhook.

POST https://viralhunt.io/tool/api/v1/brand.php
Authorization: Bearer vhk_your_owner_or_admin_token
Content-Type: application/json

{"action": "webhook", "project_id": 7, "url": "https://your-server.example.com/viralhunt", "format": "json"}

→ {"success": true, "data": {"url": "…", "format": "json", "secret": "a1b2…", "formats": {…}, "delivery": {…}}}
POST https://viralhunt.io/tool/api/v1/brand.php
{"action": "webhook_test", "project_id": 7}

→ {"success": true, "data": {"ok": true, "code": 204, "format": "discord", "hint": "Delivered: the receiver answered 204."}}

Los agentes que usan la skill de ViralHunt o el servidor MCP lo tienen como viralhunt_brand_webhook: di "pon el webhook de BrandiZone en esta URL de Discord y manda una prueba" y hace las dos cosas.

3. A dónde mandarlo

Discord

Un canal de Discord recibe mensajes por una URL de webhook entrante. ViralHunt da forma a la alerta como mensaje de Discord (un embed por hilo, con el enlace, la comunidad, la ganancia de comentarios y un color por red), así que no hace falta nada en medio.

  1. En Discord, abre el canal donde deben caer las alertas (crea uno: #brand-alerts). Ajustes del canal (el engrane) → Integraciones → Webhooks → Nuevo webhook.
  2. Nómbralo "ViralHunt", comprueba que apunta a ese canal, Copiar URL del webhook. Se ve como https://discord.com/api/webhooks/1234…/AbCd…. Trátala como una contraseña: quien la tenga puede escribir en ese canal.
  3. Pégala en Proyectos → tu marca → Webhook. El selector de formato pasa a Discord solo. Guarda y Enviar prueba: un mensaje "Test from ViralHunt" aparece en el canal en un segundo.

Qué recibe el canal: una línea "🔥 Marca: 3 discussions heating up right now", después hasta nueve embeds, cada uno con el título del hilo enlazado, "r/comunidad · 12 → 31 comments (+19 in 8 h)", la red en el pie, y un último embed "Open the radar" a tu filtro de marca. Un mensaje de Discord acepta diez embeds como máximo; cuando una corrida trae más, el último dice "N more in the radar".

VHViralHuntAPPHoy a las 14:30
🔥 BrandiZone: 2 discussions heating up right now
Has anyone tried the new organic argan oil from…?
r/HaircareScience · 12 → 31 comments (+19 in 8 h)
Reddit · ViralHunt
What is the best oil for frizzy hair in humid weather?
Quora · 0 → 64 comments, born hot
Quora · ViralHunt

Consejos. Menciona un rol cuando importe: los webhooks de Discord no pueden pingar por sí mismos, así que si quieres "@equipo-comunidad" en cada alerta, pásalo por n8n (sección siguiente) y antepón la mención. Silencia el canal para quien no comenta. Si la prueba contesta 400, el formato no es Discord: elígelo en el selector y guarda otra vez. Si contesta 404, el webhook se borró en Discord: haz uno nuevo.

Slack

  1. Entra a api.slack.com/apps → Create New App → From scratch → nombre "ViralHunt", elige tu workspace.
  2. Incoming Webhooks → ponlo en On → Add New Webhook to Workspace → elige el canal → Permitir. Copia la URL: https://hooks.slack.com/services/T…/B…/….
  3. Pégala en Proyectos → tu marca → Webhook (el formato pasa a Slack), Guarda, Enviar prueba.

Qué recibe el canal: una línea de cabecera, una sección por hilo (título enlazado, comunidad, ganancia de comentarios, red) y una línea de contexto con el enlace al radar. Slack despliega el primer enlace en una vista previa del hilo; apaga el despliegue en los ajustes de la app si el canal se llena.

n8n, Make, Zapier (y cualquier herramienta de automatización)

Estas herramientas te dan una URL que espera un POST y arranca un flujo con su cuerpo. Deja el formato JSON: cada campo de la alerta se vuelve una variable que puedes mapear a cualquier paso: un mensaje de Telegram, una plantilla de WhatsApp, un correo, una fila de Google Sheets, una página de Notion, un ticket de Jira, una llamada a OpenAI o Claude con la voz de tu marca, o una llamada de vuelta a la API de ViralHunt.

  1. n8n: añade un nodo Webhook (método POST, ruta viralhunt, Respond: Immediately). Copia su Production URL, pégala en Proyectos, Guarda, Enviar prueba: el nodo muestra el cuerpo de muestra y cada nodo posterior ve {{ $json.body.alerts }}. Activa el flujo (la Test URL solo escucha mientras miras).
  2. Make: escenario → Webhooks → Custom webhook → copia la dirección → pégala en Proyectos → Enviar prueba para que Make aprenda la estructura → añade los módulos (Telegram Bot, Gmail, Google Sheets…) y mapea los campos. Programa el escenario en "immediately".
  3. Zapier: trigger Webhooks by Zapier → Catch Hook → copia la URL → pégala → Enviar prueba → "Test trigger" la encuentra → añade las acciones.

Verifica la firma también ahí cuando el flujo haga algo que cueste dinero o publique en público: un nodo Crypto (n8n) o un paso de código que calcule sha256 HMAC(cuerpo crudo, secreto) y lo compare con la cabecera X-VH-Signature. Para un canal que solo lee, la discreción de la URL suele bastar.

Separar en un elemento por hilo: el cuerpo lleva un arreglo alerts (de uno a diez). En n8n añade un nodo Split Out sobre body.alerts; en Make un Iterator; en Zapier un paso Looping. Así cada mensaje, fila o ticket es un hilo.

Telegram, WhatsApp, Teams, email

Estos no tienen un webhook entrante simple que acepte nuestro JSON, así que hace falta un salto en medio: lo más rápido es n8n o Make (sección anterior: nodo Webhook → nodo Telegram, cinco minutos, sin código). Microsoft Teams tiene webhooks entrantes a través de Power Automate ("When a Teams webhook request is received"), que también acepta cualquier JSON. El correo ya lo tienes, de ViralHunt mismo (Notificaciones → Alertas de marca).

Si de todas formas tienes tu propio servidor, un relevo a Telegram son doce líneas: un token de bot de @BotFather, tu chat id, y una llamada por alerta.

// after verifying the signature (see "Your own server" above):
for (const a of event.alerts) {
  const text = `🔥 <b>${event.project.name}</b>\n<a href="${a.url}">${a.title}</a>\n${a.author} · ${a.comments_from} → ${a.comments_to} comments (+${a.delta} in ${a.hours} h)`;
  await fetch(`https://api.telegram.org/bot${process.env.TG_TOKEN}/sendMessage`, {
    method: "POST", headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chat_id: process.env.TG_CHAT_ID, text, parse_mode: "HTML", disable_web_page_preview: false })
  });
}

Tu propio servidor o agente

Cualquier endpoint HTTPS en un host público. Tres reglas: verifica la firma (para que nadie pueda meterle alertas falsas a tu agente), contesta 2xx en menos de 8 segundos y haz el trabajo después (ViralHunt no reintenta; lo que pierdas siempre se puede leer por la API), e ignora brand.test. Tres receptores mínimos:

0 sViralHunt POSTstu receptor contesta 2xx8 sdejamos de esperardespués trabajas: lee el hilo, redacta, crea la tarjetasin respuesta en 8 s = la entrega se marca fallida; no hay reintento, y la alerta queda en GET alerts

Node.js (Express)

import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = process.env.VH_WEBHOOK_SECRET;   // the secret shown in Projects after saving the URL

app.post("/viralhunt", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.get("X-VH-Signature") || "";
  const mac = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  if (sig.length !== mac.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac))) {
    return res.status(401).end();                 // not ours: ignore it
  }
  res.status(200).end();                          // answer FIRST: ViralHunt waits 8 s and does not retry
  const event = JSON.parse(req.body.toString("utf8"));
  if (event.event === "brand.test") return;       // the test button
  for (const alert of event.alerts) handle(event.project, alert);   // your work, after the answer
});

app.listen(3000);

Python (Flask)

import hmac, hashlib, os, threading
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["VH_WEBHOOK_SECRET"].encode()   # the secret shown in Projects after saving the URL

@app.post("/viralhunt")
def viralhunt():
    raw = request.get_data()
    mac = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(mac, request.headers.get("X-VH-Signature", "")):
        abort(401)                                    # not ours
    event = request.get_json(force=True)
    if event.get("event") != "brand.test":
        threading.Thread(target=handle, args=(event,)).start()   # the work runs after the answer
    return "", 200                                    # ViralHunt waits 8 s and does not retry

PHP

<?php
$raw = file_get_contents('php://input');
$mac = 'sha256=' . hash_hmac('sha256', $raw, getenv('VH_WEBHOOK_SECRET'));   // the secret shown in Projects
if (!hash_equals($mac, $_SERVER['HTTP_X_VH_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
http_response_code(200);
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();   // answer first, work after
$event = json_decode($raw, true);
if (($event['event'] ?? '') === 'brand.test') exit;
foreach ($event['alerts'] as $alert) handle($event['project'], $alert);

Desarrollo local: ViralHunt rechaza direcciones privadas y locales, así que expón tu máquina con un túnel (ngrok http 3000, cloudflared tunnel --url http://localhost:3000) y pega la URL https que te da. Claude Code, OpenClaw y los agentes que corren en tu máquina funcionan igual: el túnel trae el POST a un receptor local pequeño que encola la alerta para el siguiente turno del agente. claude.ai o ChatGPT en el navegador no pueden recibir un webhook: para esos, consulta por polling (sección 5) o deja que n8n sea el receptor y les entregue el resumen.

4. Qué llega: el cuerpo, las cabeceras, la firma

Formato JSON (el que lee un agente o una automatización). Discord y Slack reciben los mismos hechos como mensaje.

POST https://your-server.example.com/viralhunt
Content-Type: application/json
User-Agent: ViralHunt-Webhooks/1.0
X-VH-Event: brand.alerts            (or brand.test for the test button)
X-VH-Project: 7                     (the project id = the brand)
X-VH-Delivery: 9f1c0a7e3b2d4c58     (unique per delivery; use it to ignore a repeat)
X-VH-Signature: sha256=3f7a…e21b    (HMAC-SHA256 of the raw body with your secret, hex)
{
  "event": "brand.alerts",
  "test": false,
  "project": { "id": 7, "name": "BrandiZone" },
  "alerts": [
    {
      "id": 4182,
      "platform": "reddit",
      "title": "Has anyone tried the new organic argan oil from…?",
      "url": "https://www.reddit.com/r/HaircareScience/comments/…",
      "author": "r/HaircareScience",
      "comments_from": 12,
      "comments_to": 31,
      "delta": 19,
      "hours": 8,
      "born_hot": false,
      "posted_at": "2026-10-26T06:10:00Z",
      "sent_at": "2026-10-26T14:30:12Z"
    }
  ],
  "radar_url": "https://viralhunt.io/tool/trending/?project_id=7&sort=rising_comments&time_range=24h",
  "sent_at": "2026-10-26T14:30:12Z"
}
CampoSignificado
eventbrand.alerts o brand.test. Decide por él primero.
projectLa marca: su id (el project_id que usas en cada llamada a la API) y nombre.
alerts[].idEl id de la alerta: pásalo a alerts_read cuando tu agente la haya atendido, para que unread=1 deje de listarla.
platformreddit, quora, x, instagram, tiktok, bluesky.
title, url, authorEl hilo. author es la comunidad en Reddit (r/…), el handle en las demás.
comments_from, comments_to, delta, hoursComentarios en la lectura anterior y ahora, la ganancia, y las horas entre las dos lecturas. delta es el número por el que ordenar o filtrar.
born_hottrue cuando el hilo se encontró ya ruidoso (una sola lectura, 50 comentarios o más); entonces comments_from es 0.
posted_at, sent_atCuándo se publicó el hilo (UTC, puede ser null) y cuándo se hizo esta entrega.
radar_urlTu radar, filtrado por esta marca y ordenado por comentarios en subida: los mismos hilos con todo lo de alrededor.

Verificar la firma

cuerpo crudolos bytes exactos recibidostu secretose muestra en ProyectosHMAC-SHA256hexsha256=3f7a…e21blo que calculastecompara, tiempo constanteX-VH-Signature: sha256=3f7a…e21b✓ nuestro: atiéndelo✗ no es nuestro: ignóralo

X-VH-Signature es sha256= seguido del HMAC-SHA256 en hexadecimal del cuerpo crudo de la petición (los bytes exactos, antes de parsear el JSON) con el secreto que se muestra en Proyectos. Calcula lo mismo de tu lado y compara con una función de tiempo constante (timingSafeEqual, hmac.compare_digest, hash_equals). Si no coincide, el POST no vino de ViralHunt o el cuerpo se alteró en el camino: ignóralo. El secreto se crea con la URL y se conserva mientras haya una URL; para rotarlo, borra la URL, guarda, pégala de nuevo, guarda: aparece un secreto nuevo.

5. Qué hacer con él: las posibilidades

🛎 Un canal de guardia

Lo más simple y lo más usado: un canal de Discord o Slack por marca. La persona de guardia ve el hilo, lo lee, comenta como la marca. Nada que construir.

🤖 El agente propone el comentario

Tu agente recibe la alerta, lee el hilo, escribe un comentario con la voz de la marca y lo deja como tarjeta en el Tablero Editorial con el enlace. Una persona lo pega. Las redes siguen siendo de la persona; el agente nunca publica ahí.

📝 Del hilo a un post

Una pregunta que sube es un post que la marca debería hacer. El agente lo redacta (schedule.php create con draft: true), sobre una de tus plantillas, y espera en Borradores tu OK.

🎯 Solo lo que importa

Filtra en el flujo: delta ≥ 20, o born_hot, o platform = reddit. Las ganancias grandes al canal con mención; el resto a un registro silencioso.

📊 Un registro en una hoja

Make o Zapier añaden una fila por hilo a Google Sheets: fecha, red, título, enlace, ganancia. Tu reporte semanal de dónde se habló de la marca, sin que nadie copie enlaces.

🧠 Pregunta a un modelo, con la voz de la marca

En n8n: Webhook → HTTP Request a la API de Claude u OpenAI con el hilo y la voice de la marca (de schedule.php?action=targets) → publica las tres respuestas sugeridas en el canal. La persona elige una.

🏢 Muchas marcas, un receptor

Una agencia pone la misma URL en todos los proyectos y reparte por X-VH-Project o project.name: cada marca a su canal, su hoja, su cliente.

🔁 Cierra el ciclo

Cuando el comentario esté publicado, marca la alerta como atendida (alerts_read). unread=1 lista entonces solo lo que nadie atendió, y el resumen diario es la comprobación de que nada se cayó.

La receta del agente, llamada por llamada

Lo que hace un receptor con una alerta cuando es un agente con token de ViralHunt (las cuatro llamadas están en la referencia de la API; la skill y el MCP las tienen como herramientas).

# 1) context: the thread and what else is climbing for this brand
GET https://viralhunt.io/tool/api/v1/brand.php?action=hot&project_id=7&hours=8

# 2) the brand's voice (write the comment in it)
GET https://viralhunt.io/tool/api/v1/schedule.php?action=targets          → projects[].voice

# 3) leave the proposed comment where the team works: a board card with the link
POST https://viralhunt.io/tool/api/v1/cards.php
{"title": "Comment in r/HaircareScience: argan oil thread (+19 comments)",
 "description": "Proposed comment:\n\n<the comment, in the brand's voice, facts first, no link unless asked>\n\nThread: https://www.reddit.com/r/…",
 "post_url": "https://www.reddit.com/r/…", "platform": "reddit", "priority": "high", "assigned_to": "Ana"}

# 4) mark the alert as taken, so nobody handles it twice
POST https://viralhunt.io/tool/api/v1/brand.php   {"action": "alerts_read", "project_id": 7, "ids": [4182]}

Prompt para un agente que ya tiene la skill: "Cuando mi receptor de webhooks te entregue un evento brand.alerts, por cada alerta lee el hilo, escribe un comentario con la voz de mi marca (hechos primero, sin enlace salvo que el hilo lo pida), crea una tarjeta en el tablero titulada con la comunidad y la ganancia, asignada a Ana, con el comentario y el enlace en la descripción, y marca la alerta como leída. Nunca comentes tú en la red."

6. Consultar por polling (cuando nada puede recibir un POST)

Webhook (push)ViralHuntun POST en el minuto en que se escribe una alertatu URLPolling (pull)ViralHuntGET alerts?since=…&unread=1 cada 15 a 30 minsolo lo nuevo; después POST alerts_readtu script o agentelas alertas se calculan cada 30 minutos: preguntar cada minuto no ve nada nuevo

Un agente en un navegador, un cron en una máquina tras un firewall, un script sin URL pública: pregunta en vez de escuchar. El cron de alertas corre cada 30 minutos, así que preguntar cada 15 a 30 minutos lo ve todo y preguntar cada minuto no ve nada nuevo. Usa since = la hora de tu última lectura y unread=1, y marca lo que tomaste.

GET https://viralhunt.io/tool/api/v1/brand.php?action=alerts&project_id=7&since=2026-10-26T14:00:00Z&unread=1
Authorization: Bearer vhk_…

→ the alerts written after `since` that nobody marked as taken, newest first

POST https://viralhunt.io/tool/api/v1/brand.php
{"action": "alerts_read", "project_id": 7, "ids": [4182, 4183]}

GET brand.php?action=status dice por red cuándo corrió la última búsqueda (last_run) y si la máquina siempre encendida está viva: cuando no lo está, solo se busca en Reddit y una respuesta vacía significa "nadie buscó", no "nadie habló". GET brand.php?action=hot&project_id=N&all=1 es la lista en vivo (subiendo primero, después lo más comentado) sin esperar una alerta.

7. Seguridad

8. Cuando no funciona

Qué vesQué significa y qué hacer
La prueba dice "no answer within 8 seconds"La URL no resolvió, rechazó la conexión, o tu servidor hizo el trabajo antes de contestar. Contesta 200 primero; revisa que el túnel esté arriba; revisa que el host se alcance desde internet, no solo desde tu red.
La prueba dice 400 o 422Al receptor no le gustó el cuerpo. Para Discord o Slack el formato está mal: elígelo en el selector y guarda. Para n8n, el nodo Webhook puede estar en GET o en otra ruta.
La prueba dice 401, 403 o 404URL equivocada o muerta: un webhook de Discord borrado en Discord, una app de Slack quitada del workspace, una ruta que tu servidor no sirve, o tu propia verificación de firma rechazando la prueba (va firmada como toda entrega; revisa que el secreto que configuraste sea el actual).
La prueba funciona, nunca llega una alertaTres razones honestas, en orden: la marca no tiene keywords (o son tan genéricas que nadie las discute); ningún hilo cruzó el umbral (10 comentarios ganados en 8 horas es una conversación real, no una mención); la máquina siempre encendida que busca en X, Quora, Instagram y TikTok está apagada, así que solo se busca en Reddit. status muestra la segunda y la tercera; la tarjeta de marca del dashboard muestra lo mismo con un aviso.
Las alertas llegan dos veces en DiscordDos marcas comparten keywords y el mismo canal, o la URL está puesta en dos proyectos. Cada proyecto reporta sus propias coincidencias; dale a cada marca su canal o reparte por project.name.
Perdí una entregaNo hay reintento a propósito (una tormenta de reintentos es como nacen los duplicados), y nada se pierde: GET brand.php?action=alerts&since=… tiene todas las alertas que se escribieron para la marca.
Referencia. Cada llamada de esta página está bajo Brand en la referencia de la API: status, keywords, hot, alerts, alerts_read, webhook, webhook_test, run. La skill (sección 8d) y el servidor MCP (herramientas viralhunt_brand_*) dan lo mismo a cualquier agente.

Empieza por las keywords

Crea una cuenta, dale a tu marca sus keywords en Proyectos, pega la URL del webhook en la misma caja y pulsa Enviar prueba.

Crear una cuenta gratis Ver los planes