Referencia de la API

Webhook endpoints

Alta, baja, prueba, rotación de secreto, entregas y re-entrega.

Endpoints para administrar tus destinos de webhook y auditar las entregas. El formato del evento y cómo validarlo están en Eventos y payload y Verificar la firma.

Los mismos endpoints existen en el dashboard con autenticación de usuario: es la misma lógica y el mismo shape de respuesta, así que lo que hagas por API se ve en el panel y viceversa.

El objeto endpoint

CampoTipoRequeridoDescripción
idstringsiemprewhep_…
objectstringsiempreConstante "webhook_endpoint".
urlstringsiempreDestino https al que se hace el POST.
eventsstring[]siempreTipos de evento suscriptos.
statusenumsiempre"active" o "disabled".
descriptionstringsi se envióNota tuya para identificarlo. 1 a 200 caracteres.
secretstringsiempreEl secreto de firma. Completo sólo al crear y al rotar; en cualquier otra respuesta viene enmascarado como whsec_...abcd.
previous_secret_expires_atstringsólo al rotarHasta cuándo se firma también con el secreto anterior.
created_atstringsiempreISO 8601 UTC.
updated_atstringsiempreISO 8601 UTC.

Registrar un endpoint

POST/v1/webhook-endpointsAPI key · webhooks:write
CampoTipoRequeridoDescripción
urlstringURL https pública. Se rechazan: http, credenciales embebidas (https://user:pass@…), hosts internos (localhost, *.internal) e IPs privadas, de loopback o link-local.
eventsstring[]De 1 a 20 tipos de evento. Ver la lista en Eventos y payload.
descriptionstringno1 a 200 caracteres.
curl -sS -X POST "$KYCAR_API/v1/webhook-endpoints" \
  -H "Authorization: Bearer $KYCAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.tu-empresa.com/hooks/kycar",
    "events": [
      "verification_session.approved",
      "verification_session.rejected",
      "verification_session.review_required"
    ],
    "description": "Backend de onboarding"
  }'

201 Created:

{
  "id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "object": "webhook_endpoint",
  "url": "https://api.tu-empresa.com/hooks/kycar",
  "events": [
    "verification_session.approved",
    "verification_session.rejected",
    "verification_session.review_required"
  ],
  "status": "active",
  "description": "Backend de onboarding",
  "secret": "whsec_kR7mQ2vLxK9sYdN1bTfR4mHc8jWgE3aZ",
  "created_at": "2026-08-19T14:10:02.117Z",
  "updated_at": "2026-08-19T14:10:02.117Z"
}

Listar endpoints

GET/v1/webhook-endpointsAPI key · webhooks:read

Acepta limit y cursor. El secret viene enmascarado.

{
  "data": [
    {
      "id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
      "object": "webhook_endpoint",
      "url": "https://api.tu-empresa.com/hooks/kycar",
      "events": ["verification_session.approved"],
      "status": "active",
      "secret": "whsec_...E3aZ",
      "created_at": "2026-08-19T14:10:02.117Z",
      "updated_at": "2026-08-19T14:10:02.117Z"
    }
  ],
  "has_more": false
}

Actualizar un endpoint

PUT/v1/webhook-endpoints/{id}API key · webhooks:write
CampoTipoRequeridoDescripción
urlstringnoMismas reglas que al crear. Cambiar la URL resetea el circuit breaker del endpoint.
eventsstring[]noReemplaza la lista completa. De 1 a 20.
statusenumno"active" o "disabled".
descriptionstringno1 a 200 caracteres.

Tenés que mandar al menos un campo; un body vacío devuelve 422. Es un patch parcial pese al verbo: los campos que no mandás quedan como están.

curl -sS -X PUT "$KYCAR_API/v1/webhook-endpoints/whep_01K2M4P6R8T0V2X4Z6B8D0F2H4" \
  -H "Authorization: Bearer $KYCAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "disabled"}'

Eliminar un endpoint

DELETE/v1/webhook-endpoints/{id}API key · webhooks:write

Devuelve 204 sin cuerpo. Si sólo querés cortar entregas de forma temporal, preferí status: "disabled".


Probar un endpoint

POST/v1/webhook-endpoints/{id}/testAPI key · webhooks:write

Encola una entrega de prueba real: sale por el mismo camino y con la misma firma que un evento de producción, así podés validar tu verificación de firma antes de tener una sesión.

curl -sS -X POST "$KYCAR_API/v1/webhook-endpoints/whep_01K2M4P6R8T0V2X4Z6B8D0F2H4/test" \
  -H "Authorization: Bearer $KYCAR_API_KEY"

202 Accepted:

{
  "object": "webhook_test",
  "endpoint_id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "event_id": "evt_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "queued": true
}

El cuerpo que recibe tu endpoint:

{
  "id": "evt_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "object": "event",
  "api_version": "v1",
  "type": "test",
  "created_at": "2026-08-19T14:31:08.442Z",
  "data": {
    "object": {
      "message": "Webhook de prueba de kyc.ar: si leés esto, la firma y la entrega funcionan.",
      "webhook_id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4"
    }
  }
}
CampoTipoStatusDescripción
202Encolada202La entrega es asíncrona: el resultado queda en el log de entregas.
conflict409409El endpoint está disabled. Activalo antes de probarlo.
not_found404404El endpoint no existe.
rate_limited429429Más de 10 pruebas o re-entregas por minuto en la cuenta.

