Skip to content

Webhooks ​

Generado desde DialerDigital/core commit 0fc18008b3c8 — especificación openapi/dialer-v1.yaml v0.134.0. No se edita a mano; consulta Cómo se construye esta referencia. Descargar la especificación OpenAPI.

English version

Traducción en curso

Esta referencia se genera desde la especificación OpenAPI. Las cadenas que todavía no tienen traducción aparecen en inglés.

Webhooks de salida para CRM: el registro de endpoints del tenant, sus eventos suscritos y el secreto de firma (se muestra una vez; rotación con ventana).

Endpoints ​

MétodoRutaResumen
GET/v1/webhooksEndpoints de webhook de salida
POST/v1/webhooksRegistrar un endpoint de webhook de salida
GET/v1/webhooks/{id}Obtener un endpoint
PATCH/v1/webhooks/{id}Actualizar url / events / enabled
DELETE/v1/webhooks/{id}Deshabilitar un endpoint (las filas nunca se borran)
POST/v1/webhooks/{id}/rotate-secretRotar el secreto de firma (ventana de solapamiento de 24 h)
GET/v1/webhooks/{id}/deliveriesEntregas de un endpoint, con cada intento
POST/v1/webhooks/{id}/deliveries/{delivery_id}/redeliverEncolar un intento más para una entrega

Endpoints de webhook de salida ​

http
GET /v1/webhooks

Todos los endpoints del tenant, habilitados o no, del más antiguo al más reciente. Las respuestas llevan secret_hint (los últimos 4 caracteres del secreto de firma VIGENTE) y NUNCA el secreto. Alcance de tenant (clave dd_ o un login de dashboard); un token de widget ddw_ da 403.

Respuestas

200 ​

Los endpoints del tenant.

CampoTipoObligatorioDescripción
webhooksarreglo de WebhookEndpointsí
webhooks[].idstring (uuid)no
webhooks[].urlstringno
webhooks[].eventsarreglo de WebhookEventNameno
webhooks[].enabledbooleanno
webhooks[].disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhooks[].secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhooks[].previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhooks[].created_atstring (date-time)no
webhooks[].updated_atstring (date-time)no

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Registrar un endpoint de webhook de salida ​

http
POST /v1/webhooks

Emite el secreto de firma HMAC del endpoint y lo devuelve una sola vez, como secret de primer nivel de esta respuesta: nunca se puede volver a leer (las lecturas muestran secret_hint; POST /v1/webhooks/{id}/rotate-secret emite uno nuevo). El secreto se almacena cifrado por sobre (envelope) por tenant.

Límites (422 unprocessable). Un tenant puede tener como máximo 20 endpoints habilitados (cada evento escribe una fila de entrega por endpoint); la llave de catálogo webhook_max_endpoints lo sube por tenant. Los endpoints deshabilitados no cuentan, y un tenant que ya supera el tope sigue entregando a los que tiene.

url DEBE ser una URL https:// en el puerto 443 cuyo host se resuelva solo a direcciones PÚBLICAS: un literal de IP privada, loopback, RFC1918, CGNAT, enlace local / metadatos de la nube y un nombre que no se resuelve se rechazan con 400 que nombra url (protección SSRF, fail-closed). La protección juzga solo las direcciones resueltas: un nombre .internal o de clúster se rechaza por dónde se resuelve, nunca por su sufijo. La misma comprobación se repite en el momento de la entrega. events es un subconjunto no vacío del catálogo cerrado v1 (call.ended, call.answered, promise.recorded, sms.received, sms.optout, recording.available, disposition.event, carrier.health_changed, anomaly.detected, ai.drift.detected); un evento desconocido o repetido da 400. recording.available se acepta ya y se emitirá cuando exista su productor.

Todavía no se entrega nada: el outbox y el despachador son las siguientes fases de MT-CTI-04. tenant_id y secret NO son campos de la petición: un cuerpo que lleve cualquiera de los dos da 400 que lo nombra.

Rol. Escribir en el registro necesita una clave dd_ o un login de dashboard con el rol account_admin. Un login member recibe 403 role_forbidden (antes de validar el cuerpo; no se escribe nada) una vez que la llave de catálogo del tenant webhook_admin_role_enforced está activada. Nace apagada: hasta entonces un member escribe como antes y la plataforma solo lo cuenta (dialer.webhooks.admin_role_would_deny).

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
urlstringsíhttps:// en el 443, solo host público (véase arriba).
eventsarreglo de WebhookEventNamesí
json
{
  "url": "https://crm.example.com/hooks/dialerdigital",
  "events": [
    "call.ended",
    "promise.recorded"
  ]
}

Respuestas

201 ​

El endpoint, más el secreto de firma: la PRIMERA de las dos únicas veces que se muestra.

