Optimizely Connector
Connect Optimizely Feature Experimentation to Brevo through Tajo to sync experiment results, target campaigns by feature flag segments, and enrich marketing automations with A/B test data and audience insights.
Overview
| Property | Value |
|---|---|
| Platform | Optimizely |
| Category | Experimentation (Custom) |
| Setup Complexity | Medium |
| Official Integration | No |
| Data Synced | Experiments, Audiences, Events, Feature Flags |
| Auth Method | Personal Access Token / OAuth 2.0 |
Features
- Experiment sync - Push A/B test variation assignments to Brevo contact attributes
- Audience targeting - Use Optimizely audiences for Brevo campaign segmentation
- Conversion tracking - Track Optimizely events and map to Brevo event tracking
- Feature flag sync - Segment contacts by enabled feature flags
- Results reporting - Sync experiment results for post-analysis marketing campaigns
- Multi-project support - Connect multiple Optimizely projects to a single Tajo instance
Prerequisites
Before you begin, ensure you have:
- An Optimizely Feature Experimentation account
- A Personal Access Token from Optimizely App Settings
- An SDK key for your Optimizely environment
- A Brevo account with API access
- A Tajo account with connector permissions
Authentication
Personal Access Token
# Generate at https://app.optimizely.com/v2/accountsettings/tokensexport OPTIMIZELY_ACCESS_TOKEN=your_personal_access_tokenexport OPTIMIZELY_SDK_KEY=your_sdk_keyexport TAJO_API_KEY=your_tajo_api_keyexport BREVO_API_KEY=your_brevo_api_key// All REST API requests use Bearer token authconst headers = { 'Authorization': `Bearer ${process.env.OPTIMIZELY_ACCESS_TOKEN}`, 'Content-Type': 'application/json'};SDK Authentication
// For feature flag evaluation, use the SDKconst optimizelySDK = require('@optimizely/optimizely-sdk');
const optimizelyClient = optimizelySDK.createInstance({ sdkKey: process.env.OPTIMIZELY_SDK_KEY, datafileOptions: { autoUpdate: true, updateInterval: 60000 // 1 minute }});
await optimizelyClient.onReady();Configuration
Basic Setup
connectors: optimizely: enabled: true access_token: "${OPTIMIZELY_ACCESS_TOKEN}" sdk_key: "${OPTIMIZELY_SDK_KEY}" project_id: "12345678"
sync: experiments: true audiences: true events: true feature_flags: true schedule: "0 */2 * * *" # Every 2 hours
mapping: experiment_variation: EXPERIMENT_VARIATION feature_flags: ENABLED_FEATURES audience_segments: OPT_SEGMENTSField Mapping
field_mapping: user_id: email experiment_key: EXPERIMENT_NAME variation_key: VARIATION_NAME feature_key: FEATURE_FLAG enabled: FEATURE_ENABLED audience_name: AUDIENCE_SEGMENT decision_timestamp: EXPERIMENT_DATEAPI Endpoints
| Endpoint | Method | Description |
|---|---|---|
https://api.optimizely.com/v2/projects | GET | List projects |
https://api.optimizely.com/v2/experiments | GET | List experiments |
https://api.optimizely.com/v2/experiments/{id} | GET | Get experiment details |
https://api.optimizely.com/v2/experiments/{id}/results | GET | Get experiment results |
https://api.optimizely.com/v2/features | GET | List feature flags |
https://api.optimizely.com/v2/features/{id} | GET | Get feature flag |
https://api.optimizely.com/v2/audiences | GET | List audiences |
https://api.optimizely.com/v2/events | GET | List tracked events |
https://logx.optimizely.com/v1/events | POST | Track events (SDK endpoint) |
Code Examples
Initialize Connector
import { TajoClient } from '@tajo/sdk';
const tajo = new TajoClient({ apiKey: process.env.TAJO_API_KEY, brevoApiKey: process.env.BREVO_API_KEY});
await tajo.connectors.connect('optimizely', { accessToken: process.env.OPTIMIZELY_ACCESS_TOKEN, sdkKey: process.env.OPTIMIZELY_SDK_KEY, projectId: '12345678'});Sync Experiment Decisions to Brevo
// Track experiment decisions and sync to Brevoconst optimizelyClient = optimizelySDK.createInstance({ sdkKey: process.env.OPTIMIZELY_SDK_KEY});
await optimizelyClient.onReady();
// Register a decision notification listeneroptimizelyClient.notificationCenter.addNotificationListener( optimizelySDK.enums.NOTIFICATION_TYPES.DECISION, async (decisionObject) => { const { type, userId, attributes, decisionInfo } = decisionObject;
if (type === 'feature' || type === 'ab-test') { const email = attributes.email; if (email) { await tajo.contacts.update(email, { attributes: { EXPERIMENT_NAME: decisionInfo.experimentKey || decisionInfo.featureKey, VARIATION_NAME: decisionInfo.variationKey, FEATURE_ENABLED: decisionInfo.featureEnabled || false, EXPERIMENT_DATE: new Date().toISOString() } }); } } });Sync Experiment Results
// Fetch experiment results and sync winning segmentsconst resultsResponse = await fetch( `https://api.optimizely.com/v2/experiments/${experimentId}/results`, { headers: { 'Authorization': `Bearer ${process.env.OPTIMIZELY_ACCESS_TOKEN}` } });
const results = await resultsResponse.json();
// Process variations and update contact segmentsfor (const variation of results.metrics) { const isWinner = variation.is_improvement && variation.statistical_significance >= 0.95;
if (isWinner) { // Create a Brevo segment for users in the winning variation console.log(`Winning variation: ${variation.variation_name}`); }}Feature Flag-Based Segmentation
// Evaluate feature flags for user segmentationasync function syncFeatureFlags(userEmail, userId) { const features = ['new_checkout', 'loyalty_program', 'ai_recommendations']; const enabledFeatures = [];
for (const feature of features) { const user = optimizelyClient.createUserContext(userId, { email: userEmail }); const decision = user.decide(feature);
if (decision.enabled) { enabledFeatures.push(feature); } }
await tajo.contacts.update(userEmail, { attributes: { ENABLED_FEATURES: enabledFeatures.join(', '), FEATURE_FLAGS_SYNCED: new Date().toISOString() } });}Rate Limits
| Endpoint | Limit | Notes |
|---|---|---|
| REST API | 50 req/min | Per personal access token |
| Results API | 10 req/min | Higher latency, heavier queries |
| SDK Event Dispatch | 10,000 events/batch | Via SDK event processor |
| Datafile CDN | Unlimited | Cached with auto-updates |
Results API Latency
The Experiment Results API processes large datasets and can take 30+ seconds to respond. Use async polling or caching to avoid blocking your application.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Token expired/invalid | Regenerate Personal Access Token |
| SDK not ready | Datafile not loaded | Wait for onReady() promise to resolve |
| No decisions logged | Notification not registered | Register listener before making decisions |
| Stale feature flags | Datafile cache | Set updateInterval for auto-refresh |
| Missing results | Experiment not started | Verify experiment status is “running” |
Debug Mode
connectors: optimizely: debug: true log_level: verbose log_decisions: true log_events: trueBest Practices
- Use the SDK for decisions - Use the SDK for real-time flag evaluation, REST API for management
- Implement event batching - Batch SDK events to reduce network overhead
- Cache the datafile - Enable auto-update with appropriate intervals
- Sync winning variations - After experiments conclude, update contact segments
- Use attributes for targeting - Pass email and user attributes for audience matching
- Monitor experiment status - Only sync data from running or concluded experiments
Security
- Personal Access Tokens - Bearer token authentication for REST API
- SDK key isolation - Separate SDK keys per environment (dev, staging, prod)
- Server-side evaluation - Evaluate feature flags server-side to prevent exposure
- Token rotation - Rotate Personal Access Tokens periodically
- Minimal permissions - Use read-only tokens when write access is not needed
- Encrypted transport - TLS 1.2+ for all API and SDK communications
Related Resources
- PostHog Connector
- Segment Connector
- Customer Sync Skill
- Optimizely Feature Experimentation Documentation
Open-Source Implementation Map
This section is derived from official or public repository material discovered for the Optimizely connector. Use it as the engineering companion to the setup guide above: it shows where the API surface lives, what implementation assets exist, and how Tajo should translate them into reliable Brevo sync behavior.
Repository Snapshot
| Repository | Commit | Languages / formats | Files |
|---|---|---|---|
| optimizely/agent | 7841ece | Go (124), Python (30), Markdown (12), YAML (8), sh (8), YAML (5) | 206 |
Integration Shape
graph LR Source["Optimizely API / repository"] --> Auth["Auth and scopes"] Source --> Objects["Objects, events, and schemas"] Auth --> Tajo["Tajo connector runtime"] Objects --> Tajo Tajo --> Brevo["Brevo contacts, attributes, lists, campaigns"] Tajo --> Ops["Backfill, cursor, retries, logs"]What To Reuse
- Optimizely Agent
- This repository houses the Optimizely Agent service for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy).
- Get Started
- Refer to the Agent’s developer documentation for detailed instructions on getting started with using the SDK.
- Requirements
Tajo Revamp Checklist
- Keep authentication setup aligned with the vendor docs and the public repository’s current API shape.
- Map primary resources into explicit Tajo sync objects with stable external IDs.
- Prefer cursor-based or updated-at incremental sync where the API exposes it; otherwise document the fallback.
- Treat webhook handlers as idempotent and replay-safe, especially for order, contact, ticket, and campaign events.
- Capture pagination, rate limits, retry headers, and partial-failure behavior in connector smoke tests.
- Keep examples small and runnable against sandbox or test-mode accounts.