Observar y depurar en el simulador
Cuando una integración no funciona, casi siempre es porque estás mirando tu lado y el fallo está en el medio. El simulador de SPIDI te enseña el medio: qué petición le llegó, qué respondió, qué webhook intentó entregarte, a dónde, cuántas veces y con qué resultado.
Necesitas tus dos tokens: Credenciales.
Toda la superficie /console/** — la traza incluida — existe solo en el simulador. En producción observas con tus propios registros y con GET /api/v1/ext/payment-sessions/status/<session_id>, que sí es contrato. Aprovecha la traza para dejar tu instrumentación bien puesta antes de salir del sandbox, porque después no la vas a tener.
Las Dos Ventanas
| Qué quieres saber | Dónde | Con qué token |
|---|---|---|
| Qué ha pasado, en orden, con todo | GET /console/trace | consola |
| Cómo está una sesión ahora | GET /api/v1/ext/payment-sessions/status/<session_id> | API |
| Qué sesiones tengo | GET /console/sessions | consola |
La Traza
curl "https://sim-productos.abiertolab.com/console/trace?account_id=<tu-account-id>" \
-H "Authorization: Bearer <tu-token-de-consola>"
Devuelve { "events": [ … ], "cursor": { … } }, en orden cronológico ascendente. Cada evento tiene esta forma:
{
"id": "evt_0a1b2c3d4e5f6071",
"createdAt": 1754300000000,
"accountId": "acc_9f8e7d6c5b4a3021",
"sessionId": "ses_1f2e3d4c5b6a7890",
"type": "webhook.attempt",
"payload": { }
}
Parámetros de consulta:
account_id— obligatorio. Sin él recibes400.session_id— filtra por una sesión.type— filtra por tipo de evento. Un tipo que no exista da400.limit— entre 1 y 500, por defecto 200.after_created_atyafter_id— el cursor, para paginar. Repite en la siguiente llamada los valores delcursorque te devolvió la anterior.
Y los errores que puedes recibir: 401 si el token de consola es inválido o falta, 403 si esa key no es de tu cuenta.
Qué Cuenta Cada Tipo de Evento
type | Cuándo se registra | Lo importante del payload |
|---|---|---|
account.created | Al dar de alta la key | shortName |
api.request | Llega una petición autenticada a /api/** | method, path, correlationId |
api.response | Se responde esa petición | status, method, path, correlationId |
state.transition | La sesión cambia de estado | from, to, leg — y reason solo a veces (ver abajo) |
control.action | Forzaste un desenlace por /control/** | action, leg, outcome |
webhook.attempt | Se genera o se intenta entregar un webhook | tiene tres formas, detalladas en la sección "Las tres formas de webhook.attempt" |
webhook.delivered | Tu receptor respondió 2xx | attempt, status, event, idempotencyKey, routing |
webhook.received | Algo entró por un receptor del simulador (/console/hooks/<account_id>/<kind>) | kind, raw, body, headers, signature, idempotencyKey |
Tres detalles que ahorran confusión:
- **Las llamadas a
/control/**no aparecen comoapi.request. La captura solo mira/api/. Del plano de control queda elcontrol.actiony lastate.transitionque provocó. correlationIdempareja la petición con su respuesta. Es el mismo valor en elapi.requesty en elapi.responsedel mismo intercambio.reasonno viene en todas lasstate.transition. Solo aparece en las del fase 1 ("leg": 1), y solo si forzaste el desenlace pasando un motivo. Las dos transiciones del fase 2 —paid → accreditedypaid → accreditation_failed