Integraciones
Webhooks
55 min
conectar asisteclick con cualquier sistema externo tiempo 20 30 minutos | nivel técnico | requiere administrador o super administrador resumen este tutorial enseña a configurar webhooks para recibir automáticamente una copia de cada conversación archivada en formato json esta funcionalidad permite integrar asisteclick con crms propios, sistemas de bi, data warehouses, herramientas de analytics o cualquier sistema que acepte llamadas http post antes de empezar acceso como administrador endpoint https preparado para recibir webhooks conocimientos básicos de json y peticiones http (opcional) herramienta para testing de webhooks (requestbin, beeceptor, etc ) arquitectura + + + + + + \| asisteclick | | webhook | | tu sistema | \| | | post | | | \| conversación | > | json | > | crm / bi / | \| archivada | | payload | | data warehouse | + + + + + + flujo de datos un agente o el sistema archiva una conversación en asisteclick el backend detecta el cambio de estado a "closed" asisteclick genera un json con toda la información de la conversación se envía un post http a la url configurada tu sistema recibe y procesa los datos eventos que disparan webhooks el sistema de webhooks se activa automáticamente en los siguientes eventos evento descripción cuando ocurre ticket closed conversación archivada agente cierra el chat o el sistema lo archiva automáticamente ticket handoff transferencia a agente bot transfiere a humano o ticket se abre event created evento creado se agenda una cita desde el chatbot event updated evento modificado se modifica una cita existente event canceled evento cancelado se cancela una cita agendada configuración inicial paso 1 acceder a la configuración de webhooks desde el dashboard, haga clic en el icono de engranaje "configuración" en el menú lateral en el panel que se despliega a la derecha, haga clic en "webhooks" nota esta opción solo es visible para usuarios con rol de administrador o super administrador paso 2 configurar la url del webhook en la página de webhooks encontrará un campo de texto para ingresar su url de post el placeholder indica el formato esperado http //www site com/post html para configurar ingrese la url completa de su endpoint (debe ser http o https) haga clic en el botón "grabar" paso 3 validación de url el sistema valida que la url cumpla con el formato correcto formatos válidos https //api tuempresa com/webhooks/asisteclick http //192 168 1 100 3000/webhook https //webhook site/abc 123 def formatos inválidos urls sin protocolo api ejemplo com/webhook protocolos no soportados ftp\ //servidor com urls mal formadas mensaje de error si la url es inválida, verá el mensaje "la url \[tu url] no existe por favor, ingrese una url válida (por ejemplo http //www sitio com/pagina php )" estructura del payload json cada vez que una conversación es archivada, su endpoint recibe un post con el siguiente payload payload completo \\{ "event type" "ticket closed", "ticket id" "#1fy", "key" "md5(ticket id + access token)", "timestamp" 1586229907754, "channel" "web", "source" "chat", "source id" null, "status" "closed", "subject" "i'm interested in the new red shoes", "department" \\{ "id" 93, "name" "customer support" \\}, "bot" \\{ "id" 25478, "name" "agustin", "transferred to agent" true \\}, "agent" \\{ "id" 569884, "name" "agustin", "email" "agustin\@hello com" \\}, "customer" \\{ "fingerprint" "k811xkn31fhzcss0dia8bbd3sr56l20lpx6gx76y", "name" "john", "email" "john\@gmail com", "phone" "", "facebook id" "", "sentiment" "", "ip" "95 256 142 145", "browser os" "chrome on windows 10 64 bit", "country code" "us", "country name" "united states" \\}, "event" \\{ "calendar id" "1", "base36 id" "hx34s", "customer locale" "es", "customer timezone" " 3", "event status" "active", "event url" "\<event url>", "friendly" "chrome on windows 10 64 bit", "location" "av libertador 2632, olivos" \\}, "messages" \[ \\{ "action" "out", "timestamp" 1586229888461, "name" "bruno", "message" "hi john! i'm bruno, a virtual assistant how can i help you?" \\}, \\{ "action" "in", "timestamp" 1586229891562, "name" "john", "message" "i'm interested in the new red shoes" \\} ], "tags" \["venta", "calzado"], "custom fields" \[ \\{ "producto interes" "zapatos rojos" \\} ] \\} descripción de campos metadatos del ticket campo tipo descripción event type string tipo de evento ticket closed , ticket handoff , event created , event updated , event canceled ticket id string identificador único del ticket en formato base36 (ej #1fy ) key string hash md5 para validación de autenticidad timestamp number timestamp unix en milisegundos del momento de archivado channel string canal de origen web , whatsapp , facebook , instagram , telegram , teams source string origen chat , bot , email source id string/null id adicional del origen (si aplica) status string estado closed , open , pending subject string asunto o primer mensaje de la conversación objeto department campo tipo descripción id number id numérico del departamento name string nombre del departamento objeto bot campo tipo descripción id number id del chatbot que atendió inicialmente name string nombre del chatbot transferred to agent boolean true si se transfirió a un agente humano objeto agent campo tipo descripción id number id del agente que atendió (si aplica) name string nombre del agente email string email del agente objeto customer campo tipo descripción fingerprint string identificador único del visitante/cliente name string nombre proporcionado por el cliente email string email del cliente phone string teléfono (si lo proporcionó) facebook id string id de facebook (para canal facebook messenger) sentiment string análisis de sentimiento positive , negative , neutral o vacío ip string dirección ip del cliente browser os string navegador y sistema operativo detectados country code string código de país iso (ej us , ar , mx ) country name string nombre del país objeto event (citas/agendamiento) solo presente cuando la conversación incluye una cita agendada campo tipo descripción calendar id string id de la agenda utilizada base36 id string id de la cita en base36 customer locale string idioma del cliente (ej es , en ) customer timezone string zona horaria utc (ej 3 , +1 ) event status string estado active , canceled , completed event url string url de la página del evento friendly string descripción amigable del dispositivo location string ubicación de la cita array messages historial completo de la conversación campo tipo descripción action string dirección in (cliente), out (agente/bot), private (nota interna) timestamp number timestamp unix en milisegundos name string nombre del emisor message string contenido del mensaje arrays adicionales campo tipo descripción tags array etiquetas asignadas a la conversación custom fields array campos personalizados capturados por el chatbot headers http enviados cada webhook incluye headers de autenticación header descripción content type application/json asisteclick ticket token hash md5 del ticket para validación asisteclick access token token de acceso de la cuenta configuración de headers personalizados puedes agregar headers personalizados a tus webhooks usando el formato especial en la url formato https //api tuempresa com/webhook header1\ value1;header2\ value2 ejemplos \# agregar api key https //api tuempresa com/webhook x api key\ tu api key secreto \# agregar múltiples headers https //api tuempresa com/webhook authorization\ bearer abc123;x custom header\ valor \# agregar header de ambiente https //api tuempresa com/webhook environment\ production;source\ asisteclick importante usa para separar la url base de los headers, y ; para separar múltiples headers implementación del receptor ejemplo en python (flask) from flask import flask, request, jsonify import hashlib import json app = flask( name ) \# tu clave privada (debe coincidir con asisteclick) private key = "tu clave secreta" def validate ticket token(ticket id, received token) """valida el token del ticket""" expected = hashlib md5( (ticket id + private key) encode() ) hexdigest() return received token == expected @app route('/webhook/asisteclick', methods=\['post']) def webhook() \# obtener headers de autenticacion ticket token = request headers get('asisteclick ticket token') access token = request headers get('asisteclick access token') \# parsear el payload data = request get json() \# validar autenticacion (opcional pero recomendado) if not ticket token or not access token return jsonify(\\{'error' 'missing authentication'\\}), 401 \# procesar según tipo de evento event type = data get('event type') ticket id = data get('ticket id') print(f"recibido \\{event type\\} ticket \\{ticket id\\}") if event type == 'ticket closed' process closed ticket(data) elif event type == 'ticket handoff' process handoff(data) elif event type startswith('event ') process calendar event(data) \# responder con 200 ok return jsonify(\\{'status' 'received'\\}), 200 def process closed ticket(data) """procesa tickets cerrados""" customer = data get('customer', \\{\\}) messages = data get('messages', \[]) \# ejemplo guardar en base de datos \# db save conversation(data) \# ejemplo enviar a crm \# crm create interaction(customer\['email'], messages) print(f"ticket cerrado cliente \\{customer get('name')\\}") print(f"total mensajes \\{len(messages)\\}") def process handoff(data) """procesa transferencias a agente""" agent = data get('agent', \\{\\}) print(f"transferido a \\{agent get('name')\\}") def process calendar event(data) """procesa eventos de calendario""" event = data get('event', \\{\\}) print(f"evento \\{event get('event status')\\}") if name == ' main ' app run(port=5000) ejemplo en node js (express) const express = require('express'); const crypto = require('crypto'); const app = express(); app use(express json()); const private key = 'tu clave secreta'; // función para validar token function validatetoken(ticketid, receivedtoken) \\{ const expected = crypto createhash('md5') update(ticketid + private key) digest('hex'); return expected === receivedtoken; \\} // endpoint del webhook app post('/webhook/asisteclick', (req, res) => \\{ // headers de autenticación const tickettoken = req headers\['asisteclick ticket token']; const accesstoken = req headers\['asisteclick access token']; // payload const data = req body; const eventtype = data event type; const ticketid = data ticket id; console log(`evento $\\{eventtype\\} | ticket $\\{ticketid\\}`); // procesar según tipo de evento switch (eventtype) \\{ case 'ticket closed' handleclosedticket(data); break; case 'ticket handoff' handlehandoff(data); break; default if (eventtype startswith('event ')) \\{ handlecalendarevent(data); \\} \\} // responder ok res status(200) json(\\{ status 'received' \\}); \\}); function handleclosedticket(data) \\{ const customer = data customer || \\{\\}; const messages = data messages || \[]; console log(`cliente $\\{customer name || 'desconocido'\\}`); console log(`email $\\{customer email || 'no disponible'\\}`); console log(`canal $\\{data channel\\}`); console log(`mensajes $\\{messages length\\}`); // ejemplo guardar en mongodb // await db conversations insertone(data); // ejemplo enviar a slack // await slack sendmessage(formatconversation(data)); \\} function handlehandoff(data) \\{ const agent = data agent || \\{\\}; console log(`transferido a $\\{agent name\\}`); \\} function handlecalendarevent(data) \\{ const event = data event || \\{\\}; console log(`evento calendario $\\{event event status\\}`); \\} app listen(3000, () => \\{ console log('webhook server running on port 3000'); \\}); ejemplo en php \<?php // webhook php // leer el payload json $input = file get contents('php\ //input'); $data = json decode($input, true); // headers de autenticación $tickettoken = $ server\['http asisteclick ticket token'] ?? null; $accesstoken = $ server\['http asisteclick access token'] ?? null; // log del evento error log("webhook recibido " $data\['event type'] " " $data\['ticket id']); // validar que llegó data if (!$data || !isset($data\['event type'])) \\{ http response code(400); echo json encode(\['error' => 'invalid payload']); exit; \\} // procesar según tipo de evento switch ($data\['event type']) \\{ case 'ticket closed' processclosedticket($data); break; case 'ticket handoff' processhandoff($data); break; default if (strpos($data\['event type'], 'event ') === 0) \\{ processcalendarevent($data); \\} \\} // responder ok http response code(200); echo json encode(\['status' => 'received']); function processclosedticket($data) \\{ $customer = $data\['customer'] ?? \[]; $messages = $data\['messages'] ?? \[]; // ejemplo guardar en mysql // $pdo >prepare("insert into conversations ") >execute(\[ ]); // ejemplo enviar a api de crm // $ch = curl init('https //tu crm com/api/interactions'); error log("ticket cerrado " ($customer\['name'] ?? 'n/a')); error log("canal " ($data\['channel'] ?? 'n/a')); error log("mensajes " count($messages)); \\} function processhandoff($data) \\{ $agent = $data\['agent'] ?? \[]; error log("transferido a " ($agent\['name'] ?? 'n/a')); \\} function processcalendarevent($data) \\{ $event = $data\['event'] ?? \[]; error log("evento " ($event\['event status'] ?? 'n/a')); \\} ?> casos de uso comunes 1\ sincronización con crm guarda automáticamente cada conversación como interacción en tu crm def sync to crm(data) customer = data\['customer'] \# buscar o crear contacto contact = crm find by email(customer\['email']) if not contact contact = crm create contact(\\{ 'name' customer\['name'], 'email' customer\['email'], 'phone' customer\['phone'], 'source' 'asisteclick' \\}) \# crear interacción/nota crm add interaction(contact\['id'], \\{ 'type' 'chat', 'channel' data\['channel'], 'subject' data\['subject'], 'transcript' format messages(data\['messages']), 'agent' data\['agent']\['name'] if data get('agent') else 'bot', 'tags' data get('tags', \[]) \\}) 2\ analytics y business intelligence envía datos a tu data warehouse para análisis async function sendtobigquery(data) \\{ const row = \\{ ticket id data ticket id, timestamp new date(data timestamp), channel data channel, customer country data customer country code, customer email data customer email, message count data messages length, had agent data bot transferred to agent, agent id data agent? id || null, department data department? name, tags data tags? join(','), sentiment data customer sentiment \\}; await bigquery dataset('asisteclick') table('conversations') insert(\[row]); \\} 3\ alertas en slack/teams notifica conversaciones importantes def notify slack(data) customer = data\['customer'] \# solo notificar si tiene etiqueta importante if 'urgente' in data get('tags', \[]) slack message = \\{ 'text' f"\ rotating light conversación urgente cerrada", 'blocks' \[ \\{ 'type' 'section', 'text' \\{ 'type' 'mrkdwn', 'text' f" cliente \\{customer\['name']\\}\n email \\{customer\['email']\\}\n canal \\{data\['channel']\\}" \\} \\} ] \\} requests post(slack webhook url, json=slack message) 4\ backup a amazon s3 archiva todas las conversaciones en s3 const aws = require('aws sdk'); const s3 = new aws s3(); async function backuptos3(data) \\{ const key = `conversations/$\\{data channel\\}/$\\{data ticket id\\} json`; await s3 putobject(\\{ bucket 'asisteclick backup', key key, body json stringify(data, null, 2), contenttype 'application/json' \\}) promise(); console log(`guardado en s3 $\\{key\\}`); \\} testing y debugging usando requestbin o webhook site para probar sin implementar un servidor vaya a https //webhook site o https //requestbin com copie la url única que le proporcionan configúrela en asisteclick archive una conversación de prueba verifique el payload recibido en el sitio de testing verificación local con ngrok si desarrolla localmente \# instalar ngrok npm install g ngrok \# exponer su servidor local ngrok http 3000 \# obtendrá una url como \# https //abc123 ngrok io use esa url en la configuración de asisteclick logging de debugging agregue logging detallado en su receptor import json from datetime import datetime def log webhook(data) log entry = \\{ 'received at' datetime now() isoformat(), 'event type' data get('event type'), 'ticket id' data get('ticket id'), 'channel' data get('channel'), 'payload size' len(json dumps(data)) \\} with open('webhook log jsonl', 'a') as f f write(json dumps(log entry) + '\n') manejo de errores respuestas http esperadas código significado acción de asisteclick 200 299 éxito webhook entregado correctamente 400 bad request error en el payload revisar logs 401/403 no autorizado verificar headers de autenticación 500+ error servidor error en su endpoint revisar logs errores comunes error webhook no llega causa url mal configurada o servidor no accesible solución 1\ verificar que la url sea accesible públicamente 2\ confirmar que el puerto está abierto 3\ revisar firewall/seguridad error timeout causa su servidor tarda mucho en responder solución 1\ responder inmediatamente con 200 ok 2\ procesar el payload de forma asíncrona 3\ no hacer operaciones pesadas en el request handler error json inválido causa el payload tiene caracteres especiales solución 1\ usar json decode() o json parse() con manejo de errores 2\ verificar encoding utf 8 límites y consideraciones límite valor nota timeout del webhook 30 segundos responde rápido o el request fallará reintentos no automático actualmente no hay cola de reintentos payload máximo sin límite definido depende del tamaño de la conversación concurrencia asíncrono múltiples webhooks pueden enviarse simultáneamente recomendaciones de performance responder rápido envíe el 200 ok inmediatamente, procese después cola de procesamiento use redis/rabbitmq para manejar alto volumen idempotencia guarde ticket id para evitar duplicados monitoreo implemente alertas si su receptor falla seguridad validación de autenticidad verifica que el webhook viene de asisteclick import hashlib def verify webhook(ticket id, received token, access token) \# generar token esperado private key = "asdfsafewereereafa( @sfad5869) 12342aaaaaa!#232aasf!" expected ticket token = hashlib md5( (ticket id + private key) encode() ) hexdigest() \# validar key del payload expected key = hashlib md5( (ticket id + access token) encode() ) hexdigest() return received token == expected ticket token mejores prácticas https obligatorio siempre use https en producción validar tokens verifique los headers de autenticación ip whitelisting si es posible, limite acceso a ips de asisteclick rate limiting proteja su endpoint contra abuso logs de auditoría registre todos los webhooks recibidos troubleshooting síntoma causa probable solución no llegan webhooks url inválida o vacía verificar configuración en dashboard error 401/403 falta autenticación revisar headers requeridos payload vacío error de parsing leer php\ //input o request body datos incompletos conversación sin datos algunos campos son opcionales duplicados reintentos manuales implementar idempotencia por ticket id latencia alta procesamiento síncrono usar colas asíncronas deshabilitar webhooks para deshabilitar el envío de webhooks acceda a configuración > webhooks borre el contenido del campo url (déjelo vacío) haga clic en "grabar" verá el mensaje de confirmación "datos grabados!" y los webhooks dejarán de enviarse preguntas frecuentes ¿puedo configurar múltiples urls de webhook? actualmente solo se soporta una url por cuenta si necesita enviar a múltiples destinos, implemente un router en su servidor que redistribuya a los destinos finales ¿qué pasa si mi servidor está caído? el sistema no tiene cola de reintentos automática los webhooks que fallen durante una caída se perderán recomendamos alta disponibilidad en su receptor monitoreo de uptime logs para identificar webhooks perdidos ¿los webhooks incluyen archivos adjuntos? los mensajes con archivos incluyen la url del archivo en el campo message el archivo en sí no se envía en el payload, pero puede descargarlo desde la url proporcionada ¿puedo filtrar qué conversaciones enviar? actualmente se envían todas las conversaciones archivadas el filtrado debe hacerse en su receptor basándose en campos como channel , department , tags , etc ¿cómo manejo gran volumen de webhooks? para alto volumen recomendamos usar una cola (redis, rabbitmq, sqs) procesar asincrónicamente escalar horizontalmente su receptor recursos adicionales herramientas de testing https //webhook site inspector de webhooks online https //requestbin com alternativa a webhook site https //ngrok com exponer localhost a internet https //postman com simular requests documentación relacionada integración con apis http desde chatbots configuración de canales (whatsapp, facebook, etc ) variables de memoria del bot
