Skip to content

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.

  1. Get the CLI and scaffold the starter app

    With the onebooks CLI installed:

    Terminal window
    onebooks init my-onebooks-app
    cd my-onebooks-app
    npm install

    init copies the starter template into my-onebooks-app/ — a plain node:http server with no framework and no build step — and points its @onebooks/node dependency at the SDK version matching your CLI. See Starter template for every file and route in it.

  2. 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 page
    • app-data:read — read the tracking number back
    • app-data:write — save the tracking number

    Your sandbox install (step 7) grants whatever your app has registered here.

  3. Declare the tracking_number field

    The 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:

    SettingValue
    Resource typeINVOICE
    Field typeTEXT
    Keytracking_number
    Field nameTracking number (English is required; other languages are optional)
    Merchant visibleOn

    Skip this and the action fails with UNKNOWN_APP_DATA_FIELD — the API rejects a value for a key your app never declared.

  4. Configure and run the server

    Terminal window
    cp .env.example .env
    VariableValue
    ONEBOOKS_CLIENT_IDYour app’s client_id (Credentials tab)
    ONEBOOKS_CLIENT_SECRETIts client_secret — the server refuses to start without both
    ONEBOOKS_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 app
    NODE_ENVLeave unset while developing; set production when you deploy (see Starter template)
    Terminal window
    node server.mjs
    # OneBooks example app listening on http://localhost:3100
  5. Expose it over HTTPS

    OneBooks renders your pages in an iframe on https://app.getonebooks.com, and it only frames https URLs. In another terminal, start a tunnel to port 3100 and note the https:// address it prints:

    Terminal window
    cloudflared tunnel --url http://localhost:3100
    # or: ngrok http 3100

    The 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://localhost App URL, but only a OneBooks frontend running on your own machine will frame it — app.getonebooks.com never does. See Embedded apps.)

  6. 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:

      PlacementLabelPathInitial 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.

  7. 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 ACTIVE installation 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.

  8. 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-action page, 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.

Every request your pages made followed the same path, and it’s the one every embedded app uses:

  1. The page called app.fetch('/api/…') from App Bridge. Because the target is your own origin, App Bridge attached Authorization: Bearer <session token> — a 60-second, signed proof of which user and business the request comes from.
  2. 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.json in this example); every later request just verifies the session token and reuses the cache, refreshing it when it nears expiry.
  3. With that access token it called the OneBooks API — GET /invoices/:id, then PUT /app-data to save the value, and GET /app-data from the block.
  4. The page reported back through App Bridge: app.toast(), then app.close(); the block uses app.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:

Terminal window
onebooks login
onebooks 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 --activate

Mark an invoice in the sandbox as paid, then check the run:

Terminal window
onebooks functions logs --app <appId> --name notify-on-paid

Hosted 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.