Skip to main content

Webhooks — Eventos en tiempo real

Speaknosis envía eventos en tiempo real al endpoint configurado en tu sistema cada vez que ocurre un cambio relevante en las suscripciones o licencias de tus providers hijos.

💡 Configuración: El endpoint receptor debe ser acordado y registrado con el equipo de Speaknosis antes de comenzar. Contacta a soporte técnico para configurarlo.

↔️ Estos son los eventos que Speaknosis te envía a ti. Si tu provider opera en régimen de self-billing, además existe un webhook en sentido inverso —de tu sistema hacia Speaknosis— para notificar altas, cambios de cantidad y bajas. 👉 Ver Self-billing


Estructura general del payload​

Todos los eventos comparten la misma estructura base:

{
"type": "nombre.del.evento",
"message": {
"providerId": 1042,
...
}
}
CampoTipoDescripción
typestringIdentificador del tipo de evento.
messageobjectCuerpo del evento. Varía según el tipo.
providerIdnumberID interno del provider que originó el evento en Speaknosis.

Eventos disponibles​

EventoCuándo se dispara
subscription.contractedEl provider completa el pago de un plan por primera vez (o upgrade desde trial)
subscription.updatedEl provider cambia la cantidad de licencias, o se aplica un downgrade al renovarse
subscription.cancelledEl provider cancela la suscripción (se hará efectiva al fin del período actual)
license.associatedSe asignan uno o más usuarios a licencias
license.dissociatedSe remueven uno o más usuarios de sus licencias

Qué eventos se disparan en cada régimen de facturación​

EventoSTRIPEPARTNER_SELF (self-billing)
subscription.contractedAl confirmarse el pago en StripeAl procesarse tu checkout.session.completed
subscription.updatedAl cambiar cantidades desde el portal, o al aplicarse un downgrade programadoAl procesarse tu customer.subscription.updated con items (aunque las cantidades no cambien), y en los casos de consumo de la tabla de abajo
subscription.cancelledAl cancelar la suscripción desde el portalNo se envía: la baja la originas tú con customer.subscription.deleted
license.associated✅✅
license.dissociated✅✅

En self-billing, subscription.contracted y subscription.updated funcionan como confirmación de que el evento que enviaste se aplicó:

  • Se envían mientras se procesa tu evento, así que pueden llegarte antes que la respuesta HTTP de ese evento.
  • En un centro por cupos, subscription.updated solo incluye los planes que venían en tu evento. Por ejemplo, si enviaste BASIC: 0, el evento trae solo BASIC y no informa ELITE. Para el estado completo, consulta GET .../license.
  • Al procesarse un alta por cupos sobre un centro en período de prueba, se envía license.dissociated por los médicos que tenían licencia de prueba: esas licencias se eliminan al contratar.

Centros en modalidad por consumo​

La modalidad por consumo existe por ahora solo en self-billing (👉 ver Self-billing). Para un centro en esa modalidad, los eventos se envían así:

EventoCuándo se envía
subscription.contractedAl procesarse un alta con el ítem USAGE
subscription.updatedCuando el centro pasa de cupos a consumo, cuando cambian sus consultas incluidas, y cuando se aplica en la renovación un paso de consumo a cupos (este último, con las cantidades de cupos)
license.associatedCuando un médico recibe su licencia al abrir la herramienta de grabación, con plan: "USAGE"
license.dissociatedSolo cuando se aplica un paso de consumo a cupos, por cada médico que tenía licencia. No se envía al pasar a consumo, porque ahí los médicos no pierden el acceso

🟢 subscription.contracted​

Se dispara cuando el provider completa el pago de un plan por primera vez.

{
"type": "subscription.contracted",
"message": {
"providerId": 1042,
"licenses": [
{ "plan": "BASIC", "total": 5 },
{ "plan": "ELITE", "total": 3 }
]
}
}
CampoTipoDescripción
licensesarrayLista de planes contratados con su cantidad total de cupos.
licenses[].planstringNombre del plan: BASIC, ELITE o USAGE (modalidad por consumo).
licenses[].totalnumber | nullCantidad total de cupos contratados para ese plan. null en USAGE, porque no hay cupos.
licenses[].includedConsultationsnumberSolo en USAGE: consultas incluidas en el mínimo mensual.

Centro en modalidad por consumo:

