CORS y validación de origen
Neuroon valida el Origin de cada request de forma distinta según el endpoint y el tipo de credencial. Esta página es la fuente única de verdad: si una página de docs dice algo distinto, gana esta tabla.
Matriz canónica
| Endpoint | Auth | Acepta Origin ausente (server-to-server) | Origin debe coincidir con | Cómo añadir un dominio |
|---|---|---|---|---|
POST /api/plugin/shops/{shopId}/products/sync | X-Shop-API-Key | ✅ Sí | n/a — sólo se valida que la API Key pertenezca al shop. | n/a |
GET /api/plugin/shops/{shopId}/products | X-Shop-API-Key | ✅ Sí | n/a | n/a |
POST /api/plugin/shops/{shopId}/track/conversion | X-Shop-API-Key | ✅ Sí | n/a | n/a |
POST /api/plugin/shops/{shopId}/verify | X-Shop-API-Key | ✅ Sí | El domain del body se compara con shop.url. | Editar shop.url en el dashboard. |
GET /api/plugin/shops/{shopId}/verification-data | X-Shop-API-Key | ✅ Sí | n/a | n/a |
POST /api/shops/{id}/widget-token | X-Shop-API-Key | ✅ Sí | n/a | n/a |
GET / POST /api/widget/search, /suggestions, /trending, /compare, /cart/cross-sell | X-Widget-Token | ✅ Sí (en navegador siempre llega Origin) | app.widget.allowed-origins ∪ shop.url (si llega Origin, debe coincidir) | Pedir a soporte que añada el dominio a allowed-origins. |
POST /api/widget/search/audio, /visual | X-Widget-Token | ✅ Sí | Igual que arriba | Igual que arriba |
POST /api/widget/track/click, /track/conversion | X-Widget-Token | ✅ Sí | Igual que arriba | Igual que arriba |
POST /api/widget/analytics/event(s) | X-Widget-Token | ✅ Sí | Igual que arriba | Igual que arriba |
POST /api/webhooks/stripe | Stripe-Signature | ✅ Sí (entra desde Stripe) | n/a | n/a |
El validador
WidgetTokenValidator.validateOriginaceptaOriginausente o vacío (early-return enWidgetTokenValidator.java:60-63). Sólo cuando llegaOriginse compara contraapp.widget.allowed-origins ∪ shop.url. Esto hace los endpoints widget compatibles con curl/server-to-server, aunque desde un navegador siempre llegaráOrigin.
POST /api/tokens/refreshno aparece en esta tabla porque no es parte del contrato widget/plugin: refresca JWTs de usuario del dashboard de Neuroon (TokenController.java:36).
Defaults de app.widget.allowed-origins
Por defecto (application.yml:273-276):
localhosty127.0.0.1(suffix-match: cualquier subdominio o puerto vale).neuroon.ai(suffix-match: incluye*.neuroon.ai, p. ej.dev.neuroon.ai,cdn.neuroon.ai).- El
shop.urlregistrado en el dashboard.
Añadir un dominio nuevo
- Si es tu dominio principal: edítalo desde el dashboard → tu tienda → Settings → Storefront URL. El cambio aplica al siguiente request servido.
- Si necesitas dominios extra (staging, ramas, dominios de marketing que comparten widget): abre un ticket en soporte indicando
shopIdy los dominios. La lista vive en config del backend (app.widget.allowed-origins).
Self-service para dominios extra está en roadmap. Mientras tanto, el dashboard sólo edita
shop.url(un único valor).
Errores típicos
| Síntoma | Causa | Solución |
|---|---|---|
Browser bloquea XHR con CORS error y consola muestra 403 | El widget está corriendo en un dominio que no está en allowed-origins ni coincide con shop.url. | Verifica shop.url o pide que añadan el dominio a allowed-origins. |
403 DOMAIN_MISMATCH al llamar /verify | El url del body no coincide con shop.url. | Edita shop.url en el dashboard y reintenta. |
401 al servir el widget desde el navegador | Token expirado, no presente en el HTML, o mezcla de entorno (token DEV vs api.neuroon.ai). | Renueva el token y comprueba la URL base. |
Próximas lecturas
- Authentication · Widget Token — formato HMAC y TTL.
- Authentication · Shop API Key — formato
sk_…y rotación. - Recipe · Server-to-server token — patrón de cache y rotación.
- Custom · Server-to-server — incluye verificación de dominio sin plugin.