Skip to content

Backend & CORS

The widget renders entirely in the browser, but it calls Hobba’s backend for data and to submit transactions. By default these go to https://app.hobba.io, which you can override with the apiBaseUrl option.

In production, Hobba’s backend gates the sensitive routes by Origin: it only returns CORS headers for origins on its allowlist. A cross-origin request from an origin that isn’t allowlisted fails its CORS preflight - the browser blocks it because no Access-Control-Allow-Origin header comes back.

The backend does not reject these requests server-side - it simply omits the CORS header, so the browser is what blocks the cross-origin call.

This means a default embed (calling app.hobba.io directly from your origin) works for read-only data but the transacting routes will be CORS-blocked until your origin is handled. You have two ways to handle it.

Ask the Hobba team to add your deployed origin (e.g. https://yourapp.com) to the allowlist. Once it’s on the list, calls from your site to app.hobba.io get the CORS headers they need and everything works with the default apiBaseUrl. This is the simplest path for a production embed on a fixed domain.

Option 2 - proxy /api through your own origin

Section titled “Option 2 - proxy /api through your own origin”

Point apiBaseUrl at your own origin (or a backend you control) and forward /api/* to app.hobba.io server-side. Because the browser then only ever talks to your own origin, there’s no cross-origin request and no CORS preflight at all

  • so no allowlist change is needed.

When you proxy, present the forwarded request as a first-party call (set the Origin / Referer headers to https://app.hobba.io) so the backend treats it normally.

The same allowlist makes plain localhost development awkward: localhost is not allowlisted, so direct calls to app.hobba.io get CORS-blocked.

The clean fix is a dev proxy - route /api/* same-origin through your dev server, which forwards to app.hobba.io server-side. With Vite:

vite.config.ts
export default defineConfig({
server: {
proxy: {
"/api": {
target: "https://app.hobba.io",
changeOrigin: true,
secure: true,
configure: (proxy) => {
// Present as a first-party call so the backend treats it normally.
proxy.on("proxyReq", (proxyReq) => {
proxyReq.setHeader("origin", "https://app.hobba.io");
proxyReq.setHeader("referer", "https://app.hobba.io/");
});
},
},
},
},
});

Then point the widget’s apiBaseUrl at your dev origin so its calls go through the proxy. The widget builds a web3.js Connection from an RPC endpoint under apiBaseUrl, which needs an absolute URL - use window.location.origin, not an empty string:

const apiBaseUrl = import.meta.env.DEV
? window.location.origin // → dev proxy, same-origin, no CORS
: "https://app.hobba.io"; // → production default (must be allowlisted)

The browser only ever talks to localhost, so both the read routes and the transacting routes work in local dev with no allowlist change.

ScenarioapiBaseUrlWhat’s required
Read-only data, any origindefault (app.hobba.io)Nothing
Production embed, transactingdefault (app.hobba.io)Your origin on the allowlist
Production embed, self-hostedyour originProxy /api/*app.hobba.io
Local devwindow.location.originDev proxy → app.hobba.io