CORS & origin validation
Neuroon validates each request's Origin differently depending on the endpoint and the credential type. This page is the single source of truth: if any other doc disagrees, this matrix wins.
Canonical matrix
| Endpoint | Auth | Accepts missing Origin (server-to-server) | Origin must match | How to add a domain |
|---|---|---|---|---|
POST /api/plugin/shops/{shopId}/products/sync | X-Shop-API-Key | ✅ Yes | n/a — only that the API Key belongs to the shop. | n/a |
GET /api/plugin/shops/{shopId}/products | X-Shop-API-Key | ✅ Yes | n/a | n/a |
POST /api/plugin/shops/{shopId}/track/conversion | X-Shop-API-Key | ✅ Yes | n/a | n/a |
POST /api/plugin/shops/{shopId}/verify | X-Shop-API-Key | ✅ Yes | Body domain is compared to shop.url. | Edit shop.url in the dashboard. |
GET /api/plugin/shops/{shopId}/verification-data | X-Shop-API-Key | ✅ Yes | n/a | n/a |
POST /api/shops/{id}/widget-token | X-Shop-API-Key | ✅ Yes | n/a | n/a |
GET / POST /api/widget/search, /suggestions, /trending, /compare, /cart/cross-sell | X-Widget-Token | ✅ Yes (browsers always send Origin) | app.widget.allowed-origins ∪ shop.url (when Origin is present, it must match) | Ask support to add the domain to allowed-origins. |
POST /api/widget/search/audio, /visual | X-Widget-Token | ✅ Yes | Same as above | Same as above |
POST /api/widget/track/click, /track/conversion | X-Widget-Token | ✅ Yes | Same as above | Same as above |
POST /api/widget/analytics/event(s) | X-Widget-Token | ✅ Yes | Same as above | Same as above |
POST /api/webhooks/stripe | Stripe-Signature | ✅ Yes (Stripe → us) | n/a | n/a |
The
WidgetTokenValidator.validateOriginmethod accepts a missing or blankOrigin(early-return atWidgetTokenValidator.java:60-63). Only when anOriginis present does it check it againstapp.widget.allowed-origins ∪ shop.url. This makes the widget endpoints curl/server-to-server friendly, even though browsers always sendOrigin.
POST /api/tokens/refreshis intentionally not in this table — it is not part of the widget/plugin contract. It refreshes user JWTs for the Neuroon dashboard (TokenController.java:36).
Defaults of app.widget.allowed-origins
Default values (application.yml:273-276):
localhostand127.0.0.1(suffix-match: any subdomain or port).neuroon.ai(suffix-match: includes*.neuroon.ai, e.g.dev.neuroon.ai,cdn.neuroon.ai).- The
shop.urlregistered in the dashboard.
Adding a new domain
- For your primary domain: edit it from the dashboard → your shop → Settings → Storefront URL. The change applies to the next request served.
- For extra domains (staging, branches, marketing domains sharing the widget): open a support ticket with the
shopIdand the domains. The list lives in backend config (app.widget.allowed-origins).
Self-service for extra domains is on the roadmap. Until then, the dashboard only edits
shop.url(a single value).
Common errors
| Symptom | Cause | Fix |
|---|---|---|
Browser blocks XHR with CORS error and console shows 403 | The widget is running on a domain that is neither in allowed-origins nor matches shop.url. | Check shop.url or ask to add the domain to allowed-origins. |
403 DOMAIN_MISMATCH when calling /verify | The url in the body does not match shop.url. | Edit shop.url in the dashboard and retry. |
401 when serving the widget from the browser | Token expired, not present in the HTML, or environment mixup (DEV token against api.neuroon.ai). | Refresh the token and double-check the base URL. |
Further reading
- Authentication · Widget Token — HMAC format and TTL.
- Authentication · Shop API Key —
sk_…format and rotation. - Recipe · Server-to-server token — caching and rotation pattern.
- Custom · Server-to-server — includes domain verification without the plugin.