Queek docs

App SDK

Use @usequeek/app-sdk for install handoffs, embedded session tokens, webhook verification and a typed Merchant API client, with a working server example.

@usequeek/app-sdk is the server library for a Queek app. It verifies every signed request Queek sends, stores installations encrypted, mints the short-lived tokens your app uses to call the Merchant API, and gives you a typed client for those calls.

npm install @usequeek/app-sdk pg

Requires Node.js 22.14 or later. pg backs the production installation store. The Hono wrappers need the optional hono peer package, and the React helpers need react, only if you use them. The SDK is pre-1.0, so pin an exact version.

A working server

This is a complete server module. It answers the install, uninstall and settings handoffs, verifies and dispatches an orders/paid webhook, and calls the Merchant API from inside the handler. Each route is a plain Request in, Response out function, so it mounts in a Next.js route handler, an Express adapter or any Web-standard runtime.

server.ts
import {
  createAppTokenProvider,
  createInstallationClient,
  handleInstallRequest,
  handleWebhookRequest,
  loadAppCredential,
  QueekApiError,
  SqliteInstallationStore,
} from "@usequeek/app-sdk";

const store = new SqliteInstallationStore({
  path: "./data/installations.db",
  storeKey: process.env.APP_ENCRYPTION_KEY!,
});

// Reads APP_KEY_ID and APP_PRIVATE_KEY. The slug is the one in queek.app.toml.
const tokens = createAppTokenProvider({
  credential: loadAppCredential({ appSlug: "my-app" }),
  store,
});

export function POST(request: Request): Promise<Response> {
  const { pathname } = new URL(request.url);

  if (pathname.endsWith("/webhooks")) {
    return handleWebhookRequest(request, {
      store,
      handlers: {
        "orders/paid": async (envelope, context) => {
          const client = createInstallationClient({
            installationId: context.installation.installationId,
            apiBase: context.installation.apiBase,
            tokens,
          });
          try {
            const orders = await client.requestWithRetry<{ data: unknown[] }>("GET", "/orders", {
              query: { limit: 20 },
            });
            console.log(envelope.data, orders.data.length);
          } catch (error) {
            if (error instanceof QueekApiError) {
              console.error(error.status, error.code, error.requestId);
            }
            throw error;
          }
        },
      },
    });
  }

  // Routes on the trailing path segment: install, uninstall or settings.
  return handleInstallRequest(request, {
    appSecret: process.env.QUEEK_APP_SECRET!,
    store,
  });
}

For an order event, the handler receives the full envelope and the order is envelope.data. There is no envelope.data.order wrapper. See webhook topics and payloads. The webhook handler dedupes on webhook-id, so each event runs your handler at most once per installation.

Import the right entry

ImportUse
@usequeek/app-sdkServer handlers, Merchant client, stores and signature verifiers. Includes Node modules, so it is not browser-bundlable.
@usequeek/app-sdk/serverServer-side verification of embedded session tokens.
@usequeek/app-sdk/honoOptional Hono wrappers for install, webhook and proxy handlers.
@usequeek/app-sdk/reactQueekProvider and useQueek() for the embedded page.
@usequeek/app-sdk/browserBrowser-only bridge, theme and authenticated fetch helpers, without React.

Never import the main entry from browser code: a bundler will try to resolve its Node-only files and fail. In the browser use /react or /browser.

Environment

VariableValue
QUEEK_APP_SECRETThe whsec_ signing secret that verifies every handoff. Shown once on first deploy.
APP_ENCRYPTION_KEY32 random bytes, base64 or hex. Encrypts installation data at rest.
APP_KEY_IDId of the public key registered under the app's Keys in the Developer page.
APP_PRIVATE_KEYThe matching private key, as PEM or base64 of the PEM. Never log it or ship it to a client.
DATABASE_URLPostgres connection string. Required in production; the SDK refuses the development SQLite file there.

Embedded session tokens

Queek sends one kind of signed session token to an embedded page. The dashboard puts it in the first-load queek_token parameter and can send the same token again over the bridge when the page needs a refresh. The browser helper strips the parameter and exchanges the token with your server. Verify both first-load and refresh tokens with the same verifier from @usequeek/app-sdk/server, and keep the signing secret on the server.

The page tells the dashboard it is ready with a ready message; the dashboard waits 15 seconds for it and shows an error card if it never arrives. installThemeListener and QueekProvider send it for you from 0.7.2 on, and versions up to 0.7.1 did not send it from installThemeListener. The messages, origins and timings are in Embedded apps and the dashboard bridge.

Merchant API authentication is a separate, server-side flow. Your app's asymmetric credential mints and caches short-lived installation tokens, and the Merchant client sends one as X-Client-Key. Never send app credentials or Merchant API tokens to the browser.

Errors and retries

Every failed Merchant API call throws a QueekApiError with status, code, requestId, field, errors and, on a 429, retryAfterMs. Branch on code, and log requestId.

  • Use client.requestWithRetry for calls that may be retried. It retries on 429 and network errors with backoff, honours Retry-After, and reuses one Idempotency-Key across attempts, so a write cannot execute twice.
  • Use client.request when you want no retry.
  • On a stale token (401, or 403 with api_key_revoked, api_key_expired or invalid_client_key) the SDK re-mints once and retries once.
  • 403 app_token_revoked means the app was disabled: the SDK stops minting until the app is re-enabled and the process restarts.
  • 409 app_installation_pending means the installation is not active yet. Retry later.

The full code list is in Merchant API errors.

Typed Merchant API calls

Run queek app codegen to write types for the live Merchant spec to types/merchant.ts, then pass them to the client: createInstallationClient<AppPaths>(…). Re-run it when the Merchant API changes, and review the diff before you commit. The default types bundled with the SDK do not update on their own.

Data and uninstall

The app database holds installations only: references, cached tokens, webhook secrets and settings. Business data lives in Queek. Treat the app/uninstalled handoff as the signal to delete that installation and anything of yours keyed to it; the default handler already deletes the installation row.

The handoff body is { type, data }, where data carries installation and store. When the merchant deletes the whole store, data also carries reason: "store_deleted". A merchant uninstalling the app on its own sends no reason, so treat an absent reason as an ordinary uninstall. A store is deleted only after its grace period ends, and the handoff arrives once per installation, signed like every other handoff. Both cases mean the same for your data: delete everything you hold for that installation, including any copy of the store's customer data. To learn that one customer's data was requested or must be erased, subscribe to the customer privacy topics.

See the @usequeek/app-sdk README for Express, Fastify and Hono examples, the storefront proxy verifier, and resync after data loss.

On this page