Saltar al contenido principal

Descripción general

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:
  1. Configura la URL del webhook y selecciona los tipos de eventos
  2. Quo supervisa los eventos especificados
  3. Cuando se produce un evento, Quo envía una solicitud POST con la carga del evento
  4. Tu aplicación procesa los datos del evento y responde
  5. 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:
Formulario de creación de webhook en la configuración de Quo

Eventos de webhooks disponibles

Eventos de mensajería

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

Eventos de voz

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

Eventos de contactos

Administración de contactos:
  • contact.updated: Contacto creado o modificado en el espacio de trabajo
  • contact.deleted: Contacto eliminado del espacio de trabajo

Configurar webhooks

Requisitos de configuración

Parámetros obligatorios:
ParámetroRequisitoDescripción
URLObligatorioEndpoint del controlador del webhook (se recomienda encarecidamente HTTPS para producción)
Tipos de eventosObligatorioSelecciona uno o más tipos de eventos para supervisar
RecursosObligatorioElige números de teléfono (llamadas/mensajes) o usuarios/grupos (contactos) para supervisar
Parámetros opcionales:
ParámetroDescripción
EtiquetaNombre descriptivo para identificar y administrar el webhook

Proceso de configuración

Para crear un webhook:
  1. Ve a SettingsWebhooks en Quo
  2. Haz clic en Create webhook
  3. Ingresa la URL del controlador de webhook
  4. Selecciona los tipos de eventos que deseas monitorear
  5. Elige los números de teléfono o los recursos de contacto
  6. Añade una etiqueta opcional para su identificación
  7. 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>
ComponenteDescripciónValor actual
schemeAlgoritmo de firmaSiempre “hmac”
versionVersión de la firmaSiempre “1”
timestampHora de generación de la firmaMarca de tiempo Unix
signatureFirma HMAC codificada en Base64Hash SHA256

Proceso de verificación de firmas

Pasos de verificación:
  1. Extrae los componentes del encabezado openphone-signature
  2. Prepara los datos firmados concatenando timestamp + "." + payload
  3. Decodifica la clave de firma desde base64 (disponible en los detalles del webhook)
  4. Calcula HMAC-SHA256 usando la clave decodificada y los datos firmados
  5. 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:
  1. Ve a la página de detalles del webhook en Quo
  2. Haz clic en el menú de puntos suspensivos (⋯) en la esquina superior derecha
  3. Selecciona “Reveal signing secret”
  4. 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

Pruebas y validación

Pruebas de desarrollo

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:
  1. Ve a la página de detalles del webhook en Quo
  2. Haz clic en el icono de puntos suspensivos (⋯) en la esquina superior derecha
  3. Selecciona “Send Test Request”
  4. Quo envía un evento de muestra a tu URL de webhook
  5. 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:
  • El webhook recibe correctamente las solicitudes POST
  • La verificación de la firma funciona
  • El análisis del payload del evento se realiza correctamente
  • La lógica de la aplicación procesa los eventos correctamente
  • Las respuestas de error activan reintentos
  • Las respuestas exitosas detienen la secuencia de reintentos

Solución de problemas

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

Herramientas de depuración

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

Eventos de mensajes

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"  
    }  
  }  
}

Eventos de llamadas

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"  
    }  
  }  
}

Eventos de contacto

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"  
    }  
  }  
}

Recursos adicionales

¿Necesitas ayuda? Envía una solicitud de soporte en support.openphone.com para obtener asistencia técnica con la implementación de webhooks.
I