Descripción general

Los webhooks proporcionan notificaciones en tiempo real cuando ocurren eventos en tu espacio de trabajo de OpenPhone, permitiendo integraciones potentes y flujos de trabajo automatizados. Configura webhooks para enviar datos de eventos a tus aplicaciones al instante cuando finalicen las llamadas, lleguen mensajes o se modifiquen los contactos.

Cómo funcionan los webhooks

Notificaciones basadas en eventos: Los webhooks envían notificaciones instantáneas cuando ocurren eventos específicos en tu espacio de trabajo de OpenPhone. Cuando se desencadena un evento, OpenPhone envía una solicitud HTTP POST a la URL que hayas especificado con datos detallados del evento. Flujo de trabajo típico:
  1. Configurar la URL del webhook y seleccionar tipos de eventos
  2. OpenPhone supervisa los eventos especificados
  3. Cuando ocurre el evento, OpenPhone envía una solicitud POST con los datos del evento
  4. Tu aplicación procesa los datos del evento y responde
  5. OpenPhone registra el estado de entrega y reintenta 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 OpenPhone

Eventos de webhook disponibles

Eventos de mensajería

Notificaciones de mensajes de texto:
  • message.received: Mensaje de texto recibido por el número de teléfono del área de trabajo (incluye archivos multimedia adjuntos)
  • message.delivered: Mensaje de texto enviado desde el área de trabajo y entregado exitosamente (incluye multimedia)
Análisis impulsados por IA:
  • call.summary.completed: Resumen de llamada generado por IA disponible en los datos del evento
  • call.transcript.completed: Transcripción completa de la llamada disponible en los datos del evento

Eventos de voz

Notificaciones de estado de llamada:
  • call.ringing: Llamada entrante recibida por el número de teléfono del área de trabajo
  • call.completed: Llamada finalizada (contestada o no contestada, puede incluir buzón de voz)
  • call.recording.completed: Grabación de llamada disponible en la URL proporcionada

Eventos de contacto

Gestión de contactos:
  • contact.updated: Contacto creado o modificado en el área de trabajo
  • contact.deleted: Contacto eliminado del área de trabajo

Configuración de webhooks

Requisitos de configuración

Parámetros obligatorios:
ParámetroRequisitoDescripción
URLObligatorioPunto de conexión del controlador de webhook (se recomienda encarecidamente HTTPS para producción)
Tipos de eventoObligatorioSelecciona uno o más tipos de evento 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 la identificación y gestión del webhook

Proceso de configuración

Para crear un webhook:
  1. Ve a ConfiguraciónWebhooks en OpenPhone
  2. Haz clic en Crear webhook
  3. Introduce la URL de tu controlador de webhook
  4. Selecciona los tipos de evento que deseas supervisar
  5. Elige números de teléfono o recursos de contacto
  6. Añade una etiqueta opcional para la 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 el payload del evento JSON en el cuerpo de la solicitud
  • Responder con código de estado HTTP 2xx en un plazo de 10 segundos
  • Verificar la firma del webhook por seguridad
  • Gestionar reintentos y fallos de manera elegante
Gestión de respuestas:
  • Éxito: Devolver código de estado 2xx (no se requiere cuerpo de respuesta)
  • Fallo: Respuesta no-2xx activa la secuencia de reintentos de OpenPhone
  • Tiempo de espera agotado: Sin respuesta en 10 segundos inicia reintentos
Flexibilidad de desarrollo: Los controladores de webhook pueden desarrollarse en cualquier lenguaje de programación que soporte solicitudes y respuestas HTTP. Despliega en plataformas en la nube, servidores o funciones serverless.

Seguridad y autenticación

Verificación de firma de webhook: Todas las llamadas de webhook incluyen firmas criptográficas para verificar autenticidad y prevenir ataques de suplantación. Formato del encabezado de firma:
'openphone-signature': 'hmac;1;1639710054089;mw1K4fvh5m9XzsGon4C5N3KvL0bkmPZSAy
b/9Vms2Qo='
Estructura de la firma:
<esquema>;<versión>;<timestamp>;<firma>
ComponenteDescripciónValor Actual
esquemaAlgoritmo de firmaSiempre “hmac”
versiónVersión de la firmaSiempre “1”
timestampTiempo de generación de la firmaTimestamp Unix
firmaFirma HMAC codificada en Base64Digest SHA256

