Quickstart: build an embedded app
This walkthrough gets the embedded-node starter app running inside
OneBooks, in one of your sandbox businesses. When you’re done, a merchant can
open an invoice, choose Save tracking number from its Apps menu, and
see the saved number appear in a card on the same invoice — your page, your
backend and the OneBooks API working together.
Before you start you need what Getting started
steps 1–5 set up: a developer account, an organization, a registered app and a
sandbox business with its one-time owner login. This quickstart replaces that
page’s OAuth-redirect step 6. You also need Node.js 20 or later, and a way to
expose a local port over https — cloudflared or ngrok both work.
-
Get the CLI and scaffold the starter app
With the
onebooksCLI installed:Terminal window onebooks init my-onebooks-appcd my-onebooks-appnpm installinitcopies the starter template intomy-onebooks-app/— a plainnode:httpserver with no framework and no build step — and points its@onebooks/nodedependency at the SDK version matching your CLI. See Starter template for every file and route in it. -
Request the scopes the example uses
In the developer console, open your app’s OAuth tab and, under Scopes, request:
invoices:read— read the invoice the action runs on, and list recent invoices on the app’s home pageapp-data:read— read the tracking number backapp-data:write— save the tracking number
Your sandbox install (step 7) grants whatever your app has registered here.
-
Declare the
tracking_numberfieldThe example stores its tracking number as app data, and OneBooks only accepts values for fields you’ve declared. On the app’s App data tab, select Add field:
Setting Value Resource type INVOICEField type TEXTKey tracking_numberField name Tracking number(English is required; other languages are optional)Merchant visible On Skip this and the action fails with
UNKNOWN_APP_DATA_FIELD— the API rejects a value for a key your app never declared. -
Configure and run the server
Terminal window cp .env.example .envVariable Value ONEBOOKS_CLIENT_IDYour app’s client_id(Credentials tab)ONEBOOKS_CLIENT_SECRETIts client_secret— the server refuses to start without bothONEBOOKS_WEBHOOK_SECRETOptional here: the whsec_…secret of a webhook pointing at/webhooks(see Starter template)ONEBOOKS_API_BASEhttps://api.getonebooks.com(the default)PORT3100(the default)APP_ORIGINhttps://app.getonebooks.com— the OneBooks origin allowed to frame the appNODE_ENVLeave unset while developing; set productionwhen you deploy (see Starter template)Terminal window node server.mjs# OneBooks example app listening on http://localhost:3100 -
Expose it over HTTPS
OneBooks renders your pages in an iframe on
https://app.getonebooks.com, and it only frameshttpsURLs. In another terminal, start a tunnel to port 3100 and note thehttps://address it prints:Terminal window cloudflared tunnel --url http://localhost:3100# or: ngrok http 3100The CLI has no tunnel command of its own. A quick tunnel’s address usually changes each time you restart it — update the App URL in the next step when it does. (The API also accepts an
http://localhostApp URL, but only a OneBooks frontend running on your own machine will frame it —app.getonebooks.comnever does. See Embedded apps.) -
Set the App URL and add the two extensions
On the app’s Embedding tab:
-
App URL: the tunnel’s
https://address. This is your app’s home page inside OneBooks, and the origin every extension path resolves against. -
Add extension twice:
Placement Label Path Initial height Invoice — Action (menu item) Save tracking number/invoice-action— Invoice — Block (page card) Tracking number/invoice-block120
Those are the two extension pages the starter server serves. See UI extensions for the other 13 placements.
-
-
Install it into your sandbox
Still on the Embedding tab, the Install in a sandbox panel lists your organization’s sandbox businesses. Pick one and select Install (org admins only). That creates an
ACTIVEinstallation directly — no consent screen, no review needed — with every scope your app registered in step 2. No sandbox yet? Create one on the console’s Sandbox page first; see Sandbox. -
Open it and save a tracking number
Sign in to app.getonebooks.com (opens in a new tab) with the sandbox owner’s email and one-time password.
- Your app’s home page: your app now has its own row under Apps in the sidebar. It opens your App URL, which shows the business name, the merchant’s language and theme, and the ten most recently created invoices, read through your backend.
- Open any invoice (create one first if the sandbox is empty). An Apps menu appears in the page’s actions, and a Tracking number card lower on the page reads Not set yet.
- Choose Apps → Save tracking number. A dialog opens your
/invoice-actionpage, which saves a generated number (TRK-<invoice number>-…), shows a toast and closes itself. OneBooks then reloads the invoice’s app cards: the Tracking number card shows the new value, and an App data card lists it under your app’s name.
What just happened
Section titled “What just happened”Every request your pages made followed the same path, and it’s the one every embedded app uses:
- The page called
app.fetch('/api/…')from App Bridge. Because the target is your own origin, App Bridge attachedAuthorization: Bearer <session token>— a 60-second, signed proof of which user and business the request comes from. - Your backend verified that token against OneBooks’ public keys. The
first time it saw that business-and-user pair, it
exchanged the token for a normal access and
refresh token and cached them (in
data/tokens.jsonin this example); every later request just verifies the session token and reuses the cache, refreshing it when it nears expiry. - With that access token it called the OneBooks API —
GET /invoices/:id, thenPUT /app-datato save the value, andGET /app-datafrom the block. - The page reported back through App Bridge:
app.toast(), thenapp.close(); the block usesapp.autoResize()to fit its content.
The end-to-end guide builds a different action from nothing, explaining each of those moves as you write them.
Optional: deploy the example hosted function
Section titled “Optional: deploy the example hosted function”The template also ships functions/notify-on-paid.js, a
hosted function that logs a line whenever an
invoice becomes fully paid. OneBooks runs it for you — no server involved:
onebooks loginonebooks apps list # copy your app's "id" (not its clientId)onebooks functions deploy --app <appId> --name notify-on-paid \ --file functions/notify-on-paid.js --events invoice.paid --activateMark an invoice in the sandbox as paid, then check the run:
onebooks functions logs --app <appId> --name notify-on-paidHosted functions only run where the platform has them switched on; if the run
shows SKIPPED with Hosted functions are not enabled on this environment,
that’s the reason — see Hosted functions.