CampoTipoObligatorioDescripción
webhookobject (WebhookEndpoint)síUn endpoint de webhook de salida. NUNCA lleva el secreto de firma: secret_hint son los últimos 4 caracteres del secreto VIGENTE, suficientes para saber cuál tiene el CRM. tenant_id va implícito en el bearer.
webhook.idstring (uuid)no
webhook.urlstringno
webhook.eventsarreglo de WebhookEventNameno
webhook.enabledbooleanno
webhook.disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhook.secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhook.previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhook.created_atstring (date-time)no
webhook.updated_atstring (date-time)no
secretstringsíSecreto HMAC con prefijo whsec_. Se muestra UNA vez; guárdalo ahora.

400 — BadRequest ​

Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

422 — Unprocessable ​

La forma es correcta, la semántica no (regla de dominio incumplida).

json
{
  "error": {
    "code": "unprocessable",
    "message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Obtener un endpoint ​

http
GET /v1/webhooks/{id}

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Respuestas

200 ​

El endpoint (secret_hint, nunca el secreto).

CampoTipoObligatorioDescripción
webhookobject (WebhookEndpoint)síUn endpoint de webhook de salida. NUNCA lleva el secreto de firma: secret_hint son los últimos 4 caracteres del secreto VIGENTE, suficientes para saber cuál tiene el CRM. tenant_id va implícito en el bearer.
webhook.idstring (uuid)no
webhook.urlstringno
webhook.eventsarreglo de WebhookEventNameno
webhook.enabledbooleanno
webhook.disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhook.secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhook.previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhook.created_atstring (date-time)no
webhook.updated_atstring (date-time)no

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Actualizar url / events / enabled ​

http
PATCH /v1/webhooks/{id}

url y events repiten la misma validación que la creación. enabled: true vuelve a habilitar un endpoint que DELETE deshabilitó y borra disabled_reason. El secreto NO es un campo aquí: la rotación es su propio verbo, de modo que ninguna escritura puede reemplazar un secreto sin devolver el nuevo. Las claves desconocidas dan 400.

enabled: true cuenta como una creación: pasados webhook_max_endpoints endpoints habilitados da 422.

Rol. Escribir en el registro necesita una clave dd_ o un login de dashboard con el rol account_admin. Un login member recibe 403 role_forbidden (antes de validar el cuerpo; no se escribe nada) una vez que la llave de catálogo del tenant webhook_admin_role_enforced está activada. Nace apagada: hasta entonces un member escribe como antes y la plataforma solo lo cuenta (dialer.webhooks.admin_role_would_deny).

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
urlstringno
eventsarreglo de WebhookEventNameno
enabledbooleanno
json
{
  "events": [
    "call.ended",
    "call.answered",
    "sms.received"
  ]
}

Respuestas

200 ​

El endpoint actualizado.

CampoTipoObligatorioDescripción
webhookobject (WebhookEndpoint)síUn endpoint de webhook de salida. NUNCA lleva el secreto de firma: secret_hint son los últimos 4 caracteres del secreto VIGENTE, suficientes para saber cuál tiene el CRM. tenant_id va implícito en el bearer.
webhook.idstring (uuid)no
webhook.urlstringno
webhook.eventsarreglo de WebhookEventNameno
webhook.enabledbooleanno
webhook.disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhook.secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhook.previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhook.created_atstring (date-time)no
webhook.updated_atstring (date-time)no

400 — BadRequest ​

Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

422 — Unprocessable ​

La forma es correcta, la semántica no (regla de dominio incumplida).

json
{
  "error": {
    "code": "unprocessable",
    "message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Deshabilitar un endpoint (las filas nunca se borran) ​

http
DELETE /v1/webhooks/{id}

Fija enabled: false con disabled_reason: "disabled_by_api" y conserva la fila: es la evidencia del tenant de lo que se suscribió y cuándo, y el rol de la aplicación no tiene permiso DELETE. Idempotente: un segundo DELETE conserva el motivo original. PATCH {"enabled": true} lo recupera.

Rol. Escribir en el registro necesita una clave dd_ o un login de dashboard con el rol account_admin. Un login member recibe 403 role_forbidden (antes de validar el cuerpo; no se escribe nada) una vez que la llave de catálogo del tenant webhook_admin_role_enforced está activada. Nace apagada: hasta entonces un member escribe como antes y la plataforma solo lo cuenta (dialer.webhooks.admin_role_would_deny).

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Respuestas

200 ​

El endpoint, ahora deshabilitado.

CampoTipoObligatorioDescripción
webhookobject (WebhookEndpoint)síUn endpoint de webhook de salida. NUNCA lleva el secreto de firma: secret_hint son los últimos 4 caracteres del secreto VIGENTE, suficientes para saber cuál tiene el CRM. tenant_id va implícito en el bearer.
webhook.idstring (uuid)no
webhook.urlstringno
webhook.eventsarreglo de WebhookEventNameno
webhook.enabledbooleanno
webhook.disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhook.secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhook.previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhook.created_atstring (date-time)no
webhook.updated_atstring (date-time)no

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Rotar el secreto de firma (ventana de solapamiento de 24 h) ​

http
POST /v1/webhooks/{id}/rotate-secret

Emite un secreto nuevo y lo devuelve una sola vez (secret de primer nivel). El secreto anterior sigue verificando hasta previous_secret_expires_at (24 h): las entregas en esa ventana se firman con el secreto nuevo y llevan una segunda firma con el anterior, de modo que un receptor que aún no ha cambiado sigue verificando. Solo los dos últimos secretos verifican: una rotación dentro de una ventana abierta reemplaza al anterior. Sin cuerpo de petición.

Rol. Escribir en el registro necesita una clave dd_ o un login de dashboard con el rol account_admin. Un login member recibe 403 role_forbidden (antes de validar el cuerpo; no se escribe nada) una vez que la llave de catálogo del tenant webhook_admin_role_enforced está activada. Nace apagada: hasta entonces un member escribe como antes y la plataforma solo lo cuenta (dialer.webhooks.admin_role_would_deny).

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Respuestas

200 ​

El endpoint (nuevo secret_hint, ventana abierta) más el secreto NUEVO: la segunda y última vez que se muestra un secreto.

CampoTipoObligatorioDescripción
webhookobject (WebhookEndpoint)síUn endpoint de webhook de salida. NUNCA lleva el secreto de firma: secret_hint son los últimos 4 caracteres del secreto VIGENTE, suficientes para saber cuál tiene el CRM. tenant_id va implícito en el bearer.
webhook.idstring (uuid)no
webhook.urlstringno
webhook.eventsarreglo de WebhookEventNameno
webhook.enabledbooleanno
webhook.disabled_reasonstring | nullnodisabled_by_api tras un DELETE; null mientras está habilitado o tras PATCH {"enabled": false} (esa vía no registra motivo).
webhook.secret_hintstringnoÚltimos 4 caracteres del secreto de firma vigente.
webhook.previous_secret_expires_atstring (date-time) | nullnoFin de la ventana de rotación en la que el secreto anterior aún verifica; null = ningún secreto anterior verifica.
webhook.created_atstring (date-time)no
webhook.updated_atstring (date-time)no
secretstringsíEl secreto whsec_ NUEVO. Se muestra UNA vez.

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Entregas de un endpoint, con cada intento ​

http
GET /v1/webhooks/{id}/deliveries

La evidencia de solo anexar de lo que se envió a este endpoint y lo que respondió, las más recientes primero, paginada por keyset. Un tenant que discute con un CRM («nunca lo recibimos») responde con esto: cada entrega lleva sus attempts en orden, y cada intento el status_code HTTP, los primeros ≤ 1 KiB del CUERPO de la respuesta (response_excerpt, nunca las cabeceras) y los instantes en que la petición empezó y terminó.

status se DERIVA de los intentos, no se almacena: pending cuando aún no se hizo ningún intento, y si no el outcome del ÚLTIMO (retry, delivered, deadletter, endpoint_disabled). El sobre del evento NO forma parte de una fila: data es la proyección pública /v1 del recurso y se puede leer con el propio GET del recurso.

Filtros: event (un nombre del catálogo v1), status (uno de los cinco de arriba), since (RFC-3339, límite inferior inclusivo sobre el created_at de la entrega). Un valor desconocido de event o status da 400 que nombra el conjunto aceptado, nunca una página vacía en silencio. Alcance de tenant (clave dd_ o un login de dashboard); un token de widget ddw_ da 403, y un endpoint de otro tenant da 404.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
eventquerycall.ended · call.answered · promise.recorded · sms.received · sms.optout · recording.available · disposition.event · carrier.health_changed · anomaly.detected · ai.drift.detectedno
statusquerypending · retry · delivered · deadletter · endpoint_disabledno
sincequerystring (date-time)noSolo las entregas creadas en este instante o después.
limitqueryintegerno
cursorquerystringnoCursor keyset opaco: el next_cursor de la página anterior.

Respuestas

200 ​

Una página de entregas, las más recientes primero.

CampoTipoObligatorioDescripción
deliveriesarreglo de WebhookDeliverysí
deliveries[].idstring (uuid)no
deliveries[].endpoint_idstring (uuid)no
deliveries[].eventcall.ended · call.answered · promise.recorded · sms.received · sms.optout · recording.available · disposition.event · carrier.health_changed · anomaly.detected · ai.drift.detectednoEl catálogo cerrado v1 de eventos suscribibles (ADR #121).
deliveries[].event_idstringnoClave de idempotencia con la que el receptor deduplica; única por endpoint.
deliveries[].occurred_atstring (date-time)noCuándo ocurrió el HECHO (no cuándo se envió).
deliveries[].created_atstring (date-time)noCuándo se escribió la entrega, en la propia transacción del hecho.
deliveries[].statuspending · retry · delivered · deadletter · endpoint_disablednoDERIVADO, nunca almacenado: pending cuando la entrega aún no tiene intentos, y si no el outcome de su ÚLTIMO intento.
deliveries[].attemptsarreglo de WebhookDeliveryAttemptnoCada intento, del más antiguo al más reciente. Vacío mientras la entrega está pending.
deliveries[].attempts[].attemptintegerno
deliveries[].attempts[].outcomeretry · delivered · deadletter · endpoint_disabledno
deliveries[].attempts[].reasonstringnoVocabulario cerrado: ok, egress_refused, endpoint_gone, endpoint_not_enabled, payload_too_large, unexpected_status, timeout, transport, protocol, body_too_large, exception, y dos que no producen envío: manual_redelivery (el operador pidió uno mediante el endpoint de reentrega) y endpoint_circuit_open (el cortacircuitos del endpoint estaba abierto, así que la entrega se devolvió a la cola para cuando termine; no cuenta como intento).
deliveries[].attempts[].status_codeinteger | nullnoEl estado HTTP del receptor; null cuando no llegó respuesta.
deliveries[].attempts[].started_atstring (date-time)no
deliveries[].attempts[].finished_atstring (date-time)no
deliveries[].attempts[].next_attempt_atstring (date-time) | nullnoCuándo toca el siguiente intento; no nulo solo en retry.
deliveries[].attempts[].response_excerptstring | nullnoPrimeros bytes del CUERPO de la respuesta (≤ 1 KiB). Nunca las cabeceras.
next_cursorstring | nullsíCursor de la página siguiente; null en la última.

400 — BadRequest ​

Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Encolar un intento más para una entrega ​

http
POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver

AÑADE un intento retry debido ahora (reason: "manual_redelivery") al flujo de la entrega; el siguiente tick del despachador lo envía. Nada de lo ya registrado se edita: los intentos son de solo anexar en la propia base de datos, de modo que la evidencia en la que se apoya una disputa no se puede reescribir, ni siquiera la plataforma.

202, no 200: el envío ocurre en el siguiente tick del despachador, y su resultado aparece como el SIGUIENTE intento en GET /v1/webhooks/{id}/deliveries. La respuesta lleva el intento que se encoló.

La numeración continúa, así que una entrega que ya agotó la escalera de reintentos recibe exactamente UN envío más: un fallo después de eso se registra como deadletter, no como otro retry.

Un endpoint deshabilitado da 409 que nombra por qué se deshabilitó: el 410 Gone que respondió su receptor (endpoint_gone_410) o la llamada a la API (disabled_by_api); vuelve a habilitarlo antes con PATCH {"enabled": true}. También 409 si se añadió otro intento al mismo tiempo (dos reentregas en carrera, o una en carrera con el despachador): vuelve a leer la entrega antes de pedir otra. Sin cuerpo de petición.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
delivery_idpathstring (uuid)sí

Respuestas

202 ​

El intento encolado para el siguiente tick del despachador.

CampoTipoObligatorioDescripción
delivery_idstring (uuid)sí
statusretrysíEl estado derivado de la entrega tras la anexión.
attemptobject (WebhookDeliveryAttempt)síUn intento de enviar una entrega. De solo anexar: nunca se edita ni se borra.
attempt.attemptintegerno
attempt.outcomeretry · delivered · deadletter · endpoint_disabledno
attempt.reasonstringnoVocabulario cerrado: ok, egress_refused, endpoint_gone, endpoint_not_enabled, payload_too_large, unexpected_status, timeout, transport, protocol, body_too_large, exception, y dos que no producen envío: manual_redelivery (el operador pidió uno mediante el endpoint de reentrega) y endpoint_circuit_open (el cortacircuitos del endpoint estaba abierto, así que la entrega se devolvió a la cola para cuando termine; no cuenta como intento).
attempt.status_codeinteger | nullnoEl estado HTTP del receptor; null cuando no llegó respuesta.
attempt.started_atstring (date-time)no
attempt.finished_atstring (date-time)no
attempt.next_attempt_atstring (date-time) | nullnoCuándo toca el siguiente intento; no nulo solo en retry.
attempt.response_excerptstring | nullnoPrimeros bytes del CUERPO de la respuesta (≤ 1 KiB). Nunca las cabeceras.

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict ​

Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.