Step-By-Step Tutorial: Building an Optimizely Connect Platform (OCP) App

Step-By-Step Tutorial: Building an Optimizely Connect Platform (OCP) App

Alex Rollin
Alex Rollin
January 10, 2026
Last updated : February 15, 2026
January 10, 2026

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: true

The 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 Slack

Step 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 AM

Step 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.

Share this article