Postbacks de estado
Para: Lenders (equipo de integración)
Resumen
Sección titulada «Resumen»Cuando un consumidor solicita uno de tus productos a través de Prosperas, vos procesás esa solicitud de tu lado (la aprobás, desembolsás los fondos o la rechazás). Un postback es la forma en que le contás a Prosperas qué pasó, casi en tiempo real, para que podamos actualizar el estado del lead, conciliar el revenue y mantener sincronizada la experiencia del consumidor.
La integración es deliberadamente chica: un endpoint, una credencial, un formato de payload.
| Propiedad | Valor |
|---|---|
| Dirección | Vos → Prosperas (vos nos llamás) |
| Endpoint | POST {BASE_URL}/client_app/webhook/lender/loan-status (existe una variante GET equivalente) |
| Autenticación | Header X-API-KEY (recomendado) o IP allowlist |
| Formato del body | JSON, snake_case |
| Eventos | application, approval, disbursement, rejected, no_payment_yet, on_time, late, default, paid_in_full |
| Éxito | 200 OK con un acknowledgement en JSON |
| Reintentos | Vos reintentás ante 5xx / errores de red (nosotros no te reintentamos) |
Autenticación
Sección titulada «Autenticación»Todo postback tiene que autenticarse. Prosperas revisa primero la API key, y después la IP de origen. Si ninguna de las dos coincide con una credencial registrada, la solicitud se rechaza con 403.
API key (recomendado)
Sección titulada «API key (recomendado)»Enviá tu key en el header HTTP:
X-API-KEY: <tu-key>- El nombre del header no distingue mayúsculas/minúsculas (
X-API-KEY,x-api-key,X-Api-Keyfuncionan igual). - La key se valida por coincidencia exacta de string. Sin prefijo, sin
Bearer, sin encoding.
IP allowlist (alternativa)
Sección titulada «IP allowlist (alternativa)»Si te provisionaron por IP, no hace falta ningún header — leemos tu IP de origen desde X-Forwarded-For, después X-Real-IP, y por último el socket peer.
- La coincidencia es por string exacto contra la IP que registramos.
- No soportamos rangos CIDR / subnets. Cada IP de salida individual que uses tiene que estar registrada. Si tu infraestructura rota IPs, usá el método de API key en su lugar.
La regla del “el lead tiene que existir”
Sección titulada «La regla del “el lead tiene que existir”»La autenticación no depende solo de la credencial — también está atada a un lead real. Una solicitud es exitosa solo cuando se cumplen todas estas condiciones:
- Tu credencial (key o IP) coincide con un origen de lender registrado, y
- el
click_idde la solicitud resuelve a un lead, y - ese lead pertenece a un producto que es de tu cuenta de lender.
Si tu credencial es válida pero el click_id no resuelve a uno de tus leads, igual vas a recibir 403 (no 404). En la práctica esto significa: un click_id incorrecto, desconocido o ajeno devuelve 403.
El identificador de tracking (click_id)
Sección titulada «El identificador de tracking (click_id)»click_id es el identificador único que conecta el journey del consumidor entre Prosperas y tus sistemas.
- Prosperas lo genera y te lo entrega en el momento en que redirige al consumidor hacia vos (como identificador de tracking/click en la URL de handoff).
- Tenés que guardarlo y devolverlo exactamente igual en cada postback de esa solicitud.
- No contiene datos personales; es una referencia opaca.
Formatos aceptados:
| Formato | Ejemplo | Notas |
|---|---|---|
| UUID simple | 7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e |
|
orgname_UUID |
claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e |
Exactamente un guion bajo |
Más de un guion bajo, o un UUID que no se pueda parsear, devuelve 400.
Contrato de la solicitud
Sección titulada «Contrato de la solicitud»Método: POST con body JSON (recomendado para integraciones server-to-server). Si tu plataforma no puede emitir un POST con body, existe una variante GET totalmente soportada que recibe los mismos campos como query parameters — mismo endpoint, misma autenticación, mismo procesamiento.
Headers:
Content-Type: application/jsonX-API-KEY: <tu-key>Body (JSON, snake_case):
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "approval", "occurred_at": "2026-07-01T12:34:56Z", "data": { "provider": "tu-marca", "client": { "email": "consumidor@example.com", "fullName": "Ana Gomez", "firstName": "Ana", "lastName": "Gomez", "idNumber": "1234567890", "phoneNumber": "3001234567", "returningCustomer": false } }}Referencia de campos
Sección titulada «Referencia de campos»| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
click_id |
string | Sí | El identificador de tracking. Devolvelo exactamente igual. |
event |
string | Sí | Uno de los 9 eventos soportados: application, approval, disbursement, rejected, no_payment_yet, on_time, late, default, paid_in_full (ver Eventos y ciclo de vida más abajo). Solo minúsculas — en mayúsculas se rechaza con 422. |
occurred_at |
string | — | Timestamp ISO-8601 de cuándo ocurrió el evento de tu lado (ej. 2026-07-01T12:34:56Z). |
data |
object | — | Blob de auditoría opcional, de forma libre. Claves reconocidas: provider (string) y client (object, ver abajo). Para los eventos de estado de pago existen además claves recomendadas (ver Actualizaciones de estado de pago más abajo); no son obligatorias ni se validan. |
data.client |
object | — | Datos opcionales del consumidor. Ver sección siguiente. |
El objeto data.client (opcional)
Sección titulada «El objeto data.client (opcional)»data.client te permite enriquecer el registro del consumidor. Todos los campos son opcionales.
| Campo | Tipo | Notas |
|---|---|---|
email |
string | |
firstName |
string | Se usa solo si fullName no está presente. |
lastName |
string | Se usa solo si fullName no está presente. |
fullName |
string | Tiene prioridad: primer token → nombre, resto → apellido. |
idNumber |
string | Número de documento / cédula. |
phoneNumber |
string | |
returningCustomer |
boolean | Por defecto es false si se omite. Ver la advertencia abajo. |
Variante GET (query parameters)
Sección titulada «Variante GET (query parameters)»Si tu plataforma no puede emitir un POST con body JSON — un tag manager, un sistema legacy, un motor de reglas que solo sabe invocar URLs — podés reportar exactamente el mismo evento con un GET sobre el mismo endpoint, pasando los campos como query parameters:
GET {BASE_URL}/client_app/webhook/lender/loan-status?click_id=<id>&event=<evento>Atraviesa la misma autenticación, el mismo mapeo de evento → estado y la misma persistencia que el POST. Devuelve el mismo acknowledgement JSON (200) y los mismos códigos de error (400 / 403 / 404 / 422 / 5xx), con las mismas reglas de reintento.
Query parameters
Sección titulada «Query parameters»| Parámetro | Tipo | Requerido | Equivale en el POST a |
|---|---|---|---|
click_id |
string | Sí | click_id |
event |
string | Sí | event (minúsculas; uno de los 9 eventos) |
occurred_at |
string | — | occurred_at (ISO-8601) |
api_key |
string | — | Header X-API-KEY (ver abajo) |
provider |
string | — | data.provider |
email |
string | — | data.client.email |
firstName |
string | — | data.client.firstName |
lastName |
string | — | data.client.lastName |
fullName |
string | — | data.client.fullName |
idNumber |
string | — | data.client.idNumber |
phoneNumber |
string | — | data.client.phoneNumber |
returningCustomer |
boolean | — | data.client.returningCustomer |
Todos los valores tienen que ir URL-encoded (emails, nombres con espacios, el + de un teléfono internacional). Los parámetros que no estén en esta tabla se ignoran y no se almacenan.
Autenticación en el GET
Sección titulada «Autenticación en el GET»Las tres formas de autenticarte siguen valiendo, en este orden de preferencia:
- Header
X-API-KEY— recomendado también en elGET. UnGETpuede llevar headers; si tu cliente puede setearlos, usá esto. - Query parameter
api_key— alternativa para clientes que no pueden setear headers. - IP allowlist — sin credencial en la request, si tu IP de origen está registrada.
Ejemplo
Sección titulada «Ejemplo»curl -G "{BASE_URL}/client_app/webhook/lender/loan-status" \ -H "X-API-KEY: <tu-key>" \ --data-urlencode "click_id=claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e" \ --data-urlencode "event=approval" \ --data-urlencode "occurred_at=2026-07-01T12:34:56Z" \ --data-urlencode "provider=tu-marca" \ --data-urlencode "email=consumidor@example.com" \ --data-urlencode "fullName=Ana Gomez"{ "ok": true, "lead_id": 12345, "user_id": 678, "status": "APPROVAL" }Eventos y ciclo de vida
Sección titulada «Eventos y ciclo de vida»Enviá un postback por cada transición de estado, usando el evento que corresponda a lo que pasó:
event |
Significado | ¿Requerido? |
|---|---|---|
application |
Se recibió / inició la solicitud del consumidor de tu lado. | Opcional |
approval |
La solicitud fue aprobada. | Sí |
disbursement |
Se desembolsaron los fondos. | Sí |
rejected |
La solicitud fue rechazada. | Sí |
no_payment_yet |
El préstamo fue desembolsado pero todavía no llegó la primera fecha de pago programada. | Sí — requerido |
on_time |
Se recibió el pago en la fecha de vencimiento (o dentro del período de gracia estándar). | Sí — requerido |
late |
Se recibió el pago después del vencimiento + gracia, por debajo del umbral de default de tu institución. | Sí — requerido |
default |
El préstamo cumplió el criterio interno de default de tu institución. | Sí — requerido |
paid_in_full |
Se saldaron todos los pagos programados y el saldo pendiente; el préstamo queda cerrado en buen estado. | Sí — requerido |
application se acepta pero es opcional — enviálo si tenés un hito significativo de “solicitud recibida”; si no, los tres resultados terminales (approval, disbursement, rejected) son lo que más importa.
Los cinco eventos de estado de pago (no_payment_yet, on_time, late, default, paid_in_full) son distintos de los cuatro anteriores: no son un hito único sino actualizaciones continuas durante toda la vida del préstamo, reportadas después del desembolso. Se van reemplazando entre sí a medida que cambia la situación de pago — no_payment_yet es el estado inicial y se reemplaza por on_time, late o default en cuanto pasa la primera fecha de vencimiento. Reportar los cinco eventos es obligatorio para todos los leads, con vigencia antes del 31 de agosto. Los datos de aprobación y desembolso solo dicen quién calificó para el crédito; los datos de pago cierran el círculo sobre quién efectivamente repaga, lo que mejora la calidad del matchmaking para todos los lenders. Ver la sección Actualizaciones de estado de pago más abajo para el detalle de cada estado.
Enviá los eventos en el orden en que ocurren. Prosperas tolera eventos que llegan desordenados, pero la entrega en orden mantiene limpio el historial del lead.
Actualizaciones de estado de pago
Sección titulada «Actualizaciones de estado de pago»A diferencia de los cuatro eventos anteriores, que marcan un hito único en la solicitud, las actualizaciones de estado de pago son un reporte continuo: enviás un nuevo postback cada vez que cambia la situación de pago del consumidor, durante toda la vida del préstamo.
Definiciones por estado
Sección titulada «Definiciones por estado»event |
Definición |
|---|---|
no_payment_yet |
El préstamo fue desembolsado pero todavía no llegó la primera fecha de pago programada. Es el estado inicial y saludable — se reemplaza por on_time, late o default en cuanto pasa esa fecha. |
on_time |
Se recibió el pago en la fecha de vencimiento, o dentro del período de gracia estándar de tu institución. |
late |
Se recibió el pago después del vencimiento + gracia, pero todavía por debajo del umbral de default de tu institución. |
default |
El préstamo cumplió tu criterio interno de default. |
paid_in_full |
Se saldaron todos los pagos programados y el saldo pendiente (capital, intereses, cargos). El préstamo queda cerrado en buen estado. |
Buckets de DPD (late)
Sección titulada «Buckets de DPD (late)»Para late, se recomienda reportar el bucket de días de mora (DPD, days past due) en data.dpd_bucket:
| Bucket | Rango |
|---|---|
1-30 |
1 a 30 días de mora |
31-60 |
31 a 60 días de mora |
61-90 |
61 a 90 días de mora |
La convención de 90+ DPD (default)
Sección titulada «La convención de 90+ DPD (default)»El criterio más común de la industria para default es 90 o más días de mora. Si tu institución usa un umbral distinto, reportalo explícitamente en data.default_threshold_dpd para que quede claro qué convención estás aplicando.
Claves de data recomendadas por evento
Sección titulada «Claves de data recomendadas por evento»| Evento | Claves recomendadas en data |
|---|---|
no_payment_yet |
disbursed_amount |
on_time |
— |
late |
dpd_bucket |
default |
default_threshold_dpd |
paid_in_full |
paid_off_date |
Ejemplos por evento
Sección titulada «Ejemplos por evento»no_payment_yet
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "no_payment_yet", "occurred_at": "2026-08-01T09:00:00Z", "data": { "disbursed_amount": 500000 }}on_time
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "on_time", "occurred_at": "2026-09-01T09:00:00Z", "data": {}}late
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "late", "occurred_at": "2026-09-15T09:00:00Z", "data": { "dpd_bucket": "1-30" }}default
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "default", "occurred_at": "2026-12-01T09:00:00Z", "data": { "default_threshold_dpd": 90 }}paid_in_full
{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "paid_in_full", "occurred_at": "2027-06-01T09:00:00Z", "data": { "paid_off_date": "2027-06-01" }}Respuestas y códigos de estado
Sección titulada «Respuestas y códigos de estado»Body de éxito (200 OK):
{ "ok": true, "lead_id": 12345, "status": "APPROVAL", "user_id": 6789}ok— siempretrueen caso de éxito.lead_id— el lead de Prosperas contra el cual se registró el evento.status— el estado interno al que mapeó tu evento (APPLICATION/APPROVAL/DISBURSEMENT/REJECTED/NO_PAYMENT_YET/ON_TIME/LATE/DEFAULT/PAID_IN_FULL).user_id— el id del registro del consumidor, cuando está disponible.
Códigos de estado:
| Código | Significado | Qué hacer |
|---|---|---|
200 |
Evento registrado. | Listo. |
400 |
click_id mal formado (UUID inválido o demasiados guiones bajos). |
Corregí el click_id; no reintentes sin cambios. |
403 |
Falló la autenticación o el click_id no resuelve a uno de tus leads. |
Revisá tu key/IP y que estés devolviendo el click_id correcto. No reintentes a ciegas. |
404 |
Sesión no encontrada (caso raro, ocurre después de que la autenticación fue exitosa). | Verificá el click_id; contactá a Prosperas si persiste. |
422 |
Error de validación — ej. clickId en camelCase, evento en mayúsculas, tipos incorrectos. |
Corregí la forma del payload (ver sección de errores comunes). No reintentes sin cambios. |
5xx |
Error transitorio del lado de Prosperas. | Reintentá con backoff exponencial (ver sección de reintentos). |
Errores comunes (leé esto antes de integrar)
Sección titulada «Errores comunes (leé esto antes de integrar)»Estos cuatro errores explican casi todas las integraciones fallidas en su primer intento. Evitalos:
| ❌ No hagas esto | ✅ Hacé esto |
|---|---|
Usar clickId en camelCase en el body |
Usá snake_case click_id — camelCase → 422 |
Enviar event: "APPROVAL" (mayúsculas) |
Enviá minúsculas approval — mayúsculas → 422 |
| Asumir que el endpoint es público / sin auth | Enviá X-API-KEY (o llamá desde una IP en la allowlist) — si no, 403 |
| Poner la API key en un query string por comodidad | Enviá la key en el header X-API-KEY — también se puede en un GET. ?api_key= existe solo como último recurso (ver variante GET) |
Reintentos y garantías de entrega
Sección titulada «Reintentos y garantías de entrega»- Los reintentos son tu responsabilidad. Prosperas no te reintenta. Ante un
5xxo un error de red, reintentá el postback con backoff exponencial (ej. 1s, 2s, 4s, 8s…), hasta un límite razonable. - No reintentes ante
4xx. Un400,403o422significa que la solicitud está mal — corregila y enviá una solicitud corregida en lugar de insistir con el mismo payload. - El endpoint no es idempotente. Cada postback aceptado registra un evento nuevo. Si reintentás después de una solicitud que en realidad tuvo éxito (pero cuya respuesta no recibiste), podés generar un evento duplicado. Mantené los reintentos acotados y priorizá reintentar solo ante fallas confirmadas.
- Cada solicitud queda registrada de nuestro lado (con tu API key enmascarada) para soporte y conciliación.
Ejemplos prácticos
Sección titulada «Ejemplos prácticos»Caso exitoso — aprobación (POST)
Sección titulada «Caso exitoso — aprobación (POST)»curl -X POST "{BASE_URL}/client_app/webhook/lender/loan-status" \ -H "Content-Type: application/json" \ -H "X-API-KEY: <tu-key>" \ -d '{ "click_id": "claro_7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "approval", "occurred_at": "2026-07-01T12:34:56Z", "data": { "provider": "tu-marca" } }'Respuesta — 200 OK:
{ "ok": true, "lead_id": 12345, "status": "APPROVAL", "user_id": 6789 }Desembolso con enriquecimiento de datos del consumidor (POST)
Sección titulada «Desembolso con enriquecimiento de datos del consumidor (POST)»curl -X POST "{BASE_URL}/client_app/webhook/lender/loan-status" \ -H "Content-Type: application/json" \ -H "X-API-KEY: <tu-key>" \ -d '{ "click_id": "7e1c2f1f-9b4d-4c1d-9f6c-2f3c1a2b4d5e", "event": "disbursement", "occurred_at": "2026-07-01T15:00:00Z", "data": { "provider": "tu-marca", "client": { "fullName": "Ana Gomez", "idNumber": "1234567890" } } }'Error — campo en camelCase (422)
Sección titulada «Error — campo en camelCase (422)»# INCORRECTO: "clickId" (camelCase) en lugar de "click_id"curl -X POST "{BASE_URL}/client_app/webhook/lender/loan-status" \ -H "Content-Type: application/json" \ -H "X-API-KEY: <tu-key>" \ -d '{ "clickId": "7e1c2f1f-...", "event": "approval" }'# → 422 Unprocessable Entity (el campo "click_id" es requerido)Error — credencial faltante o incorrecta (403)
Sección titulada «Error — credencial faltante o incorrecta (403)»# Sin header X-API-KEY y con una IP que no está en la allowlist → 403curl -X POST "{BASE_URL}/client_app/webhook/lender/loan-status" \ -H "Content-Type: application/json" \ -d '{ "click_id": "7e1c2f1f-...", "event": "approval" }'# → 403 ForbiddenReferencia rápida
Sección titulada «Referencia rápida»| Endpoint | POST {BASE_URL}/client_app/webhook/lender/loan-status |
| Variante sin body | GET al mismo path con los campos como query parameters — ver Variante GET |
| Header de auth | X-API-KEY: <tu-key> (no distingue mayúsculas/minúsculas) |
| Campos requeridos del body | click_id (snake_case), event (minúsculas) |
| Eventos | application · approval · disbursement · rejected · no_payment_yet · on_time · late · default · paid_in_full |
| Campos opcionales del body | occurred_at (ISO-8601), data, data.client |
| Éxito | 200 → { ok, lead_id, status, user_id } |
| Errores del cliente | 400 click_id inválido · 403 auth/lead · 404 sesión · 422 validación |
| Reintentar ante | Solo 5xx / red, con backoff exponencial |
| Firma | Ninguna (sin HMAC) — protegé la API key |
Notas importantes
Sección titulada «Notas importantes»Siguiente paso: con los postbacks funcionando, cerrá la integración con los Datos de producto.
Última verificación: 2026-09-15 · v1.3 · coincide con api/client_app/webhook/router.py