{
"type": "subscription.contracted",
"message": {
"providerId": 1042,
"licenses": [{ "plan": "USAGE", "total": null, "includedConsultations": 300 }]
}
}

⚠️ Si tu sistema lee total como un número, tiene que aceptar null para los centros por consumo.


🔵 subscription.updated​

Se dispara cuando el provider modifica la cantidad de licencias contratadas. También se dispara al inicio de un período de renovación cuando hay un downgrade programado que se aplica.

ℹ️ En renovaciones simples (sin cambio de cantidad) no se envía este evento.

{
"type": "subscription.updated",
"message": {
"providerId": 1042,
"licenses": [
{ "plan": "BASIC", "total": 8 },
{ "plan": "ELITE", "total": 8 }
]
}
}
CampoTipoDescripción
licensesarrayLista de planes con su cantidad total vigente tras el cambio (no el delta). En self-billing por cupos, solo los planes que venían en tu evento.
licenses[].planstringNombre del plan: BASIC, ELITE o USAGE (modalidad por consumo).
licenses[].totalnumber | nullNueva cantidad total contratada para ese plan. null en USAGE.
licenses[].includedConsultationsnumberSolo en USAGE: consultas incluidas vigentes. Un cambio rige desde el próximo período.

En un centro por consumo, este evento llega con { "plan": "USAGE", "total": null, "includedConsultations": 500 } cuando el centro pasa a consumo o cambian sus consultas incluidas. Cuando se aplica un paso de consumo a cupos, llega con las cantidades de cupos, igual que en el flujo de cupos.


🟠 subscription.cancelled​

Se dispara cuando el provider cancela la suscripción. La cancelación no es inmediata: se hace efectiva al final del período de facturación actual (hasta esa fecha los doctores mantienen su acceso).

{
"type": "subscription.cancelled",
"message": {
"providerId": 1042,
"cancelAt": "2026-09-01 00:00:00"
}
}
CampoTipoDescripción
cancelAtstringFecha y hora (UTC) en que la cancelación se hace efectiva (fin del período actual).

🟡 license.associated​

Se dispara cuando se asignan uno o más usuarios a licencias. Puede incluir múltiples usuarios en un solo evento si la asignación fue en lote.

{
"type": "license.associated",
"message": {
"providerId": 1042,
"users": [
{ "userId": "tu-id-externo-1", "plan": "BASIC" },
{ "userId": "tu-id-externo-2", "plan": "ELITE" }
]
}
}
CampoTipoDescripción
usersarrayLista de usuarios que recibieron una licencia.
users[].userIdstringID externo del usuario en tu sistema (el id enviado al registrar el doctor).
users[].planstringPlan asignado: BASIC, ELITE, TRIAL o USAGE.

ℹ️ En un centro por consumo, la licencia se crea cuando el médico abre la herramienta de grabación por primera vez, y el evento llega con plan: "USAGE".


🔴 license.dissociated​

Se dispara cuando se remueve uno o más usuarios de sus licencias. Puede incluir múltiples usuarios en un solo evento si la remoción fue en lote.

{
"type": "license.dissociated",
"message": {
"providerId": 1042,
"users": [{ "userId": "tu-id-externo-1" }, { "userId": "tu-id-externo-2" }]
}
}
CampoTipoDescripción
usersarrayLista de usuarios que perdieron su licencia.
users[].userIdstringID externo del usuario en tu sistema.

ℹ️ Este evento no incluye el plan que tenía el usuario, ya que la licencia fue liberada.

ℹ️ En un centro por consumo, este evento solo se envía cuando se aplica un paso a cupos: los médicos quedan sin licencia hasta que el administrador les asigna un cupo. Al pasar a consumo no se envía, porque los médicos no pierden el acceso.


Consideraciones de implementación​

  • Tu endpoint debe responder con HTTP 2xx para confirmar la recepción.
  • En self-billing, la confirmación de un evento que enviaste puede llegarte antes que la respuesta HTTP de ese evento. No dependas de ese orden.
  • El endpoint debe estar disponible públicamente y soportar solicitudes POST con Content-Type: application/json.
  • Si el endpoint no responde o devuelve un error, Speaknosis no reintenta el envío automáticamente.
  • El campo providerId siempre hace referencia al provider que originó el evento, útil si un mismo endpoint recibe eventos de múltiples providers hijos.