Add an integration adapter
Verify a provider's credentials, encrypt them, and turn its inbound events into canonical records.
Updated September 19, 2026 2 min read
When a workflow needs data or actions from another service, an adapter connects that provider to the platform’s existing records and permissions. Implement the IntegrationAdapter contract in src/lib/revenue-os/integration-adapters.ts so credential checks, incoming events and supported operations have a defined owner.
WhatsApp, HubSpot, Stripe and Postiz are registered adapters. OpenRouter and MCP stay outside it on purpose (tenant-scoped encryption and a server-issued key). Most new adapters will not need that exception.
Registration
Implement verify (a real call against the provider) and connect (the receipt that gets stored). Declare credentialFields so each form field maps to the key stored in integration_connections.encrypted_credentials.
Register the adapter in INTEGRATION_ADAPTERS in the same file. configureAdapterProvider() in the tenant providers route looks it up by id, calls verify, encrypts each declared field, and audits the write. Add the validated configure/disconnect action to that route when introducing a provider; keep its credential lifecycle in the shared adapter service.
Rules that are not optional
- Credentials are encrypted at rest through the shared provider credential helpers; new tenant credentials use authenticated tenant/provider/field binding. Never store or return plaintext.
- Writes are tenant-bound. Inside a webhook or system-context path, build the client with
createServiceRoleClient(provider.context), never the unbound platform client. The unbound client writes rows with notenant_id. - Inbound webhook handlers live under
src/app/api/public/[tenantSlug]/webhooks/<provider>/, verify the provider signature withtimingSafeEqual, and reject replays outside a bounded window. - Do not add another analytics, email, AI, or scheduling vendor when the existing first-party path can do the work.
Inbound shape
Normalize the provider event into the canonical inbound service (identity.ts, inbound.ts, activities.ts). Do not insert operational rows from the webhook handler itself. The handler authenticates, calls the service, and returns a truthful receipt.
A separately hosted service
Social Marketing uses a Postiz adapter with an operator-fixed HTTPS origin and a verified organization identity. Each workspace connects a separate organization; group filters are not a tenant boundary. Publication stays behind the host's approval queue and durable attempt, because the upstream create-post API has no idempotency guarantee.
See Social Marketing for the user journey and the service packaging guide for the reusable deployment, licensing, recovery and verification pattern. Source metadata helps track the release; it does not prove live readiness.