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

Eventos de webhook disponibles

Eventos de mensajería

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

Eventos de voz

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

Eventos de contactos

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ámetroRequisitoDescripción
URLObligatorioEndpoint del controlador de webhooks (se recomienda firmemente usar HTTPS en producción)
Tipos de eventoObligatorioSelecciona 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 gestionar el webhook

Proceso de configuración

Para crear un webhook:
  1. Ve a SettingsWebhooks en OpenPhone
  2. Haz clic en Create webhook
  3. Ingresa la URL de tu controlador de webhooks
  4. Selecciona los tipos de eventos a supervisar
  5. Elige los números de teléfono o los recursos de contactos
  6. Agrega una etiqueta opcional para su identificación
  7. 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>
ComponentDescriptionCurrent Value
schemeAlgoritmo de firmaAlways “hmac”
versionVersión de la firmaAlways “1”
timestampMomento de generación de la firmaUnix timestamp
signatureFirma HMAC codificada en Base64Resumen SHA256

Proceso de verificación de la firma

Pasos de verificación:
  1. Extraer los componentes del encabezado openphone-signature
  2. Preparar los datos firmados concatenando timestamp + "." + payload
  3. Decodificar la clave de firma desde base64 (disponible en los detalles del webhook)
  4. Calcular HMAC-SHA256 usando la clave decodificada y los datos firmados
  5. 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:
  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 “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 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”

Notificaciones de fallos

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

Pruebas y validación

Pruebas de desarrollo

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:
  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 “Send Test Request”
  4. OpenPhone 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 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:
  • [ ] 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 adecuadamente
  • [ ] Las respuestas de error activan reintentos
  • [ ] Las respuestas satisfactorias detienen la secuencia de reintentos

Solución de problemas

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

Herramientas de depuración

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

Eventos de mensajes

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

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

Eventos de contacto

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

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.