Skip to content

Offer Notion, GitHub and Slack in your product (experimental)

View Markdown

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.

  1. Ask your operator to enable developer integrations for the stack and the server’s LBB_PROVIDER_APPS_ENABLED=1 flag. The API and worker require the same explicit LBB_CREDENTIALS_KEY. Use separate test and production stacks.
  2. 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.
  3. 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.
  4. 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.

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.

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.

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.

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:

Terminal window
npm ci
npm run build -w @littlebigbrain/client
export 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.mjs

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