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.
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
| Evento | Cuándo | Cada cuánto | Qué 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)
- Proyectos → tu marca → el botón de keywords ("N keywords tracked" o "Add the keywords this brand owns").
- Escribe las keywords (una por línea; se aceptan
#hashtags) y marca las redes. - 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.
- Save keywords. El secreto aparece bajo el campo: cópialo a la configuración de tu servidor (para Discord o Slack nunca hace falta).
- 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.
- En Discord, abre el canal donde deben caer las alertas (crea uno:
#brand-alerts). Ajustes del canal (el engrane) → Integraciones → Webhooks → Nuevo webhook. - 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. - 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".
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
- Entra a api.slack.com/apps → Create New App → From scratch → nombre "ViralHunt", elige tu workspace.
- Incoming Webhooks → ponlo en On → Add New Webhook to Workspace → elige el canal → Permitir. Copia la URL:
https://hooks.slack.com/services/T…/B…/…. - 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.
- 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). - 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".
- 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:
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 retryPHP
<?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"
}| Campo | Significado |
|---|---|
event | brand.alerts o brand.test. Decide por él primero. |
project | La marca: su id (el project_id que usas en cada llamada a la API) y nombre. |
alerts[].id | El id de la alerta: pásalo a alerts_read cuando tu agente la haya atendido, para que unread=1 deje de listarla. |
platform | reddit, quora, x, instagram, tiktok, bluesky. |
title, url, author | El hilo. author es la comunidad en Reddit (r/…), el handle en las demás. |
comments_from, comments_to, delta, hours | Comentarios 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_hot | true cuando el hilo se encontró ya ruidoso (una sola lectura, 50 comentarios o más); entonces comments_from es 0. |
posted_at, sent_at | Cuándo se publicó el hilo (UTC, puede ser null) y cuándo se hizo esta entrega. |
radar_url | Tu radar, filtrado por esta marca y ordenado por comentarios en subida: los mismos hilos con todo lo de alrededor. |
Verificar la firma
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)
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
- Solo https, solo host público. Una dirección privada, local o que no resuelve se rechaza al guardar, y otra vez antes de cada entrega; no se siguen redirecciones.
- La firma es tu prueba de origen. Verifícala en cualquier receptor que actúe (publique, gaste, escriba en tus sistemas). Un canal de Discord o Slack que solo muestra puede vivir sin ella.
- El secreto se muestra a owners y admins en Proyectos y por la API; los miembros no lo ven. Rótalo borrando y guardando de nuevo la URL.
- Trata una URL de webhook de Discord o Slack como una contraseña: quien la tenga puede escribir en ese canal. Guárdala en el campo y en nada más.
- Nunca enviamos tus tokens de API, tus credenciales ni datos de tus otras marcas: una entrega lleva los hilos de esa marca y un enlace al radar, nada más.
- Idempotencia:
X-VH-Deliveryes único por POST. Si tu receptor pudiera recibir dos veces (un proxy que reintenta), recuerda los últimos ids y salta los repetidos.
8. Cuando no funciona
| Qué ves | Qué 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 422 | Al 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 404 | URL 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 alerta | Tres 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 Discord | Dos 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 entrega | No 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. |
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