Skip to content

Provider Architecture

The SDK uses a provider pattern to decouple business logic from data sources. A provider is a plain object that implements one or more domain methods — placement resolution, entitlement checks, placement type persistence, and user identification.

interface RevTurbineSdkProvider {
getPlacement?: (config: RevTurbinePlacementRequestConfig) => Promise<PlacementOutput | null>;
checkEntitlement?: (handle: string, context?: RevTurbineEntitlementContext) => Promise<EntitlementResult>;
persistPlacementTypes?: (types: RevTurbinePlacementTypeEntity[]) => Promise<void>;
identify?: (userId: string, contextOrTraits?: UserContextInput | SdkTraits) => void;
}

Every method is optional. Implement only the domains you need to customize — the SDK falls back to its built-in local runtime for anything you don’t provide.

Resolves a placement for a given slot and surface type. Called by FixedSurfaceSlot, AccessGateSurfaceSlot, MessageSurfaceSlot, and the headless API.

const analyticsProvider: RevTurbineSdkProvider = {
async getPlacement(config) {
// config.slotId — the slot requesting a placement
// config.surfaceType — the surface type (button, modal, banner, etc.)
// config.entitlementHandle — for access gate slots
// Example: fetch from your own decisioning API
const res = await fetch(`/api/placements/${config.slotId}`);
if (!res.ok) return null;
return res.json();
},
};

Input — RevTurbinePlacementRequestConfig:

FieldTypeDescription
slotIdstring?Slot identifier
surfaceTypestring?Surface type (button, modal, banner, etc.)
entitlementHandlestring?Entitlement to check (access gates)
planHandlestring?Plan-specific placements
placementHandlestring?Chaining from a prior CTA path

Return: PlacementOutput | null — the resolved placement, or null if no placement matches.

Checks whether the current user has access to a feature. Called by AccessGateSurfaceSlot and sdk.checkEntitlement().

const entitlementProvider: RevTurbineSdkProvider = {
async checkEntitlement(handle, context) {
// handle — e.g. "data_export", "brand_kit"
// context — optional entitlement context
const res = await fetch(`/api/entitlements/${handle}`);
const data = await res.json();
return {
status: data.allowed ? 'allowed' : 'denied',
allowed: data.allowed,
};
},
};

Return — EntitlementResult:

FieldTypeDescription
statusstring'allowed', 'denied', 'usage_capped', etc.
allowedbooleanWhether access is granted

Stores placement type metadata. Called during SDK initialization to register built-in and custom slot types.

const storageProvider: RevTurbineSdkProvider = {
async persistPlacementTypes(types) {
// types — array of { id, label, description, surfaceType, priority }
await fetch('/api/placement-types', {
method: 'POST',
body: JSON.stringify(types),
});
},
};

Called when a user is identified or their context changes. Use this for analytics, session tracking, or syncing identity to your backend.

const identityProvider: RevTurbineSdkProvider = {
identify(userId, contextOrTraits) {
analytics.identify(userId, contextOrTraits);
},
};

Pass your provider via the provider option:

import { RevTurbineProvider } from '@revturbine/sdk';
import exportedConfig from './exported_config.json';
import { useMemo } from 'react';
const myProvider = {
async getPlacement(config) {
const res = await fetch(`/api/placements/${config.slotId}`);
if (!res.ok) return null;
return res.json();
},
async checkEntitlement(handle) {
const res = await fetch(`/api/entitlements/${handle}`);
const data = await res.json();
return { status: data.allowed ? 'allowed' : 'denied', allowed: data.allowed };
},
};
function App() {
const options = useMemo(() => ({
localRuntime: { exportedConfig },
provider: myProvider,
}), []);
return (
<RevTurbineProvider options={options}>
{/* slots will call myProvider.getPlacement() */}
</RevTurbineProvider>
);
}

If your provider needs access to the SDK’s init options, use a factory function:

const myProviderFactory = (options) => ({
async getPlacement(config) {
// options.localRuntime, options.user, etc. are available here
return null;
},
});
const options = {
localRuntime: { exportedConfig },
provider: myProviderFactory,
};

The SDK supports a chain of providers. If the primary provider returns null or throws, the next one is tried:

const options = useMemo(() => ({
localRuntime: { exportedConfig },
provider: apiProvider,
providerFallbacks: [cachedProvider, staticFallback],
}), []);

If all providers fail:

BehaviorResult
'invisible' (default)Slots render nothing
'placeholder'Slots show fallback content

Entitlement checks always fail-open to { allowed: true } to avoid blocking users.

The SDK can forward all impressions, interactions, and lifecycle events to your analytics platform (Segment, Heap, Amplitude, PostHog, Mixpanel, or any custom sink). Use createAnalyticsProvider() to create a domain provider that receives every SDK event.

import { RevTurbineProvider, createAnalyticsProvider } from '@revturbine/sdk';
import exportedConfig from './exported_config.json';
import { useMemo } from 'react';
function App() {
const options = useMemo(() => {
const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
// Push to your analytics platform
window.analytics.track(eventName, properties);
},
});
return {
localRuntime: { exportedConfig },
domainProviders: [analytics],
};
}, []);
return (
<RevTurbineProvider options={options}>
{/* All impressions and interactions now flow to Segment */}
</RevTurbineProvider>
);
}

The analytics provider receives every SDK event, including:

EventWhen
placement_interactionA placement is shown, clicked, or dismissed
placement_dismissedUser closes a placement
placement_convertedUser completes a CTA
placement_snoozedUser clicks “remind me later”
page_viewPage navigation
Trigger eventstrial_expiring, usage_limit_reached, etc.

Each event includes user_id, anonymous_id, session_id, placement_id, interaction_type, url, page_title, and the full event properties.

Only forward the events you care about:

const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
heap.track(eventName, properties);
},
filter: ['placement_interaction', 'placement_dismissed', 'placement_converted'],
});

Rename events, enrich properties, or drop events with a transform function. Return null to suppress an event.

const analytics = createAnalyticsProvider({
handler: (name, props) => posthog.capture(name, props),
transform: (name, props) => ({
eventName: `revturbine.${name}`,
properties: { ...props, source: 'revturbine-sdk' },
}),
});

Segment:

const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
window.analytics.track(eventName, properties);
},
});

Heap:

const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
heap.track(eventName, properties);
},
filter: ['placement_interaction', 'placement_dismissed', 'placement_converted'],
});

Amplitude:

const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
amplitude.track(eventName, properties);
},
});

PostHog (with prefix):

const analytics = createAnalyticsProvider({
handler: (name, props) => posthog.capture(name, props),
transform: (name, props) => ({
eventName: `revturbine.${name}`,
properties: props,
}),
});