Integrations engine
Understand provider ownership, secure project connections, lifecycle actions, MCP boundaries, and safe migration or key rotation.
- 6 min read
- Updated Aug 31, 2026
Nibleaf's integrations catalog is a discovery and lifecycle layer for external provider connections. It does not replace provider-specific domain models. Git, analytics, and instance infrastructure remain authoritative in their existing modules; project webhook connections use the common encrypted lifecycle.
Ownership and provider inventory
| Provider group | Providers | Lifecycle owner | Provider boundary |
|---|---|---|---|
| Source control | GitHub, GitLab, public Git | Existing Git connection or workspace metadata adapter | Official Octokit for GitHub; existing Git adapters for other imports |
| Site analytics | Google Analytics 4, Plausible | Existing project configuration adapter | Published-site analytics configuration |
| Webhooks and automation | Slack, Discord, Zapier | Project integration engine | Official @slack/webhook; Got only for Discord and Zapier, which have no required server SDK here |
| AI and search | OpenRouter, Qdrant | Instance-managed, read-only in project settings | Official @openrouter/sdk and @qdrant/js-client-rest |
| Product analytics | ClickHouse | Instance-managed, read-only in project settings | Existing ClickHouse client boundary |
| Postmark, SMTP | Instance-managed, read-only in project settings | Official Postmark SDK or the existing SMTP transport | |
| Asset storage | Maxio, MinIO, Amazon S3, Cloudflare R2, Backblaze B2 | Instance-managed, read-only in project settings | Existing AWS S3 SDK boundary |
| Custom domains | Cloudflare for SaaS | Existing domain provisioning adapter | Existing Cloudflare provider boundary |
Google OAuth signs users into Nibleaf and is intentionally outside the project-integration catalog. Add-ons own product feature switches, usage owns metering, and integrations own external provider connections. No provider in this engine processes payments.
Unavailable planned providers remain discoverable only when the manifest says so and advertise no mutation. Adding a catalog entry does not imply that a connection is functional.
Public contract
Every catalog response contains static capability/category metadata and an
optional project connection summary. The summary is provider-discriminated and
allowlisted. It includes status, health, revision, safe configuration, and only
credential.configured; it never includes credential material, a fingerprint,
provider response bodies, or arbitrary provider configuration.
The shared domain actions use the verbs list, get, create, update,
activate, deactivate, verify, and delete. Dashboard sessions exclusively
own credential creation and replacement. Provider-visible Slack and Zapier test
events require explicit dashboard confirmation; Discord and GitHub verification
are passive.
API keys must carry the exact integrations:read, integrations:write,
integrations:verify, or integrations:delete scope required by the action.
The legacy * scope grants no integration access. Every action reloads the key,
checks revocation and expiry, binds it to the project, and applies project
authorization itself. Cross-tenant access returns the ordinary not-found
envelope.
The stable action exports are listProjectIntegrations,
getProjectIntegration, createProjectIntegration,
updateProjectIntegration, activateProjectIntegration,
deactivateProjectIntegration, verifyProjectIntegration,
createIntegrationDeleteConfirmation, and deleteProjectIntegration. Their
shared request boundaries are integrationProviderIdSchema,
configurableIntegrationProviderIdSchema, createProjectIntegrationBody,
updateProjectIntegrationBody, integrationRevisionBody,
verifyProjectIntegrationBody, createIntegrationDeleteConfirmationBody,
deleteProjectIntegrationBody, and integrationConnectionSummarySchema.
Lifecycle clients may branch on the stable integration errors
integration:not_found, integration:provider_unsupported,
integration:provider_unavailable, integration:already_connected,
integration:invalid_configuration, integration:credentials_required,
integration:credentials_invalid, integration:inactive,
integration:verification_failed, integration:rate_limited,
integration:idempotency_conflict, integration:revision_conflict,
integration:external_confirmation_required,
integration:confirmation_invalid, integration:confirmation_expired, and
integration:confirmation_consumed. Unknown database or provider failures are
sanitized at the normal error boundary and are not relabeled as one of these
business conditions.
MCP may expose credential-free list, get, activate, deactivate, passive
verify, and confirmed delete only. MCP tools never accept, return, or persist
credentials. A Better Auth dashboard session mints the single-use delete
confirmation, optionally bound to one API key, project, provider, connection,
and revision. MCP cannot mint that confirmation or send a provider-visible test.
Secrets, replay, and audit
Set a distinct INTEGRATION_CREDENTIAL_ENCRYPTION_KEY containing exactly 32
base64-encoded bytes. It must not silently reuse the Git credential key.
Credentials use versioned AES-256-GCM envelopes. HMAC digests for idempotency
keys, request comparisons, and confirmations are purpose-separated. New writes
use the current key; reads also try INTEGRATION_CREDENTIAL_PREVIOUS_KEYS during
a bounded rotation window.
Mutation idempotency records retain a strict, redacted result DTO for the
configured retention window. Replaying an older key returns its original result
even after later mutations or deletion. A reused key with a different request
returns integration:idempotency_conflict. Raw keys, credential-bearing request
bodies, and provider response bodies are never persisted.
Request digests use an explicit provider-discriminated canonical serialization that excludes the separately digested idempotency key. Equivalent dashboard and MCP requests replay regardless of object property order; changing the provider, revision, configuration, or credential changes the digest and conflicts.
Connection revisions use an atomic compare-and-swap predicate. Webhook
verification commits the terminal delivery state, connection health/revision,
and audit record in one transaction after the external call. A competing update
is recorded and replayed as integration:revision_conflict.
Verification delivery records use a bounded running lease. A stale passive
Discord check can be reclaimed safely. A stale Slack or Zapier test is finalized
as the sanitized integration:provider_unavailable outcome without resending an
external message; retrying it requires a new dashboard confirmation and
idempotency key. Nibleaf does not claim exactly-once external webhook delivery.
Audit rows contain stable projectId and providerId values independent of the
mutable connection relation. Deleting a connection clears the optional relation
but preserves provider identity and the secret-free lifecycle history.
Consumed delete confirmations likewise retain a digest-only tombstone with a
cleared connection relation. Consumption uses one atomic claim, so replays and
concurrent losers return integration:confirmation_consumed after deletion.
Consumed tombstones remain available for the configured retention window, then
are removed by bounded project-local cleanup. Expired unconsumed challenges are
removed when the next challenge is created.
Key rotation and rollback
- Generate a new independent 32-byte base64 key. Set it as
INTEGRATION_CREDENTIAL_ENCRYPTION_KEYand add the old key toINTEGRATION_CREDENTIAL_PREVIOUS_KEYS. - Deploy all API replicas together. New credentials and digests now use the new key; existing ciphertext, ledger entries, and confirmations remain readable.
- Reconnect each project-owned webhook to re-encrypt it. Keep the old key until every credential is replaced and the longest idempotency and confirmation TTL has elapsed.
- Verify each connection, then remove the old key. To roll back during the window, restore the old key as current and retain the new key as previous.
The additive migration owns the nullable ApiKey.expiresAt column as a shared
authentication prerequisite. The MCP control-plane migration depends on it. To
roll back, first roll back any dependent MCP migration, stop integration
mutations, preserve required audit evidence, and remove the integration tables
in foreign-key-safe reverse order. Drop ApiKey.expiresAt only when rolling back
integrations before that dependent migration and after confirming no other
consumer relies on it. Restoring the prior application release is the final
step; never delete encrypted data before the rollback decision is complete.
The complete release dependency order and the distinction between runtime and destructive schema rollback are documented in the combined release migration guide.
Adding a provider
- Add the provider ID, manifest, capabilities, truthful availability, and an explicit branch in the public configuration union and replay validator.
- Decide whether an existing domain model remains authoritative, the provider is instance-managed, or the common project lifecycle is appropriate.
- Add a provider-specific boundary. Prefer the official SDK, then Got when no SDK is suitable; do not add raw HTTP beside an available supported client.
- Define secret fields as write-only, verification side effects, webhook signature/replay boundaries, stable safe error codes, and audit metadata.
- Add tenant, exact-scope, redaction, CAS, idempotency, rotation, RTL, mobile, and provider failure tests before advertising mutation support.