Los webhooks ofrecen notificaciones en tiempo real cuando se producen eventos en tu espacio de trabajo de Quo, lo que permite integraciones potentes y flujos de trabajo automatizados. Configura webhooks para enviar datos de eventos a tus aplicaciones al instante cuando se completen llamadas, lleguen mensajes o se actualicen contactos.
Cómo funcionan los webhooks
Notificaciones basadas en eventos:
Los webhooks envían notificaciones instantáneas cuando se producen eventos específicos en tu espacio de trabajo de Quo. Cuando se activa un evento, Quo envía una solicitud HTTP POST a la URL que hayas especificado con datos detallados del evento.
Flujo de trabajo típico:
- Configura la URL del webhook y selecciona los tipos de eventos
- Quo supervisa los eventos especificados
- Cuando se produce un evento, Quo envía una solicitud POST con la carga del evento
- Tu aplicación procesa los datos del evento y responde
- Quo registra el estado de la entrega y vuelve a intentarlo si es necesario
La configuración de webhooks requiere permisos de propietario o administrador del espacio de trabajo. Los ajustes se gestionan únicamente a través de las aplicaciones web y de escritorio.
Interfaz de configuración de webhooks:
Eventos de webhooks disponibles
Notificaciones de mensajes de texto:
message.received
: Mensaje de texto recibido en el número de teléfono del espacio de trabajo (incluye archivos multimedia)
message.delivered
: Mensaje de texto enviado desde el espacio de trabajo y entregado correctamente (incluye archivos multimedia)
Información con IA:
call.summary.completed
: Resumen de llamada generado por IA disponible en la carga útil del evento
call.transcript.completed
: Transcripción completa de la llamada disponible en la carga útil del evento
Notificaciones de estado de la llamada:
call.ringing
: Llamada entrante recibida en el número de teléfono del espacio de trabajo
call.completed
: Llamada finalizada (contestada o no contestada; puede incluir un buzón de voz)
call.recording.completed
: Grabación de la llamada disponible en la URL indicada
Administración de contactos:
contact.updated
: Contacto creado o modificado en el espacio de trabajo
contact.deleted
: Contacto eliminado del espacio de trabajo
Requisitos de configuración
Parámetros obligatorios:
Parámetro | Requisito | Descripción |
---|
URL | Obligatorio | Endpoint del controlador del webhook (se recomienda encarecidamente HTTPS para producción) |
Tipos de eventos | Obligatorio | Selecciona uno o más tipos de eventos para supervisar |
Recursos | Obligatorio | Elige números de teléfono (llamadas/mensajes) o usuarios/grupos (contactos) para supervisar |
Parámetros opcionales:
Parámetro | Descripción |
---|
Etiqueta | Nombre descriptivo para identificar y administrar el webhook |
Para crear un webhook:
- Ve a Settings → Webhooks en Quo
- Haz clic en Create webhook
- Ingresa la URL del controlador de webhook
- Selecciona los tipos de eventos que deseas monitorear
- Elige los números de teléfono o los recursos de contacto
- Añade una etiqueta opcional para su identificación
- Guarda y prueba la configuración
Creación de controladores de webhook
Requisitos del controlador
Especificaciones técnicas:
- Aceptar solicitudes HTTP POST en tu URL de webhook
- Procesar la carga útil de eventos en formato JSON en el cuerpo de la solicitud
- Responder con un código de estado HTTP 2xx en un máximo de 10 segundos
- Verificar la firma del webhook por seguridad
- Manejar los reintentos y los errores de forma adecuada
Gestión de respuestas:
- Éxito: Devolver un código de estado 2xx (no se requiere cuerpo de respuesta)
- Error: Una respuesta que no sea 2xx activa la secuencia de reintentos de Quo
- Tiempo de espera: Si no hay respuesta en 10 segundos, se inician reintentos
Flexibilidad de desarrollo:
Los controladores de webhook pueden desarrollarse en cualquier lenguaje de programación que admita solicitudes y respuestas HTTP. Despliega en plataformas en la nube, servidores o funciones sin servidor.
Seguridad y autenticación
Verificación de la firma del webhook:
Todas las llamadas de webhook incluyen firmas criptográficas para verificar la autenticidad y evitar ataques de suplantación.
Formato del encabezado de la firma:
'openphone-signature': 'hmac;1;1639710054089;mw1K4fvh5m9XzsGon4C5N3KvL0bkmPZSAy
b/9Vms2Qo='
Estructura de la firma:
<esquema>;<versión>;<marca_temporal>;<firma>
Componente | Descripción | Valor actual |
---|
scheme | Algoritmo de firma | Siempre “hmac” |
version | Versión de la firma | Siempre “1” |
timestamp | Hora de generación de la firma | Marca de tiempo Unix |
signature | Firma HMAC codificada en Base64 | Hash SHA256 |
Proceso de verificación de firmas
Pasos de verificación:
- Extrae los componentes del encabezado
openphone-signature
- Prepara los datos firmados concatenando
timestamp + "." + payload
- Decodifica la clave de firma desde base64 (disponible en los detalles del webhook)
- Calcula HMAC-SHA256 usando la clave decodificada y los datos firmados
- Compara el resultado con la firma del encabezado
Requisitos importantes:
- Elimina todos los espacios y saltos de línea del JSON
payload
antes de concatenar
- Usa la forma binaria de la clave de firma decodificada desde base64 para el cálculo de HMAC
- Asegúrate de que la cadena coincida exactamente para que la verificación sea exitosa
Cómo obtener tu clave de firma:
- Ve a la página de detalles del webhook en Quo
- Haz clic en el menú de puntos suspensivos (⋯) en la esquina superior derecha
- Selecciona “Reveal signing secret”
- Copia la clave codificada en base64 para tu aplicación
Ejemplos de implementación
Controlador de webhook en Node.js:
const express = require("express")
const bodyParser = require('body-parser')
const crypto = require('node:crypto');
const app = express()
const port = 8000
app.use(bodyParser.json())
app.post("/", function (req, res) {
// signingKey proviene de "Reveal Signing Secret" en la aplicación Quo.
const signingKey = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='
// Parsear los campos del encabezado openphone-signature.
const signature = req.headers['openphone-signature']
const fields = signature.split(';')
const timestamp = fields[2]
const providedDigest = fields[3]
// Calcular los datos cubiertos por la firma.
const signedData = timestamp + '.' + JSON.stringify(req.body)
// Convertir la clave de firma codificada en base64 a binario.
const signingKeyBinary = Buffer.from(signingKey, 'base64').toString('binary')
// Calcular el digest SHA256 HMAC.
const computedDigest = crypto.createHmac('sha256',signingKeyBinary)
.update(Buffer.from(signedData,'utf8'))
.digest('base64')
// Verificar que la firma coincida
if (providedDigest === computedDigest) {
console.log(`verificación de firma exitosa`)
// Procesar evento webhook aquí
} else {
console.log(`verificación de firma falló`)
return res.status(401).send('No autorizado')
}
res.send({})
});
app.listen(port, function () {
console.log(`servidor webhook escuchando en el puerto ${port}`)
})
Controlador de webhooks en Python:
import base64
import hmac
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/', methods=['POST'])
def handle_webhook_call():
# signingKey se obtiene de "Reveal Signing Secret" en la aplicación Quo.
signing_key = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='
# Parsear los campos del encabezado openphone-signature.
signature = request.headers['openphone-signature']
fields = signature.split(';')
timestamp = fields[2]
provided_digest = fields[3]
# Calcular los datos cubiertos por la firma como bytes.
signed_data_bytes = b''.join([timestamp.encode(), b'.', request.data])
# Convertir la clave de firma codificada en base64 a bytes.
signing_key_bytes = base64.b64decode(signing_key)
# Calcular el digest SHA256 HMAC.
hmac_object = hmac.new(signing_key_bytes, signed_data_bytes, 'sha256')
computed_digest = base64.b64encode(hmac_object.digest()).decode()
# Verificar que la firma coincida
if provided_digest == computed_digest:
print('verificación de firma exitosa')
# Procesar evento webhook aquí
else:
print('verificación de firma falló')
return jsonify({'error': 'Unauthorized'}), 401
return jsonify({})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Es posible que en futuras versiones se incluyan varias firmas separadas por comas. Separa el valor del encabezado por comas para manejar múltiples firmas si es necesario.
Mejores prácticas de seguridad
Protección contra ataques de repetición (replay):
Implementa la validación de marcas de tiempo para prevenir ataques de repetición:
- Compara la marca de tiempo de la firma con la hora actual
- Rechaza solicitudes con marcas de tiempo fuera de la tolerancia aceptable (p. ej., 5 minutos)
- Cada llamada de webhook genera una marca de tiempo y una firma únicas
- Los reintentos incluyen automáticamente nuevas marcas de tiempo
Medidas de seguridad adicionales:
- Usa siempre URL HTTPS para webhooks en producción
- Almacena las claves de firma de forma segura (variables de entorno, gestión de secretos)
- Implementa un manejo adecuado de errores y registros
- Considera aplicar limitación de tasa (rate limiting) en los endpoints de webhook
Gestión de errores y reintentos
Sistema automático de reintentos
Comportamiento de los reintentos:
- Condiciones de activación: Códigos de respuesta no 2xx o tiempo de espera de 10 segundos
- Estrategia de backoff: Retroceso exponencial con retrasos crecientes
- Duración de los reintentos: Hasta 3 días de intentos
- Fallo final: Se envía una notificación por correo electrónico al creador del webhook
Cronograma de reintentos:
- Los primeros reintentos se realizan rápidamente para minimizar la demora
- Los retrasos aumentan exponencialmente con cada intento
- Se prioriza la entrega lo más cerca posible del momento del evento original
- Seguimiento automático del estado durante todo el proceso
Opciones de reintento manual
Administración de webhooks:
- Ver el estado de entrega en los detalles del webhook de Quo
- Reintentar manualmente las llamadas de webhook fallidas en cualquier momento
- Los webhooks fallidos se marcan con el estado “failure”
- Los reintentos manuales exitosos actualizan el estado a “success”
Notificaciones de errores
Cuando se agotan los reintentos:
- Se envía una alerta por correo electrónico al creador del webhook
- La llamada del webhook se marca como error permanente
- Los detalles del evento se conservan para revisión manual
- Opción de reintentar manualmente una vez resueltos los problemas
Opciones de pruebas locales:
- Clientes HTTP: Usa cURL, Postman o Insomnia para enviar solicitudes POST de prueba
- URLs locales: Prueba con localhost durante el desarrollo
- Cargas útiles simuladas: Crea JSON de eventos de ejemplo que cumpla con el formato de Quo
- Pruebas de firma: Verifica la lógica de validación HMAC con claves de prueba
Funciones de prueba de Quo
Solicitud de prueba integrada:
- Ve a la página de detalles del webhook en Quo
- Haz clic en el icono de puntos suspensivos (⋯) en la esquina superior derecha
- Selecciona “Send Test Request”
- Quo envía un evento de muestra a tu URL de webhook
- Verifica la validación de la firma y el manejo de la respuesta
Las solicitudes de prueba requieren URL de webhook accesibles públicamente. Las URL de desarrollo local no funcionan con la función de prueba de Quo.
Pruebas de eventos en vivo
Disparadores de eventos reales:
- Configura el webhook para un tipo de evento específico (p. ej.,
message.received
)
- Selecciona tu número de Quo en los recursos del webhook
- Dispara un evento real (envía un SMS a tu número)
- Supervisa la entrega y el procesamiento del webhook
- Verifica la funcionalidad completa de extremo a extremo
Lista de verificación de pruebas:
Problemas comunes y soluciones
Problemas de configuración:
- Verifica que la URL del webhook sea correcta y accesible
- Comprueba que el estado del webhook esté habilitado en la configuración
- Confirma que los tipos de eventos estén seleccionados correctamente
- Valida que los recursos (números de teléfono/usuarios/grupos) sean los correctos
Problemas de respuesta:
- Códigos de estado HTTP: Asegura respuestas 2xx para un procesamiento correcto
- Tiempo de respuesta: Devuelve las respuestas dentro del tiempo de espera de 10 segundos
- Manejo de errores: Implementa respuestas de error adecuadas para facilitar la depuración
Verificación de seguridad:
- Validación de la firma: Verifica el cálculo de la firma HMAC
- Formato de la clave: Asegura que la clave de firma esté correctamente decodificada en base64
- Manejo de la marca de tiempo: Revisa la extracción y concatenación de la marca de tiempo
Supervisión de eventos:
- Registro de eventos: Consulta el historial de entrega en la página de detalles del webhook
- Seguimiento del estado: Supervisa las tasas de éxito y fallo
- Reintentos: Revisa las secuencias de reintentos automáticos
- Pruebas manuales: Usa “Send Test Request” para una validación inmediata
Optimización del rendimiento:
- Tiempo de respuesta: Mantén el procesamiento por debajo de 10 segundos
- Tasa de errores: Minimiza los fallos para reducir la sobrecarga de reintentos
- Registro: Implementa un registro exhaustivo de solicitudes y respuestas
- Supervisión: Configura alertas para fallos en la entrega de webhooks
Ejemplos de cargas útiles de eventos
Carga de message.received
:
{
"id": "EVc67ec998b35c41d388af50799aeeba3e",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-23T16:55:52.557Z",
"type": "message.received",
"data": {
"object": {
"id": "AC24a8b8321c4f4cf2be110f4250793d51",
"object": "message",
"from": "+14155550100",
"to": "+13105550199",
"direction": "incoming",
"body": "Hola",
"media": [
{
"url": "https://storage.googleapis.com/opstatics-dev/6c908000ada94d9fb206649ecb8cc928",
"type": "image/jpeg"
}
],
"status": "received",
"createdAt": "2022-01-23T16:55:52.420Z",
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
Carga útil message.delivered
:
{
"id": "EVdefd85c2c3b740429cf28ade5b69bcba",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-23T17:05:56.220Z",
"type": "message.delivered",
"data": {
"object": {
"id": "ACcdcc2668c4134c3cbfdacb9e273cac6f",
"object": "message",
"from": "+13105550199",
"to": "+14155550100",
"direction": "outgoing",
"body": "Que tengas un buen día",
"media": [
{
"url": "https://opstatics-dev.s3.amazonaws.com/i/ab6084db-5259-42c0-93c1-e17fb2628567.jpeg",
"type": "image/jpeg"
}
],
"status": "delivered",
"createdAt": "2022-01-23T17:05:45.195Z",
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
Carga útil de call.ringing
:
{
"id": "EV95c3708f9112412a834cc8d415470cd8",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-23T17:07:51.454Z",
"type": "call.ringing",
"data": {
"object": {
"id": "ACbaee66e137f0467dbed5ad4bc8d60800",
"object": "call",
"from": "+14155550100",
"to": "+13105550199",
"direction": "entrante",
"media": [],
"voicemail": null,
"status": "timbrando",
"createdAt": "2022-01-23T17:07:51.116Z",
"answeredAt": null,
"completedAt": null,
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
call.completed
(llamada entrante con buzón de voz):
{
"id": "EVd39d3c8d6f244d21a9131de4fc9350d0",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-24T19:22:25.427Z",
"type": "call.completed",
"data": {
"object": {
"id": "ACa29ee906a4e04312a6928427b1c21721",
"object": "call",
"from": "+14145550100",
"to": "+13105550199",
"direction": "incoming",
"media": [],
"voicemail": {
"url": "https://m.openph.one/static/85ad4740be6048e4a80efb268d347482.mp3",
"type": "audio/mpeg",
"duration": 7
},
"status": "completed",
"createdAt": "2022-01-24T19:21:59.545Z",
"answeredAt": null,
"completedAt": "2022-01-24T19:22:19.000Z",
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
call.completed
(llamada saliente atendida):
{
"id": "EV348de11e4b134fa48017ac45a251dd3e",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-24T19:28:45.370Z",
"type": "call.completed",
"data": {
"object": {
"id": "AC7ab6f57e62924294925d0ea961de7dc5",
"object": "call",
"from": "+13105550199",
"to": "+14155550100",
"direction": "outgoing",
"media": [],
"voicemail": null,
"status": "completed",
"createdAt": "2022-01-24T19:28:33.892Z",
"answeredAt": "2022-01-24T19:28:42.000Z",
"completedAt": "2022-01-24T19:28:45.000Z",
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
call.recording.completed
payload:
{
"id": "EVda6e196255814311aaac1983005fa2d9",
"object": "event",
"apiVersion": "v2",
"createdAt": "2022-01-24T19:30:55.400Z",
"type": "call.recording.completed",
"data": {
"object": {
"id": "AC0d3b9011efa041d78c864019ad9e948c",
"object": "call",
"from": "+14155550100",
"to": "+13105550199",
"direction": "incoming",
"media": [
{
"url": "https://storage.googleapis.com/opstatics-dev/b5f839bc72a24b33a7fc032f78777146.mp3",
"type": "audio/mpeg",
"duration": 7
}
],
"voicemail": null,
"status": "completed",
"createdAt": "2022-01-24T19:30:34.675Z",
"answeredAt": "2022-01-24T19:30:38.000Z",
"completedAt": "2022-01-24T19:30:48.000Z",
"userId": "USu5AsEHuQ",
"phoneNumberId": "PNtoDbDhuz",
"conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"
}
}
}
Payload de contact.updated
y contact.deleted
:
{
"id": "EVe844e47e9fa4494d9acfa1144839ed94",
"object": "event",
"createdAt": "2022-01-24T19:44:09.579Z",
"apiVersion": "v2",
"type": "contact.updated",
"data": {
"object": {
"id": "CT61eeff33f3b14cfe6358cb52",
"object": "contact",
"firstName": "Jane",
"lastName": "Smith",
"company": "Comp Inc",
"role": "Agente",
"pictureUrl": null,
"fields": [
{
"name": "Teléfono",
"type": "phone-number",
"value": "+14155551212"
},
{
"name": "Email",
"type": "email",
"value": null
},
{
"name": "Prop1",
"type": "string",
"value": "Value12"
}
],
"notes": [
{
"text": "@USu5AsEHuQ mi nota 😂",
"enrichment": {
"taggedIds": {
"groupIds": [],
"userIds": [
"USu5AsEHuQ"
],
"orgIds": []
},
"tokens": {
"USu5AsEHuQ": {
"token": "USu5AsEHuQ",
"replacement": "Tom Smith",
"type": "mención",
"locations": [
{
"startIndex": 1,
"endIndex": 11
}
]
}
}
},
"createdAt": "2022-01-24T19:35:38.323Z",
"updatedAt": "2022-01-24T19:35:38.323Z",
"userId": "USu5AsEHuQ"
}
],
"sharedWith": [
"USu5AsEHuQ"
],
"createdAt": "2022-01-24T19:35:38.318Z",
"updatedAt": "2022-01-24T19:44:09.565Z",
"userId": "USu5AsEHuQ"
}
}
}
Eventos de análisis de IA
call.summary.completed
(payload):
{
"id": "EVc86d16fed5314cf6bd7bf13f11c65fd2",
"object": "event",
"apiVersion": "v3",
"createdAt": "2024-09-05T15:30:24.213Z",
"type": "call.summary.completed",
"data": {
"object": {
"object": "callSummary",
"callId": "AC641569d25e0a4489ad807409d9bee3fd",
"status": "completed",
"summary": [
"este es el resumen de tu llamada."
],
"nextSteps": [
"estos son tus próximos pasos."
]
}
}
}
Carga útil de call.transcript.completed
:
{
"id": "EV67e3564088bf4badb6593cd7db8f2832",
"object": "event",
"apiVersion": "v3",
"createdAt": "2024-09-05T15:30:18.629Z",
"type": "call.transcript.completed",
"data": {
"object": {
"object": "callTranscript",
"callId": "AC741569d25e0a4489ad804709d9bee3fc",
"createdAt": "2024-09-05T15:30:18.198Z",
"dialogue": [
{
"end": 73.57498,
"start": 0.03998,
"userId": "UStpTEr9a6",
"content": "hola mundo",
"identifier": "+12345678901"
},
{
"end": 1.52,
"start": 1.12,
"userId": "USiAYGldYE",
"content": "hola",
"identifier": "+19876543210"
}
],
"duration": 87.614685,
"status": "completed"
}
}
}
¿Necesitas ayuda? Envía una solicitud de soporte en support.openphone.com para obtener asistencia técnica con la implementación de webhooks.