Referencia de integración · admin.u-site.app
Las 10 preguntas del equipo integrador, respondidas leyendo el código del servidor en vez de inferirlas desde el cliente. Cada respuesta cita archivo y línea para que se pueda auditar. Una es crítica y explica por qué la integración probablemente nunca autenticó.
expires_in es un timestamp Unix absolutoEl campo se reusa: entra como duración en segundos y sale como timestamp.
// config/token.go:26-49 t.Expire = ninetyYears // duración, en segundos expiresIn := time.Now().Add( time.Duration(t.Expire)*time.Second).Unix() claims["exp"] = expiresIn t.Expire = expiresIn // ← se SOBREESCRIBE
Lo que viaja en la respuesta es expiresIn: segundos desde epoch.
Pero hay algo más grave. Login llama a
CreateToken sin pasarle vencimiento (pkg/auth/user.go:98),
así que aplica el default: 90 años.
En la práctica ese token nunca expira, y toda la lógica de caché y renovación del cliente no se ejerce jamás. Es un arreglo nuestro, no de ustedes.
POST /oauth/token{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 4626547200
}
rest/controllers/api/auth_controller.go:42-45
token, no access_token.expires_in está en el nivel raíz.token_type, ni refresh_token, ni envoltorio.Error de credenciales, siempre HTTP 401:
{ "error": true, "message": "Invalid Credentials" }
| Endpoint | Método | Respuesta |
|---|---|---|
/generate-qr-tmp | POST | {"url": "https://…"} |
/generate-url-qr | POST | {"url": "https://…"} |
/generate-vcf | POST | {"success": true, "url": "https://…"} |
/generate-qr | POST | No es JSON — imagen binaria image/webp |
vcard_qr.go:133-135 · vcard_vcf.go:103-106 · vcard_qr.go:169-170
Ojo con el último: generate-qr responde bytes de imagen. Un
json_decode sobre eso falla siempre.
Errores: {"error": "Datos inválidos"} con 400, o
{"error": "<detalle>"} con 500.
Nadie lo adivinó mal. La doble codificación existe:
// rest/controllers/api/dlocal_controller.go:42-45 return c.Status(200).JSON(fiber.Map{ "message": "Plan creado exitosamente.", "response": string(response), // ← el JSON, como STRING })
Lo que llega:
{
"message": "Plan creado exitosamente.",
"response": "{\"id\":\"PLAN-123\",\"status\":\"active\"}"
}
Hay que hacer json_decode dos veces: una del cuerpo y
otra del campo response. Aplica igual a los 7 endpoints dLocal
(líneas 44, 77, 110, 137, 162, 188, 221).
El texto de message cambia según el endpoint, así que no conviene
usarlo para decidir nada.
// rest/middlewares/auth.go:271-286 token := c.Cookies("Verify-Rest-Token") if token == "" { return c.Status(401).JSON("Token not found") // falta la cookie } … ErrorHandler: return ctx.Status(401).JSON("Invalid Attempt") // cookie inválida o vencida
Los dos son JSON string, con comillas incluidas — el cuerpo
literal es "Invalid Attempt", 16 bytes, no el texto pelado.
Recomendación: reintentar ante cualquier 401, sin mirar el texto. Es más robusto y no depende de una redacción que puede cambiar.
Todo cuelga de https://admin.u-site.app/api/v1/. Lo correcto es
guardar la raíz y concatenar la ruta, en vez de guardar la URL del
token y derivar las demás con str_replace.
POST /api/v1/oauth/token
POST /api/v1/generate-qr-tmp
POST /api/v1/generate-qr (imagen)
POST /api/v1/generate-vcf
POST /api/v1/generate-url-qr
POST /api/v1/dlocal/subscription/crear-plan
GET /api/v1/dlocal/subscription/ver-plan/:planID
PATCH /api/v1/dlocal/subscription/actualizar-plan/:planID
GET /api/v1/dlocal/subscription/plan/all
PATCH /api/v1/dlocal/subscription/plan/:planId/subscription/:subscriptionId/deactivate
GET /api/v1/dlocal/subscription/:subscriptionId/execution/:invoiceId
POST /api/v1/dlocal/payment/crear-pago
POST /api/v1/rapyd/wallet/create
Cookie es obligatorio, y hoy está rotoLa API no acepta Bearer. Autentica exclusivamente por cookie:
// rest/middlewares/auth.go:272-281 token := c.Cookies("Verify-Rest-Token") if token == "" { return c.Status(401).JSON("Token not found") } TokenLookup: "cookie:Verify-Rest-Token"
Con el placeholder literal ... sin completar, los 12
endpoints autenticados devuelven 401. Si algo funciona hoy, es porque el
cliente HTTP tiene cookie jar y reusa la cookie que POST /oauth/token
deja seteada en la respuesta (config/token.go:40-47).
Authorization: Bearer
no autentica nada.session_id no hace falta: el middleware no lo
mira. Se puede quitar.Lo correcto: tomar el token de la respuesta de oauth/token
y mandarlo como Cookie: Verify-Rest-Token=<token>, o dejar que el
cliente maneje cookies solo.
/api/v1Solo se valida la cookie JWT. No hay allowlist; no hace falta registrar la IP del servidor de producción.
Sí existe validación por IP, pero en otra API:
/api/v2 usa ApiKey con IP obligatoria y scopes, y
/api/v1/pagos-externos usa AuthServicioPago — ambas
excluidas de AuthApi (auth.go:266-268).
Si prefieren un esquema con IP fija y token que no viva en una cookie,
/api/v2 es el camino, y conviene migrar ahí en vez de
arreglar el actual.
No van acá. Son las de un usuario real de la tabla users
(login.CheckLogin(), auth_controller.go:28). Que se
entreguen por un canal seguro.
Dado que llevan 15 meses sin rotar, conviene crear un usuario dedicado a la integración en vez de reusar el de una persona.
Hay webhooks entrantes de pago ya montados
(rest/routes/publicas.go:22-33): /webhooks/bold,
/webhooks/dlocal, /webhooks/paypal, más Coolify y Telegram.
Pero sirven para que la pasarela nos avise a nosotros, no para avisarle a un tercero. Un callback hacia su sistema es desarrollo nuevo.
Mientras tanto, la alternativa ya construida es
/api/v1/pagos-externos, pensada justo para apps de terceros:
GET /api/v1/pagos-externos/pasarelas POST /api/v1/pagos-externos/solicitar GET /api/v1/pagos-externos/:referencia/estado
Usa token Bearer + IP (no cookie), así que evita todo el problema del punto 07. Para una integración nueva, recomendamos esta y no los endpoints dLocal directos.
No son preguntas: son cosas a corregir en admin.u-site.app.
config/token.go:26). Un token
filtrado es acceso permanente. Ponerle un vencimiento razonable — y recién ahí la
caché y el reintento del cliente van a tener sentido.Secure: false
(config/token.go:44), así que viaja también por HTTP plano./api/v2 ya lo resuelve bien.