Offer Notion, GitHub and Slack in your product (experimental)
Your company owns the provider app and its branding. Your backend holds the LBB stack key, chooses a graph for each customer, and creates a temporary connection link. Customers consent to your app, select resources, and return to your product without an LBB account. LBB seals their grants and runs the source readers.
This is an opt-in beta. Fixture tests cover the local flow; they do not prove live provider acceptance, distribution approval, or permission to index a customer’s data. Enable and test your own apps before offering the flow to users.
Enable and register an app
Section titled “Enable and register an app”- Ask your operator to enable developer integrations for the stack and the
server’s
LBB_PROVIDER_APPS_ENABLED=1flag. The API and worker require the same explicitLBB_CREDENTIALS_KEY. Use separate test and production stacks. - In the console, select the stack, then open Integrations → Provider apps. App management requires an account session with stack-management permission. Stack API keys can list safe app summaries and create customer links; they cannot manage app credentials.
- Register your app’s identifiers and secrets. Copy the callback and events URLs displayed by LBB into the provider’s settings. If your product already receives events, configure forwarding that preserves the original body and signing headers; do not replace an existing receiver without arranging delivery.
- Allow your product’s return origin, such as
https://app.example.com. HTTPS is required except for loopback development. A saved Configured state records configuration; it does not certify provider approval or a successful install.
Keep app credentials in this authenticated setup flow. Customers never paste
provider tokens into the hosted consent page. Direct credential-based connection
creation is refused for hosted notion, github and slack connections.
The target must be a property graph; RDF-native graphs are refused during setup.
Rotate in two stages. Keep the current primary unchanged while adding the future key to every reader’s decryption-only keyring:
| Process | Decryption-only setting | Format |
|---|---|---|
| SaaS API and integration worker | LBB_CREDENTIALS_PREVIOUS_KEYS |
Comma-separated base64 keys |
| Separate CDC host | LBB_CDC_OPEN_ONLY_KEYS |
JSON array of base64 key strings |
Both settings accept up to eight keys. Verify every reader has restarted with the
future key before promoting it to LBB_CREDENTIALS_KEY. Retain the former primary
in each reader’s decryption-only setting during that second rollout. The CDC host
does not read LBB_CREDENTIALS_PREVIOUS_KEYS. Deployment preflight validates the
configured target fleet before changing services; the operator must also verify
the separately managed CDC hosts.
Keep former keys while saved grants, registrations or callback URLs still use them;
changing the primary does not rewrite stored records. Lost keys cannot decrypt
their old records.
| Provider | Developer setup | Customer scope and current reader |
|---|---|---|
| Notion | A public connection for Any workspace, with Read content; client ID, secret and integration ID. Configure its webhook subscription. Retrieve the candidate verification value from LBB’s app-management page, verify it in Notion, then explicitly confirm it in LBB. | Explicit pages and data sources inside the consent grant. Page roots include descendants; data sources include their rows. Pages, bounded block text and source metadata map onto Documents. No attachments are downloaded. |
| GitHub | A public GitHub App for installation by other accounts; App ID, client ID/secret, private key, webhook secret and slug. Request read-only Metadata, Issues and Pull requests; enable expiring user tokens and repository/issue/PR/comment/review events. | Up to 100 repositories in one installation, intersected with the connecting user’s access. Repository metadata, issues/PRs, comments and reviews map onto Work. GitHub.com only. |
| Slack | A distributed confidential app; App ID, client ID/secret, signing secret and a one-time app configuration access token. Bot scopes: channels:read, channels:history, groups:read, groups:history. Configure message/lifecycle events and the Events URL. |
Explicit joined, non-shared channels, with messages and threads. No DMs or Slack Connect. History stays disabled until the owner has the required provider/use-case agreement and explicitly enables the history profile in LBB. |
For GitHub, customers must install the app as well as authorize their user. If the resource list is empty, follow the app’s installation link, complete any required organization approval, then start a new connection link. Callback installation IDs alone do not establish access. Slack’s history flag records the owner’s configuration; LBB does not verify an agreement merely because it is enabled.
Slack setup requires an App configuration access token generated by an app
manager at Slack’s app settings. LBB uses
apps.manifest.export
to verify access to the exact registered app, then discards the token. It is not
stored with app credentials and is not a customer’s bot token or app-level token.
The management request uses secrets.ownershipToken. For an existing registration
without proof, use Edit to supply it before new consent. Existing grants stay
readable and local disconnect stays available. Only proven registrations can
reserve global mint capacity; a public App ID alone is insufficient.
Provider setup references: Notion public connections and webhooks, GitHub App visibility and sharing an installation link, Slack OAuth, Events API and commercial distribution terms.
Create the customer link from your backend
Section titled “Create the customer link from your backend”Use the client from Integrations for your customers.
Derive graph from your authenticated customer record. A stack key can access
every graph in the stack, so never accept a customer-supplied graph as authority.
const { apps } = await lbb.integrations.providerApps();const app = apps.find((app) => app.active && app.kind === "notion");if (!app) throw new Error("Notion is not configured for this product");
const session = await lbb.integrations.createConnectSession({ appId: app.id, graph: authenticatedCustomer.graph, connectionId: "notion-main", returnUrl: "https://app.example.com/settings/connections",});// Redirect this customer to session.url. Do not log or cache the bearer link.apps = lbb.integrations.provider_apps()["apps"]app = next(app for app in apps if app["active"] and app["kind"] == "notion")session = lbb.integrations.create_connect_session( app_id=app["id"], graph=authenticated_customer.graph, connection_id="notion-main", return_url="https://app.example.com/settings/connections",)# Redirect this customer to session["url"]. Keep it out of logs and analytics.The browser visits LBB’s public connect page, completes provider consent, selects
resources explicitly, then returns to the registered product origin. LBB adds
lbb_session to the return URL. Your backend must fetch that session and verify
both the customer’s graph and complete status. Browser parameters alone do not
prove success.
const result = await lbb.integrations.getConnectSession(returnedSessionId);if (result.graph !== authenticatedCustomer.graph || result.status !== "complete") { throw new Error("Connection has not completed for this customer");}const { connections } = await lbb.integrations.list({ graph: result.graph });Python uses get_connect_session(id) and dictionary fields. The async client
offers the same methods. Poll connection status for sync progress: a completed
consent session does not mean its first sync has finished. There is no product
notification webhook in this beta.
Sessions expire after 30 minutes and each creation consumes the creation quota. The SDK does not automatically retry creation. Request a fresh link after expiry or an ambiguous code-exchange outcome; an authorization code is single-use. Treat the link as a bearer secret, including in browser analytics and referrer handling.
The authorization link must finish in the browser that started it. LBB checks an HttpOnly, SameSite cookie at the provider callback before exchanging the code. Opening a copied provider authorization URL in another browser fails safely.
Reconnect, rotate or disconnect
Section titled “Reconnect, rotate or disconnect”Reauthorize with the existing connectionId and the same selected resources.
Resource changes require a new graph and rebuild in this beta. A new
connection ID in the same graph retains the old source data. Hosted app
connections reject direct setCredentials and setSettings changes.
Rotate developer app secrets from Provider apps; blank secret fields preserve the saved values. Keep the same app identity for rotation. A replacement provider app requires a new registration and customer consent. Disable an app to prevent further use of its LBB grants and revoke their provider tokens. Failed provider calls are retried independently. Re-enabling an app does not restore revoked customer grants; customers must connect again.
integrations.delete(id, { graph }) in TypeScript, or
integrations.delete(id, graph=graph) in Python, removes the local connection
and revokes its provider tokens. Imported graph data remains. LBB blocks
new provider reads immediately; if the provider is unavailable, disconnect reports a retryable
error and retains sealed credentials only for recovery. Retries stop after eight
attempts or 24 hours, retaining a sealed diagnostic record and releasing active
capacity. Pending retries do not block unrelated customer connections. Tokens
held by another live grant or consent session for the same app are protected;
Slack token revocation never uninstalls the workspace app. Staging and production
may share a physical Slack app. Shared and legacy registrations require manual
remote revocation: disconnect and erase fence local access and archive the grant
as shared_token_manual. Coordinate revocation across environments, then acknowledge
it through the operator route. The console shows a read-only Revocation policy. Fresh exclusive
registrations retain automatic revocation after a verified grant. Only a successful
exchange returning the registered Slack app ID establishes membership; a second
verified registration permanently selects manual revocation. Denied consent,
missing codes and wrong-app grants cannot change the policy. Consent attempts
hold only expiring two-minute token-operation reservations. Registrations from
older policy versions remain manual. Safety history survives stack erasure
without locking new registrations out.
Use customer erasure
when every connection and all data in that customer’s graph must be removed.
Erasure proceeds after local access is fenced, even if provider revocation fails.
New connect links do not wait for old provider retries. Retry queues have their own
capacity; overflow or exhausted attempts retain sealed archives for operator action.
Service admins can list, retry or acknowledge them through
/admin/provider-revocations. Expired consent records are reclaimed after archiving.
Repeated client retries respect backoff and do not exhaust provider attempts early.
New archive logs carry the stack, app and archive IDs needed by these routes.
The admin-protected /metrics endpoint also exposes
lbb_provider_revocation_archived_total{kind="auth"|"session"}, a process counter
of newly archived grants, excluding duplicate retries. It resets on restart and
does not represent the current unresolved backlog.
A missing sealing key also preserves the opaque grant after fencing local access;
restore the key or revoke externally and acknowledge.
For an unverified Notion subscription with a stale challenge, use Discard unconfirmed token in app settings and request a fresh challenge from Notion. Discard uses the displayed candidate version and refuses to overwrite a newer or already trusted verification. Confirmation and discard hold a two-minute claim; after a crashed request it expires, so retrying setup cannot leave an older confirmation free to publish a discarded token.
Data and access limits
Section titled “Data and access limits”These readers use bounded source passes and signed events; they do not promise
complete incremental replication. Missing records in a partial listing do not
prove deletion. Verified deletion markers live in the graph, with no lifetime
256-deletion limit; each turn processes a bounded batch. The reserved
LbbIntegrationDeletion bookkeeping class is visible in raw queries and class
inspection and Ask names, and counts toward storage. Select your source classes in app queries.
Markers survive disconnect/full sync. Notion restore events verify the resource’s
current state before allowing active content again. A later trash row can subsequently retract that page even if its delete webhook
is lost. When restore and re-trash share Notion’s coarse timestamp, LBB checks the
resource’s current state rather than treating the equal timestamp as a restore. Other sources require a new
connection identity or rebuilt graph for restore. At the graph storage limit,
marker writes can still block retraction; metadata is not exempt from storage quota. Clearing every old source property and
relationship is not fully reconciled. Explicit entity deletion can also remove
properties your product added to that imported entity, so keep enrichment that
must survive source deletion on separate entities.
Webhook intake captures a routing snapshot and saves the payload in a bounded
durable inbox before replying. Each of 16 hash shards admits at most 64 deliveries
and 48 KiB. A full shard returns 503 with Retry-After: 30 before acceptance;
unaccepted deliveries require provider or forwarding-service retries. GitHub does
not automatically redeliver failures;
its owner can redeliver through the UI or API within three days. Oversized deliveries
return 413. Background workers move accepted work into each customer’s own paged
storage, even when its 128-delivery working queue is full. Each page holds at most
128 references and 48 KiB. A full customer queue no longer occupies shared
admission capacity; sustained ingress above fanout capacity or a control-plane
outage can still affect the app. Accepted deletion work remains until delivered or
its authorization generation retires, so backlog storage grows with accepted work.
Workers wake on arrivals and retry deadlines.
Slack’s three-second reply target still needs live load validation. Notion signed events have a seven-day age limit.
GitHub events without a trustworthy occurrence timestamp cannot all receive the
same check. Small completed and unmatched event fences remain until stack/account erasure to
protect acknowledged deliveries from concurrent cleanup and prevent known delivery
IDs from rebinding after reconnect. GitHub delivery IDs are in unsigned headers; a
captured payload submitted under a new ID still has no universal timestamp check.
An unmatched delivery keeps its fence so replay after a new consent cannot attach
it to that customer. Retired overflow and journal pages retain compact closed-page
fences after their payloads and references are removed; these grow with page
rotations and also remain until stack erasure. Physical reclamation is future work.
Background retries skip unchanged progress/cursor writes and rotate a failed
batch once; a failed promotion does not block its existing working queue.
GitHub quota resets save a durable deadline and finish the turn. GitHub fit planning and suggestion application use a local schema and run during the pause; credential checks still need a provider request and wait. A paused Apply for a connector needing remote schema remains visibly tracked until it finishes. Settings and mapping saves apply immediately; credential checks and source-dependent fit checks show that they are waiting, then follow the resumed action’s actual result. The host tick resumes due work, including after a resume turn was skipped.
Coalesced webhook hints preserve the cursor and retain a follow-up request for resources already read, including hints arriving during the first source turn. GitHub fresh passes triggered by hints wait at least five minutes after the last pass finishes. Hints during that interval share one durable request, which also runs on manual schedules. This interval does not delay explicit user syncs, configured schedules, continuing the current pass or processing deletion events. It limits repeated full scans; a large pass can still exhaust GitHub quota. A delayed worker shows Resume delayed in the console. Old unreleased builds may have checkpointed a timer turn that retains its original wake time until the engine claims it. Removed repositories, inaccessible pages and unavailable channels produce sync warnings and allow remaining sources to proceed. Existing imported facts remain until there is authoritative deletion evidence or explicit erasure.
Losing provider access stops subsequent authorized reads; it does not guarantee immediate erasure of previously imported facts or cancel an already-issued write. Stored source ACL fields do not automatically filter SPARQL queries. Your backend must authenticate users and enforce which customer graphs and results they can read.
Run the local product example
Section titled “Run the local product example”The repository includes sdk/typescript/examples/embedded-integrations/server.mjs.
It has Connect, backend-verified status and Disconnect, using one fixed demo
customer. From the repository root:
npm cinpm run build -w @littlebigbrain/clientexport LBB_STACK_URL='https://YOUR-STACK.db.eu.littlebigbrain.com'export LBB_STACK_API_KEY='YOUR_TEST_STACK_KEY'node sdk/typescript/examples/embedded-integrations/server.mjsAllow http://localhost:8080 in the provider app’s return origins, then open
that exact address. PORT changes the demo’s port and DEMO_CUSTOMER_GRAPH
changes its backend-selected graph. Register a new return origin when changing
the port. The example binds to loopback and rejects cross-origin mutations.
LBB_INTEGRATIONS_URL optionally selects another LBB API. Its operator must set
LBB_SAAS_PUBLIC_URL for reachable provider callbacks/events and
LBB_SAAS_WEB_URL for the console’s consent page. The provider callback is an
LBB API URL, not the product’s localhost:8080 return URL. Before adapting
this example for production, replace its fixed customer with your authenticated
product session and keep the stack key on your backend.
Expected Notion text, block-request, property and relation bounds remain visible
in each record’s textStatus, propertiesStatus or relationsStatus as
truncated; they do not mark the whole sync as warning. Skipped or inaccessible
sources remain incomplete and still raise warnings.