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 se producen eventos específicos en tu espacio de trabajo de OpenPhone. Cuando se dispara un evento, OpenPhone envía una solicitud HTTP POST a la URL que especifiques con datos detallados del evento.
Flujo de trabajo típico:
- Configura la URL del webhook y selecciona los tipos de evento
- OpenPhone monitoriza los eventos especificados
- Cuando ocurre un evento, OpenPhone envía una solicitud POST con el payload del evento
- Tu aplicación procesa los datos del evento y responde
- OpenPhone registra el estado de la entrega y reintenta si es necesario
La configuración de webhooks requiere permisos de propietario o administrador del espacio de trabajo. Estos ajustes se gestionan únicamente desde las aplicaciones web y de escritorio.
Interfaz de configuración de webhooks:
Eventos de webhook disponibles
Notificaciones de SMS:
message.received
: SMS recibido por el número del espacio de trabajo (incluye archivos multimedia)
message.delivered
: SMS enviado desde el espacio de trabajo y entregado correctamente (incluye multimedia)
Información con IA:
call.summary.completed
: Resumen de llamada generado por IA disponible en la carga del evento
call.transcript.completed
: Transcripción completa de la llamada disponible en la carga del evento
Notificaciones del estado de la llamada:
call.ringing
: Llamada entrante que está siendo recibida por el número del espacio de trabajo
call.completed
: Llamada finalizada (contestada o no; puede incluir buzón de voz)
call.recording.completed
: Grabación de la llamada disponible en la URL proporcionada
Gestión de contactos:
contact.updated
: Contacto creado o modificado en el espacio de trabajo
contact.deleted
: Contacto eliminado del espacio de trabajo
Configuración de webhooks
Requisitos de configuración
Parámetros obligatorios:
Parámetro | Requisito | Descripción |
---|
URL | Obligatorio | Endpoint del controlador de webhooks (se recomienda firmemente usar HTTPS en producción) |
Tipos de evento | 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 gestionar el webhook |
Para crear un webhook:
- Ve a Settings → Webhooks en OpenPhone
- Haz clic en Create webhook
- Ingresa la URL de tu controlador de webhooks
- Selecciona los tipos de eventos a supervisar
- Elige los números de teléfono o los recursos de contactos
- Agrega una etiqueta opcional para su identificación
- Guarda y prueba la configuración
Creación de controladores de webhooks
Requisitos del controlador
Especificaciones técnicas:
- Aceptar solicitudes HTTP POST en tu URL de webhook
- Procesar la carga útil de eventos JSON en el cuerpo de la solicitud
- Responder con un código de estado HTTP 2xx dentro de 10 segundos
- Verificar la firma del webhook por seguridad
- Manejar reintentos y errores de forma adecuada
Manejo de respuestas:
- Éxito: Devolver código de estado 2xx (no se requiere cuerpo de respuesta)
- Error: Una respuesta no 2xx activa la secuencia de reintentos de OpenPhone
- Tiempo de espera: Si no hay respuesta dentro de 10 segundos, se inician reintentos
Flexibilidad de desarrollo:
Los controladores de webhooks pueden desarrollarse en cualquier lenguaje de programación que admita solicitudes y respuestas HTTP. Despliega en plataformas en la nube, servidores o funciones serverless.
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 prevenir ataques de suplantación.
Formato del encabezado de la firma:
'openphone-signature': 'hmac;1;1639710054089;mw1K4fvh5m9XzsGon4C5N3KvL0bkmPZSAy
b/9Vms2Qo='
Estructura de la firma:
<scheme>;<version>;<timestamp>;<signature>
Component | Description | Current Value |
---|
scheme | Algoritmo de firma | Always “hmac” |
version | Versión de la firma | Always “1” |
timestamp | Momento de generación de la firma | Unix timestamp |
signature | Firma HMAC codificada en Base64 | Resumen SHA256 |
Proceso de verificación de la firma
Pasos de verificación:
- Extraer los componentes del encabezado
openphone-signature
- Preparar los datos firmados concatenando
timestamp + "." + payload
- Decodificar la clave de firma desde base64 (disponible en los detalles del webhook)
- Calcular HMAC-SHA256 usando la clave decodificada y los datos firmados
- Comparar el resultado con la firma del encabezado
Requisitos importantes:
- Eliminar todos los espacios y saltos de línea del payload JSON antes de la concatenación
- Usar la forma binaria de la clave de firma decodificada de base64 para el cálculo del HMAC
- Garantizar coincidencia exacta de cadenas para que la verificación sea exitosa
Cómo obtener tu clave de firma:
- Ve a la página de detalles del webhook en OpenPhone
- Haz clic en los puntos suspensivos (⋯) en la parte 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 is from "Reveal Signing Secret" in the OpenPhone app.
const signingKey = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='
// Parse the fields from the openphone-signature header.
const signature = req.headers['openphone-signature']
const fields = signature.split(';')
const timestamp = fields[2]
const providedDigest = fields[3]
// Compute the data covered by the signature.
const signedData = timestamp + '.' + JSON.stringify(req.body)
// Convert the base64-encoded signing key to binary.
const signingKeyBinary = Buffer.from(signingKey, 'base64').toString('binary')
// Compute the SHA256 HMAC digest.
const computedDigest = crypto.createHmac('sha256',signingKeyBinary)
.update(Buffer.from(signedData,'utf8'))
.digest('base64')
// Verify signature matches
if (providedDigest === computedDigest) {
console.log(`signature verification succeeded`)
// Process webhook event here
} else {
console.log(`signature verification failed`)
return res.status(401).send('Unauthorized')
}
res.send({})
});
app.listen(port, function () {
console.log(`webhook server listening on port ${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 proviene de "Reveal Signing Secret" en la app de OpenPhone.
signing_key = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='
# Analiza los campos del encabezado openphone-signature.
signature = request.headers['openphone-signature']
fields = signature.split(';')
timestamp = fields[2]
provided_digest = fields[3]
# Calcula los datos cubiertos por la firma como bytes.
signed_data_bytes = b''.join([timestamp.encode(), b'.', request.data])
# Convierte la clave de firma codificada en base64 a bytes.
signing_key_bytes = base64.b64decode(signing_key)
# Calcula el digest HMAC con SHA256.
hmac_object = hmac.new(signing_key_bytes, signed_data_bytes, 'sha256')
computed_digest = base64.b64encode(hmac_object.digest()).decode()
# Verifica que la firma coincida
if provided_digest == computed_digest:
print('verificación de firma exitosa')
# Procesa aquí el evento del webhook
else:
print('falló la verificación de la firma')
return jsonify({'error': 'Unauthorized'}), 401
return jsonify({})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Es posible que las versiones futuras incluyan varias firmas separadas por comas. Si es necesario, divide el valor del encabezado por comas para manejar múltiples firmas.
Mejores prácticas de seguridad
Protección contra ataques de repetición (replay):
Implementa la validación de la marca 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, gestores de secretos)
- Implementa un manejo adecuado de errores y registros (logging)
- Considera aplicar limitación de tasa (rate limiting) para los endpoints de webhooks
Gestión de errores y reintentos
Sistema de reintentos automático
Comportamiento de reintento:
- Condiciones de activación: Códigos de respuesta no 2xx o tiempo de espera de 10 segundos
- Estrategia de backoff: Backoff 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 reintentos iniciales ocurren rápidamente para minimizar la demora
- Las demoras 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
Gestión de webhooks:
- Ver el estado de entrega en los detalles del webhook en OpenPhone
- 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”
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 fallida de forma permanente
- Los detalles del evento se conservan para revisión manual
- Opción de reintentar manualmente cuando se resuelvan los problemas
Opciones de pruebas locales:
- Clientes HTTP: Usa cURL, Postman o Insomnia para enviar solicitudes POST de prueba
- URL locales: Prueba contra localhost durante el desarrollo
- Payloads simulados (mock): Crea JSON de eventos de muestra que coincida 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:
- Ve a la página de detalles del webhook en OpenPhone
- Haz clic en los puntos suspensivos (⋯) en la parte superior derecha
- Selecciona “Send Test Request”
- OpenPhone 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 funcionarán con la función de prueba de OpenPhone.
Pruebas con eventos en vivo
Desencadenadores 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
- Genera un evento real (envía un mensaje de texto 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 la URL del webhook que 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 los recursos (números de teléfono/usuarios/grupos) que sean correctos
Problemas de respuesta:
- Códigos de estado HTTP: Asegúrate de devolver 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: Asegúrate de 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
Monitoreo 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
- Intentos de reintento: Revisa las secuencias de reintentos automáticos
- Pruebas manuales: Usa “Send Test Request” para validación inmediata
Optimización del rendimiento:
- Tiempo de respuesta: Mantén el procesamiento por debajo de 10 segundos
- Tasas de error: Minimiza los fallos para reducir la carga de reintentos
- Registro: Implementa un registro integral de solicitudes/respuestas
- Monitoreo: Configura alertas para fallas en la entrega de webhooks
Ejemplos de cargas útiles de eventos
Carga útil 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"
}
}
}
Carga útil 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"
}
}
}
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": "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
(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
(carga útil):
{
"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"
}
}
}
Carga útil 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 con IA
Carga útil 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 los 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": "hello world",
"identifier": "+12345678901"
},
{
"end": 1.52,
"start": 1.12,
"userId": "USiAYGldYE",
"content": "hi there",
"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.