Las pruebas ignoran el circuit breaker a propósito —son una herramienta de diagnóstico— y tampoco lo alimentan.


Rotar el secreto de firma

POST/v1/webhook-endpoints/{id}/roll-secretAPI key · webhooks:write

Genera un secreto nuevo y devuelve el valor completo una sola vez.

{
  "id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "object": "webhook_endpoint",
  "url": "https://api.tu-empresa.com/hooks/kycar",
  "events": ["verification_session.approved"],
  "status": "active",
  "secret": "whsec_2vLxK9sYdN1bTfR4mHc8jWgE3aZkR7mQ",
  "previous_secret_expires_at": "2026-08-20T14:33:51.000Z",
  "created_at": "2026-08-19T14:10:02.117Z",
  "updated_at": "2026-08-19T14:33:51.204Z"
}

Durante 24 horas cada entrega se firma con los dos secretos: v1 con el nuevo y v2 con el anterior. Eso te deja rotar sin ventana de caída:

  1. Rotás y guardás el secreto nuevo junto al viejo.
  2. Tu verificador acepta el evento si cualquiera de las firmas del header valida contra alguno de tus dos secretos.
  3. Antes de previous_secret_expires_at, borrás el viejo.

El detalle del header con doble firma está en Verificar la firma.


Listar entregas

GET/v1/webhook-deliveriesAPI key · webhooks:read
CampoTipoRequeridoDescripción
endpointstringId del endpoint, whep_…. Sin este parámetro la request devuelve 422.
limitintegerno1 a 100. Por defecto 25.
cursorstringnoCursor opaco.
curl -sS "$KYCAR_API/v1/webhook-deliveries?endpoint=whep_01K2M4P6R8T0V2X4Z6B8D0F2H4" \
  -H "Authorization: Bearer $KYCAR_API_KEY"
{
  "data": [
    {
      "id": "d2hlcF8wMUsyTTRQNlI4VDBWMlg0WjZCOEQwRjJINCNBIzE",
      "object": "webhook_delivery",
      "endpoint_id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
      "event_id": "evt_7QK3M9ZP2X8V4T0R6B1D5F3H7J",
      "event_type": "verification_session.approved",
      "session_id": "vs_01K2M4P6R8T0V2X4Z6B8D0F2H4",
      "status": "failed",
      "attempt": 3,
      "http_status": 502,
      "duration_ms": 218,
      "error": "http_502",
      "created_at": "2026-08-19T14:22:48.011Z",
      "resolved_at": "2026-08-19T14:22:48.229Z"
    }
  ],
  "has_more": false
}
CampoTipoSignificadoDescripción
pendingstatusEn vueloEl intento arrancó y todavía no se resolvió.
deliveredstatusOKTu endpoint respondió 2xx.
failedstatusFalló, se reintentaError de red, timeout, o respuesta fuera del rango 2xx. Queda otro intento por delante.
deadstatusFalló, sin reintentosEra el último intento: el evento se abandona.

El campo error es un texto corto y estable para casos frecuentes: http_502 (respuesta no-2xx), circuit_breaker_open (el endpoint estaba cortado), secret_decrypt_failed (problema de plataforma, no de tu endpoint).

El id de una entrega es un identificador opaco (base64url): pasalo tal cual al endpoint de re-entrega, no lo interpretes ni lo construyas.


Re-entregar un evento

POST/v1/webhook-deliveries/{id}/redeliverAPI key · webhooks:write

Vuelve a enviar el evento original, con el mismo event_id. Tu receptor lo deduplica por ese id, así que re-entregar dos veces no duplica nada de tu lado.

curl -sS -X POST "$KYCAR_API/v1/webhook-deliveries/d2hlcF8wMUsy.../redeliver" \
  -H "Authorization: Bearer $KYCAR_API_KEY"

202 Accepted:

{
  "object": "webhook_redelivery",
  "delivery_id": "d2hlcF8wMUsyTTRQNlI4VDBWMlg0WjZCOEQwRjJINCNBIzE",
  "endpoint_id": "whep_01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "event_id": "evt_7QK3M9ZP2X8V4T0R6B1D5F3H7J",
  "redelivery_id": "01K2M4P6R8T0V2X4Z6B8D0F2H4",
  "queued": true
}

redelivery_id identifica este reintento (el event_id es el del evento original y se repite). Sirve para correlacionar con el log si tenés que pedir soporte.

CampoTipoStatusDescripción
validation_failed422422La entrega es de tipo test: no se re-entrega. Disparás una prueba nueva.
not_found404404La entrega o el endpoint no existen.
conflict409409El endpoint está disabled.
rate_limited429429Más de 10 pruebas o re-entregas por minuto en la cuenta.

El payload se reconstruye en el momento a partir de la sesión: si el estado cambió desde la entrega original, la re-entrega refleja el estado actual.