Proceso de verificación de firma

Pasos de verificación:
  1. Extraer componentes del encabezado openphone-signature
  2. Preparar datos firmados concatenando timestamp + "." + payload
  3. Decodificar clave de firma desde base64 (disponible en detalles del webhook)
  4. Calcular HMAC-SHA256 usando la clave decodificada y los datos firmados
  5. Comparar resultado con la firma del encabezado
Requisitos importantes:
  • Eliminar todos los espacios en blanco y saltos de línea del payload JSON antes de la concatenación
  • Usar la forma binaria de la clave de firma decodificada en base64 para el cálculo HMAC
  • Asegurar coincidencia exacta de cadenas para el éxito de la verificación
Obtener tu clave de firma:
  1. Ve a la página de detalles del webhook en OpenPhone
  2. Haz clic en los puntos suspensivos (⋯) en la parte superior derecha
  3. Selecciona “Revelar secreto de firma”
  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 OpenPhone.
  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 HMAC SHA256.
  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 de webhook aquí
  } else {
    console.log(`verificación de firma falló`)
    return res.status(401).send('Unauthorized')
  }

  res.send({})
});

app.listen(port, function () {
  console.log(`servidor de webhook escuchando en el puerto ${port}`)
})
Controlador de webhook 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 OpenPhone.
    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 HMAC SHA256.
    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('falló la verificación de firma')
        return jsonify({'error': 'Unauthorized'}), 401
        
    return jsonify({})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)
Las versiones futuras pueden incluir múltiples firmas separadas por comas. Divide 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: Implementa validación de timestamp para prevenir ataques de repetición:
  • Compara el timestamp de la firma con la hora actual
  • Rechaza solicitudes con timestamps fuera del rango de tolerancia aceptable (ej: 5 minutos)
  • Cada llamada webhook genera un timestamp y firma únicos
  • Los reintentos incluyen automáticamente nuevos timestamps
Medidas de seguridad adicionales:
  • Siempre usa URLs HTTPS para webhooks en producción
  • Almacena las claves de firma de forma segura (variables de entorno, gestión de secretos)
  • Implementa manejo adecuado de errores y logging
  • Considera implementar rate limiting para endpoints de webhook

Manejo de errores y reintentos

Sistema de reintentos automático

Comportamiento de reintentos:
  • Condiciones de activación: Códigos de respuesta distintos a 2xx o tiempo de espera de 10 segundos
  • Estrategia de espera: Espera exponencial con intervalos crecientes
  • Duración de reintentos: Hasta 3 días de intentos de reintento
  • Falla final: Notificación por correo electrónico enviada al creador del webhook
Cronología de reintentos:
  • Los reintentos iniciales ocurren rápidamente para minimizar el retraso
  • Los intervalos aumentan exponencialmente con cada intento
  • Prioriza la entrega cercana al momento del evento original
  • Seguimiento automático del estado durante todo el proceso

Opciones de reintento manual

Gestión de webhooks:
  • Ver el estado de entrega en los detalles del webhook de OpenPhone
  • Reintentar manualmente las llamadas de webhook fallidas en cualquier momento
  • Los webhooks fallidos se marcan con estado de ‘error’
  • Los reintentos manuales exitosos actualizan el estado a ‘exitoso’

Notificaciones de falla

Cuando se agotan los reintentos:
  • Alerta por correo electrónico enviada al creador del webhook
  • La llamada del webhook se marca como permanentemente fallida
  • Los detalles del evento se conservan para revisión manual
  • Opción de reintentar manualmente cuando se resuelvan los problemas

Pruebas y validación

Pruebas de desarrollo

Opciones de pruebas locales:
  • Clientes HTTP: Utiliza cURL, Postman o Insomnia para enviar solicitudes POST de prueba
  • URLs locales: Realiza pruebas contra localhost durante el desarrollo
  • Payloads simulados: Crea JSON de eventos de ejemplo que coincidan con el formato de OpenPhone
  • Pruebas de firma: Verifica la lógica de validación HMAC con claves de prueba

Funciones de prueba de OpenPhone

