
Step-By-Step Tutorial: Building an Optimizely Connect Platform (OCP) App
Optimizely Connect Platform went generally available in July 2025, and it has quickly become the standard way to integrate external systems with Optimizely products. Whether you need to sync CRM data, push analytics into Opal, or build custom workflows between your commerce stack and Optimizely, OCP provides the serverless infrastructure to make it happen. This tutorial walks you through building your first OCP app from scratch. By the end, you'll have a working app that receives webhook data and sends notifications to Slack. A practical starting point you can adapt for more complex integrations.
Prerequisites
Before starting, make sure you have the following in place:
Development Environment
- Node.js 18 or later installed
- A code editor with TypeScript support (VS Code works well)
- Git for version control
- Terminal access for CLI commands
Optimizely Access
- An OCP developer account (request access by emailing the OCP developer team listed in Optimizely's documentation)
- The OCP CLI installed and configured with your credentials
- Access to at least one OCP account where you can install apps
External Services
- A Slack workspace where you can create incoming webhooks
- A Slack webhook URL for testing (create one at api.slack.com under Incoming Webhooks)
Technical Knowledge
- Basic TypeScript familiarity
- Understanding of REST APIs and webhooks
- Experience with npm package management
If you're missing the OCP developer account, that's the longest lead time item. Optimizely typically processes requests within a few business days.
Step 1: Install and Configure the OCP CLI
The OCP CLI handles everything from scaffolding your app to publishing it in the App Directory. Start by installing it globally:
npm install -g @optimizely/ocp-cli
After installation, authenticate with your developer credentials:
ocp auth login
This opens a browser window for Opti ID authentication. Once authenticated, verify your setup:
ocp auth status
You should see your developer account information and associated permissions.
Step 2: Register Your App
Every OCP app needs a unique identifier registered with Optimizely before you can build it. Run the registration command:
ocp app register
The CLI prompts you for several pieces of information:
- App ID: Use snake_case, like slack_notifier. This must be globally unique.
- Display Name: Human-readable name, like "Slack Notifier"
- Target Product: Select "Connect Platform"
- Visibility: Choose "private" for development (you can change this later)
Registration reserves your app ID and creates the backend records needed for development.
Step 3: Scaffold the App Structure
With your app registered, generate the project structure:
ocp app init
The CLI asks for additional details:
- Name: Pre-filled from registration
- App ID: Pre-filled from registration
- Version: Start with 1.0.0-dev.1 for development
- Summary: Brief description like "Sends Slack notifications for OCP events"
- Support URL: Your documentation or support page
- Support Email: Contact email for users
- Category: Choose something appropriate (avoid reserved categories like Commerce Platform or Content Management)
- Template: Select "Empty Project" for this tutorial
The scaffolding creates this structure:
slack-notifier/ ├── app.yml ├── package.json ├── tsconfig.json ├── src/ │ ├── functions/ │ ├── jobs/ │ └── schema/ └── node_modules/
Navigate into your new project and install dependencies:
cd slack-notifier npm install
Step 4: Define the Settings Form
Users need a way to configure your app after installation. OCP handles this through a settings form defined in your app.yml file. Open it and add a settings section:
id: slack_notifier
name: Slack Notifier
version: 1.0.0-dev.1
summary: Sends Slack notifications for OCP events
settings:
- id: slack_webhook_url
type: string
label: Slack Webhook URL
description: The incoming webhook URL from your Slack workspace
required: true
sensitive: true
- id: notification_channel
type: string
label: Channel Override
description: Optional channel name to override the webhook default
required: false
- id: include_timestamp
type: boolean
label: Include Timestamp
description: Add timestamp to notification messages
default: trueThe sensitive: true flag tells OCP to mask this field in logs and the UI. Always use this for API keys, tokens, and webhook URLs.
Our experience shows that providing sensible defaults and clear descriptions in settings reduces support requests significantly. Users appreciate knowing exactly what each field does without needing to reference external documentation.
Step 5: Create the Notification Function
Functions are the core of OCP apps; they handle incoming requests and execute your business logic. Create a new file at src/functions/notify.ts:
import * as App from '@zaiusinc/app-sdk';
interface NotificationPayload {
title: string;
message: string;
severity?: 'info' | 'warning' | 'error';
metadata?: Record<string, string>;
}
export class Notify extends App.Function {
public async perform(): Promise<App.Response> {
// Get configuration from app settings
const webhookUrl = this.app.settings.slack_webhook_url;
const channelOverride = this.app.settings.notification_channel;
const includeTimestamp = this.app.settings.include_timestamp;
if (!webhookUrl) {
this.logger.error('Slack webhook URL not configured');
return new App.Response(500, {
error: 'Slack webhook URL not configured'
});
}
// Parse incoming request
let payload: NotificationPayload;
try {
payload = this.request.body as NotificationPayload;
} catch (err) {
this.logger.error('Invalid request payload', { error: err });
return new App.Response(400, {
error: 'Invalid request payload'
});
}
// Build Slack message
const slackMessage = this.buildSlackMessage(
payload,
includeTimestamp,
channelOverride
);
// Send to Slack
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(slackMessage)
});
if (!response.ok) {
throw new Error(`Slack returned ${response.status}`);
}
this.logger.info('Notification sent successfully', {
title: payload.title
});
return new App.Response(200, {
success: true,
message: 'Notification sent'
});
} catch (err) {
this.logger.error('Failed to send Slack notification', { error: err });
return new App.Response(500, {
error: 'Failed to send notification'
});
}
}
private buildSlackMessage(
payload: NotificationPayload,
includeTimestamp: boolean,
channelOverride?: string
) {
const colorMap = {
info: '#36a64f',
warning: '#ff9800',
error: '#dc3545'
};
const attachment: any = {
color: colorMap[payload.severity || 'info'],
title: payload.title,
text: payload.message,
fields: []
};
// Add metadata as fields
if (payload.metadata) {
for (const [key, value] of Object.entries(payload.metadata)) {
attachment.fields.push({
title: key,
value: value,
short: true
});
}
}
// Add timestamp if configured
if (includeTimestamp) {
attachment.ts = Math.floor(Date.now() / 1000);
}
const message: any = {
attachments: [attachment]
};
if (channelOverride) {
message.channel = channelOverride;
}
return message;
}
}Now register this function in your app.yml:
functions:
- id: notify
entry_point: src/functions/notify.ts
description: Receives notification requests and sends them to SlackStep 6: Add a Scheduled Health Check Job
Jobs run on schedules rather than responding to HTTP requests. They're useful for periodic syncs, cleanup tasks, or health checks. Create src/jobs/health-check.ts:
import * as App from '@zaiusinc/app-sdk';
export class HealthCheck extends App.Job {
public async perform(): Promise<void> {
const webhookUrl = this.app.settings.slack_webhook_url;
if (!webhookUrl) {
this.logger.warn('Health check skipped: webhook not configured');
return;
}
try {
// Send a simple test message
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: '✅ OCP Slack Notifier health check: Connection verified'
})
});
if (response.ok) {
this.logger.info('Health check passed');
} else {
this.logger.error('Health check failed', {
status: response.status
});
}
} catch (err) {
this.logger.error('Health check error', { error: err });
}
}
}Register the job in app.yml:
jobs:
- id: health_check
entry_point: src/jobs/health-check.ts
description: Verifies Slack webhook connectivity
schedule: 0 9 * * 1 # Every Monday at 9 AMStep 7: Test Locally with the OCP Dev Tool
The local testing tool, released in December 2025, lets you test your app without deploying. Start it with:
ocp dev
This launches a local web interface where you can:
Test Settings Form
Navigate to the Settings tab to see exactly how your form renders. Enter your Slack webhook URL and save. The tool validates required fields and handles sensitive data masking.
Test Functions
Go to the Functions tab, select your notify function, and send a test payload:
{
"title": "Test Notification",
"message": "This is a test from the OCP local dev tool",
"severity": "info",
"metadata": {
"Source": "Local Testing",
"Environment": "Development"
}
}Click Execute and check both the response in the tool and your Slack channel for the message.
Test Jobs
Switch to the Jobs tab and manually trigger the health check. Review the execution logs to verify it completed successfully.
Review Logs
The Logs panel shows detailed output from all function and job executions, including your custom log messages. This is invaluable for debugging.
Step 8: Validate and Prepare for Publishing
Once local testing passes, validate your app configuration:
ocp app validate
This checks your app.yml, TypeScript compilation, and schema definitions. Fix any errors before proceeding.
Next, prepare the distributable package:
ocp app prepare
This bundles your code, settings definitions, and metadata into a format ready for the App Directory.
Step 9: Publish to the App Directory
Publish your app with:
ocp directory publish [email protected]
This uploads your app to the App Directory. Since you registered it as private, only your account can see it initially.
Step 10: Install and Configure in an OCP Account
To install your app in a specific OCP account, you need the account's tracker ID. Find this in the OCP UI under account settings, then run:
ocp directory install [email protected] YOUR_TRACKER_ID
After installation:
- Open the OCP UI and navigate to the App Directory
- Find your Slack Notifier app
- Click Configure and enter your production Slack webhook URL
- Save the settings
Your function is now live and accessible at a URL like:
https://connect.optimizely.com/slack_notifier/notify/[installation-uuid]
Use this URL as a webhook endpoint in other systems to trigger Slack notifications.
Common Mistakes to Avoid
Forgetting to Mark Sensitive Fields
Always use sensitive: true for credentials. We've found that apps failing security review often miss this simple flag on one or two settings fields. It's easy to overlook but critical for protecting user data.
Hardcoding Configuration
Never put API keys or environment-specific values directly in code. Everything configurable should go through the settings form. This seems obvious but becomes tempting when debugging.
Ignoring Error Responses
Your functions should always return appropriate HTTP status codes. A function that returns 200 for every request makes debugging impossible for users and external systems.
Skipping Local Testing
The ocp dev tool exists for a reason. Deploying untested code and debugging through the App Directory takes significantly longer than local iteration.
Using Reserved Categories
When registering your app, avoid categories like "Commerce Platform," "Content Management," or "Loyalty & Rewards." These are reserved for specific Optimizely product integrations.
Not Handling Missing Settings
Users might install your app before configuring it. Your functions should gracefully handle missing required settings rather than throwing cryptic errors.
Verifying Your Deployment
After installation, verify everything works:
Test the Function Endpoint
Use curl or Postman to send a test request:
curl -X POST https://connect.optimizely.com/slack_notifier/notify/[your-uuid] \
-H "Content-Type: application/json" \
-d '{"title": "Production Test", "message": "Hello from production!", "severity": "info"}'Check Slack
Confirm the message appears in your configured channel with proper formatting.
Review Logs in OCP
Navigate to your app in the OCP UI and check the execution logs for successful completion.
Trigger the Scheduled Job
Manually run the health check job from the OCP UI to verify scheduled tasks work correctly.
Moving to Production
When you're ready for broader distribution:
Version Increment
Update your version in app.yml to a production number like 1.0.0, then run validate, prepare, and publish again.
Public Listing
Change your app visibility to public and submit for review. Optimizely typically completes reviews in 1-2 business days.
Documentation
Create user documentation covering installation, configuration, and troubleshooting. Link this from your support URL in app.yml.
Conclusion
You now have a functioning OCP app that demonstrates the core patterns: settings configuration, function implementation, scheduled jobs, and the full development-to-deployment workflow. From here, you can expand into more complex territory, adding schema definitions for data storage, building multi-step sync workflows, or integrating with additional Optimizely products.
Teams we work with report that the initial learning curve with OCP comes from understanding the lifecycle and CLI commands rather than the actual coding. Once you've built one app, subsequent ones follow the same patterns.
Building custom OCP integrations requires balancing Optimizely's platform conventions with your specific business requirements. If you're planning an OCP app for production use, whether it's a data sync connector, an Opal tool integration, or something more specialized, we can help you design the architecture and avoid common pitfalls that slow down development.