Solicitud de prueba integrada:
  1. Ve a la página de detalles del webhook en OpenPhone
  2. Haz clic en los puntos suspensivos (⋯) en la esquina superior derecha
  3. Selecciona “Send Test Request”
  4. OpenPhone envía un evento de ejemplo a tu URL de webhook
  5. Verifica la validación de firma y el manejo de respuestas
Las solicitudes de prueba requieren URLs de webhook accesibles públicamente. Las URLs de desarrollo local no funcionarán con la función de prueba de OpenPhone.

Pruebas de eventos en tiempo real

Disparadores de eventos reales:
  • Configura el webhook para un tipo de evento específico (p. ej., message.received)
  • Selecciona tu número de OpenPhone en los recursos del webhook
  • Dispara un evento real (envía un mensaje de texto a tu número)
  • Supervisa la entrega y procesamiento del webhook
  • Verifica la funcionalidad completa de extremo a extremo
Lista de verificación de pruebas:
  • El webhook recibe solicitudes POST correctamente
  • La verificación de firma funciona
  • El análisis del payload del evento es exitoso
  • 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:
  • Verificar que la URL del webhook sea correcta y accesible
  • Comprobar que el estado del webhook esté habilitado en la configuración
  • Confirmar que los tipos de eventos estén seleccionados correctamente
  • Validar que los recursos (números de teléfono/usuarios/grupos) sean correctos
Problemas de respuesta:
  • Códigos de estado HTTP: Asegurar respuestas 2xx para procesamiento exitoso
  • Tiempo de respuesta: Devolver respuestas dentro del límite de tiempo de 10 segundos
  • Manejo de errores: Implementar respuestas de error apropiadas para depuración
Verificación de seguridad:
  • Validación de firma: Verificar el cálculo de la firma HMAC
  • Formato de clave: Asegurar que la clave de firma esté decodificada correctamente en base64
  • Manejo de marca de tiempo: Verificar la extracción y concatenación de la marca de tiempo

Herramientas de depuración

Monitoreo de eventos:
  • Registro de eventos: Ver el historial de entrega en la página de detalles del webhook
  • Seguimiento de estado: Monitorear las tasas de éxito/fallo
  • Intentos de reintento: Revisar las secuencias de reintento automático
  • Pruebas manuales: Usar “Enviar Solicitud de Prueba” para validación inmediata
Optimización del rendimiento:
  • Tiempo de respuesta: Mantener el procesamiento por debajo de 10 segundos
  • Tasas de error: Minimizar fallas para reducir la sobrecarga de reintentos
  • Registro: Implementar registro completo de solicitudes/respuestas
  • Monitoreo: Configurar alertas para fallas de entrega de webhooks

Ejemplos de payload de eventos

Eventos de mensajes

Payload 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": "Hello",  
      "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"  
    }  
  }  
}
Payload de 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": "Have a nice day",  
      "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

Payload 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": "incoming",  
      "media": [],  
      "voicemail": null,  
      "status": "ringing",  
      "createdAt": "2022-01-23T17:07:51.116Z",  
      "answeredAt": null,  
      "completedAt": null,  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
call.completed (entrante con correo 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 respondida):
{  
  "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"  
    }  
  }  
}
Carga útil de call.recording.completed:
{  
  "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": "Agent",  
      "pictureUrl": null,  
      "fields": [  
         {  
           "name": "Phone",  
           "type": "phone-number",  
           "value": "+14155551212"  
         },  
         {  
           "name": "Email",  
           "type": "email",  
           "value": null  
         },  
         {  
           "name": "Prop1",  
           "type": "string",  
           "value": "Value12"  
         }  
      ],  
      "notes": [  
        {  
          "text": "@USu5AsEHuQ mynote 😂",  
          "enrichment": {  
            "taggedIds": {   
              "groupIds": [],   
              "userIds": [  
                "USu5AsEHuQ"  
              ],  
              "orgIds": []  
            },  
            "tokens": {   
              "USu5AsEHuQ": {   
                "token": "USu5AsEHuQ",   
                "replacement": "Tom Smith",   
                "type": "mention",   
                "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

Payload de call.summary.completed:
{  
  "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."  
      ]  
    }  
  }  
}
Payload 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.