> ## Documentation Index
> Fetch the complete documentation index at: https://docs.permutive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Opinary

> Integrate with Opinary for survey functionality

export const NoBadge = () => {
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.5rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: '#F7D0E2',
    color: '#1A1A1A',
    fontWeight: '500'
  }}>
      No
    </span>;
};

export const YesBadge = () => {
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.5rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: '#C7E8F9',
    color: '#1A1A1A',
    fontWeight: '500'
  }}>
      Yes
    </span>;
};

export const BadgeRowCenter = ({label, children}) => {
  return <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: '0.5rem'
  }}>
      <span style={{
    fontSize: '0.625rem',
    color: '#6b7280',
    textTransform: 'uppercase',
    fontWeight: '500',
    letterSpacing: '0.05em'
  }}>
        {label}
      </span>
      {children}
    </div>;
};

export const BadgeRow = ({label, children}) => {
  return <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'flex-start',
    marginBottom: '0.5rem'
  }}>
      <span style={{
    fontSize: '0.625rem',
    color: '#6b7280',
    textTransform: 'uppercase',
    fontWeight: '500',
    letterSpacing: '0.05em'
  }}>
        {label}
      </span>
      {children}
    </div>;
};

export const BadgeContainer = ({children}) => {
  return <div style={{
    display: 'flex',
    gap: '0.25rem',
    flexWrap: 'wrap',
    justifyContent: 'flex-end',
    minWidth: '0',
    flex: '1'
  }}>
      {children}
    </div>;
};

export const ProductRequiredBadge = ({product}) => {
  const getBadgeStyle = product => {
    switch (product) {
      case 'Core Platform':
        return {
          background: '#CB88FC',
          color: '#1A1A1A'
        };
        --purple;
      case 'Routing':
        return {
          background: '#CB88FC',
          color: '#1A1A1A'
        };
        --purple;
      case 'Contextual':
        return {
          background: '#CB88FC',
          color: '#1A1A1A'
        };
        --purple;
      default:
        return {
          background: '#A7B3D9',
          color: '#1A1A1A'
        };
        --haze;
    }
  };
  const style = getBadgeStyle(product);
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.375rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: style.background,
    color: style.color,
    fontWeight: '500'
  }}>
      {product}
    </span>;
};

export const SdkRequiredBadge = ({required}) => {
  const getBadgeStyle = required => {
    switch (required) {
      case 'Yes':
        return {
          background: '#C7E8F9',
          color: '#1A1A1A'
        };
        --blue;
      case 'No':
        return {
          background: '#F7D0E2',
          color: '#1A1A1A'
        };
        --pink;
      default:
        return {
          background: '#A7B3D9',
          color: '#1A1A1A'
        };
        --haze;
    }
  };
  const style = getBadgeStyle(required);
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.375rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: style.background,
    color: style.color,
    fontWeight: '500'
  }}>
      {required}
    </span>;
};

export const CapabilityBadge = ({capability}) => {
  const getBadgeStyle = capability => {
    switch (capability) {
      case 'Event Collection':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Cohort Activation':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Campaign Optimization':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Identity Signal':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Contextual Signal':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Connectivity':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Routing':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      case 'Data Collaboration':
        return {
          background: '#EFDFC8',
          color: '#1A1A1A'
        };
        --clay;
      default:
        return {
          background: '#A7B3D9',
          color: '#1A1A1A'
        };
        --haze;
    }
  };
  const style = getBadgeStyle(capability);
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.375rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: style.background,
    color: style.color,
    fontWeight: '500',
    whiteSpace: 'nowrap'
  }}>
      {capability}
    </span>;
};

export const EnvironmentBadge = ({environment}) => {
  const getBadgeStyle = environment => {
    switch (environment) {
      case 'Web':
        return {
          background: '#F9C1A8',
          color: '#1A1A1A'
        };
        --peach;
      case 'iOS':
        return {
          background: '#F9C1A8',
          color: '#1A1A1A'
        };
        --peach;
      case 'Android':
        return {
          background: '#F9C1A8',
          color: '#1A1A1A'
        };
        --peach;
      case 'CTV':
        return {
          background: '#F9C1A8',
          color: '#1A1A1A'
        };
        --peach;
      case 'API Direct':
        return {
          background: '#F9C1A8',
          color: '#1A1A1A'
        };
        --peach;
      default:
        return {
          background: '#A7B3D9',
          color: '#1A1A1A'
        };
        --haze;
    }
  };
  const style = getBadgeStyle(environment);
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.375rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: style.background,
    color: style.color,
    fontWeight: '500',
    whiteSpace: 'nowrap'
  }}>
      {environment}
    </span>;
};

export const DirectionBadge = ({direction}) => {
  const getBadgeStyle = direction => {
    switch (direction) {
      case 'Bidirectional':
        return {
          background: '#FA8784',
          color: '#1A1A1A'
        };
        --tomato;
      case 'Destination':
        return {
          background: '#FA8784',
          color: '#1A1A1A'
        };
        --tomato;
      case 'Source':
        return {
          background: '#FA8784',
          color: '#1A1A1A'
        };
        --tomato;
      default:
        return {
          background: '#A7B3D9',
          color: '#1A1A1A'
        };
        --haze;
    }
  };
  const style = getBadgeStyle(direction);
  return <span style={{
    display: 'inline-block',
    padding: '0.125rem 0.375rem',
    borderRadius: '0.25rem',
    fontSize: '0.625rem',
    background: style.background,
    color: style.color,
    fontWeight: '500'
  }}>
      {direction}
    </span>;
};

<Card title="">
  <div style={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}>
    <div style={{ width: '32px', height: '32px', marginRight: '0.75rem', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
      <img src="https://mintcdn.com/permutive/pNhz39ducTVcQczh/images/integrations/logos/opinary.svg?fit=max&auto=format&n=pNhz39ducTVcQczh&q=85&s=43c2e442c35b4ff0bdff5549c1002c36" alt="Opinary" style={{ maxWidth: '32px', maxHeight: '32px', display: 'block' }} width="142" height="142" data-path="images/integrations/logos/opinary.svg" />
    </div>

    <h3 style={{ margin: 0, fontSize: '1.125rem', fontWeight: '600' }}>Opinary</h3>
  </div>

  <div style={{ marginBottom: '1rem' }}>
    <BadgeRowCenter label="Direction">
      <DirectionBadge direction="Source" />
    </BadgeRowCenter>

    <BadgeRowCenter label="Environment">
      <BadgeContainer>
        <EnvironmentBadge environment="Web" />
      </BadgeContainer>
    </BadgeRowCenter>

    <BadgeRowCenter label="Capability">
      <BadgeContainer>
        <CapabilityBadge capability="Event Collection" />
      </BadgeContainer>
    </BadgeRowCenter>

    <BadgeRowCenter label="SDK Required">
      <SdkRequiredBadge required="Yes" />
    </BadgeRowCenter>

    <BadgeRowCenter label="Product(s) Required">
      <ProductRequiredBadge product="Core Platform" />
    </BadgeRowCenter>
  </div>

  <p style={{ margin: 0, fontSize: '0.875rem', color: '#6b7280', lineHeight: '1.5' }}>
    Opinary helps publishers embed interactive opinion tools that boost engagement and enable contextual ad targeting.
  </p>
</Card>

<CardGroup cols={2}>
  <Card title="Setup" href="#setup" icon="gear" />

  <Card title="Troubleshooting" href="#troubleshooting" icon="wrench" />
</CardGroup>

## Overview

Opinary is a software platform that allows publishers to engage their audience by using interactive widgets on their website. The widgets include various types of polls that are designed to gather user opinions and generate insights.

Unlike other survey integrations where the survey platform handles the integration setup, Opinary requires you to deploy custom JavaScript on your website to forward poll responses to Permutive. This declared first-party data is useful for building cohorts for demographics or purchase intent targeting.

Use cases include:

* Build cohorts based on poll responses (e.g., demographic segments, opinion-based targeting)
* Track poll completion and question-level responses for advanced segmentation
* Enrich audience profiles with opinion data collected through Opinary widgets

## Environment Compatibility

| Environment    | Supported    | Notes |
| -------------- | ------------ | ----- |
| **Web**        | <YesBadge /> | --    |
| **iOS**        | <NoBadge />  | --    |
| **Android**    | <NoBadge />  | --    |
| **CTV**        | <NoBadge />  | --    |
| **API Direct** | <NoBadge />  | --    |

## Prerequisites

* **Permutive SDK Deployment**: The Permutive JavaScript SDK must be deployed on your website where Opinary widgets will run.
* **Custom OpinarySurveyResponse Event**: Contact your Permutive Customer Success Manager (CSM) to set up the OpinarySurveyResponse custom event on your workspace. This event must be preconfigured before using this integration.
* **Access to Opinary Platform**: You must have access to the Opinary platform and polls configured on your website.
* **JavaScript Implementation Access**: You need the ability to add custom JavaScript code to your website pages where Opinary polls are displayed.
* **Verify with your CSM**: Confirm that this collector is part of your Permutive plan.

## Setup

<Tabs>
  <Tab title="Primary Setup Steps">
    <Steps>
      <Step title="Request Custom Event Setup">
        Contact your Permutive Customer Success Manager (CSM) to configure the OpinarySurveyResponse custom event on your workspace. This step must be completed before proceeding with the JavaScript implementation.
      </Step>

      <Step title="Deploy JavaScript Collector">
        Add the following JavaScript code to your website where Opinary polls are displayed. This code listens for Opinary vote events and forwards them to Permutive.

        <Warning>
          This code is provided "as is", without warranty of any kind, express or implied. In no event shall Permutive be liable for any claim, damages, updates, or other liability.
        </Warning>

        ```javascript theme={"dark"}
        (function () {
          function listenToOpinary() {
            Opinary.on('opinary.vote', function (vote, poll) {
              if (permutive && poll.dmpIntegration) {
                permutive.track('OpinarySurveyResponse', {
                  survey: {
                    id: poll.pollId,
                    type: poll.type,
                  },
                  question: {
                    text: poll.header
                  },
                  answer: {
                    text: vote.label,
                    posX: vote.x || 0.0,
                    posY: vote.y || 0.0,
                    optionIdentifier: vote.optionID || "",
                    optionPosition: vote.position || 0,
                    rawValue: vote.value || 0.0,
                    unit: vote.unit || ""
                  }
                });
              }
            });
          }

          if (Opinary && Opinary.on) {
            listenToOpinary()
          } else {
            window.addEventListener('OpinaryReady', function () {
              listenToOpinary()
            });
          }
        })();
        ```

        This code should be placed on pages where Opinary polls are displayed, ensuring it loads after both the Permutive SDK and Opinary scripts.
      </Step>

      <Step title="Verify Data Collection">
        Once configured, poll responses from Opinary will be sent to Permutive as OpinarySurveyResponse events.

        To verify:

        1. Navigate to the Permutive dashboard.
        2. Go to **Events** and look for the OpinarySurveyResponse event type.
        3. Check that poll response data is being collected.
        4. Interact with an Opinary poll on your site and verify the event appears in your dashboard.
      </Step>

      <Step title="Build Survey-Based Cohorts">
        Once event collection is verified, you can build cohorts based on poll responses. In the Permutive cohort builder, the `OpinarySurveyResponse` event will appear in the event dropdown list.

        For example, you can create cohorts for:

        * Users who answered a specific poll question in a certain way (e.g., users whose gender is "female")
        * Users who completed specific polls
        * Demographic segments based on poll responses
        * Opinion-based segments for targeted content or advertising
      </Step>
    </Steps>
  </Tab>

  <Tab title="Web">
    The integration code provided in the Primary Setup Steps is specifically for Web environments. No additional web-specific configuration is required beyond deploying the code snippet and ensuring the Permutive SDK is present on the page.

    The code listens for Opinary vote events and forwards them to Permutive via the `window.permutive.track()` method.
  </Tab>
</Tabs>

## Data Types

With your Opinary integration setup, you'll see the following event type collected in Permutive:

<AccordionGroup>
  <Accordion title="OpinarySurveyResponse">
    The OpinarySurveyResponse event is triggered when a user votes on an Opinary poll. The event captures details about the survey, question, and answer provided.

    <ResponseField name="survey.id" type="string">
      The unique identifier for the Opinary poll. Example: `12345`
    </ResponseField>

    <ResponseField name="survey.type" type="string">
      The type of Opinary poll (e.g., single choice, multiple choice, rating scale).
    </ResponseField>

    <ResponseField name="question.text" type="string">
      The text of the poll question presented to the user. Example: `What is your preferred news category?`
    </ResponseField>

    <ResponseField name="answer.text" type="string">
      The user's text response or label for their answer. Example: `Politics`
    </ResponseField>

    <ResponseField name="answer.posX" type="number">
      The X position of the answer (used for spatial poll types like image grids). Defaults to `0.0` if not applicable.
    </ResponseField>

    <ResponseField name="answer.posY" type="number">
      The Y position of the answer (used for spatial poll types). Defaults to `0.0` if not applicable.
    </ResponseField>

    <ResponseField name="answer.optionIdentifier" type="string">
      The unique identifier for the selected answer option. Empty string if not applicable.
    </ResponseField>

    <ResponseField name="answer.optionPosition" type="number">
      The position/index of the selected option within the poll choices. Defaults to `0` if not applicable.
    </ResponseField>

    <ResponseField name="answer.rawValue" type="number">
      The raw numeric value of the answer (used for rating scales or numeric inputs). Defaults to `0.0` if not applicable.
    </ResponseField>

    <ResponseField name="answer.unit" type="string">
      The unit associated with the answer (e.g., "years", "dollars"). Empty string if not applicable.
    </ResponseField>

    <Note>
      OpinarySurveyResponse events also contain default Permutive event properties such as page URL (`client.url`) and page title (`client.title`).
    </Note>
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="OpinarySurveyResponse events are not appearing in Permutive">
    Verify the following:

    * The custom `OpinarySurveyResponse` event has been configured in your Permutive workspace. Check the Events page in your dashboard to confirm the event exists.
    * The Permutive SDK is deployed and loading correctly on the pages where your Opinary polls are displayed. Use the Permutive Chrome Extension or check for `window.permutive` in the browser console.
    * The Opinary SDK is loaded before the collector JavaScript runs. Check for `window.Opinary` in the browser console.
    * The collector JavaScript has been added to the page and is executing without errors. Check the browser console for JavaScript errors.
    * The `poll.dmpIntegration` flag is set to `true` in your Opinary poll configuration. This flag controls whether poll responses are sent to DMPs.
  </Accordion>

  <Accordion title="JavaScript errors when voting on polls">
    If you see JavaScript errors in the console:

    * Verify that both `permutive` and `Opinary` objects are available before the collector code runs.
    * Check that the Opinary SDK version you're using supports the `opinary.vote` event and the `OpinaryReady` event.
    * Ensure there are no conflicts with other JavaScript libraries on the page.
    * Test the integration on a clean page with minimal JavaScript to isolate conflicts.
  </Accordion>

  <Accordion title="Custom event not configured in workspace">
    The `OpinarySurveyResponse` event is a custom event that must be preconfigured before the integration will work. If you attempt to deploy the JavaScript collector without this custom event being set up first, poll data will not be collected.

    Contact your Permutive Customer Success Manager (CSM) to request the custom event setup.
  </Accordion>

  <Accordion title="Unable to build cohorts using poll data">
    If the `OpinarySurveyResponse` event is not appearing in the cohort builder dropdown:

    * Confirm that events are being collected by checking the Events page in your dashboard.
    * Ensure at least one event has been received. Events only appear in the cohort builder after data has been collected.
    * Wait a few minutes after the first event is received, then refresh the cohort builder page.
    * Verify that the event schema includes the fields you want to use for cohort building.

    If events are being collected but still not appearing, contact your Permutive CSM for assistance.
  </Accordion>

  <Accordion title="Some answer fields are empty or have default values">
    Not all Opinary poll types populate all fields in the OpinarySurveyResponse event:

    * Simple choice polls may only populate `answer.text` and `answer.optionPosition`.
    * Rating scale polls will populate `answer.rawValue` and may include `answer.unit`.
    * Spatial polls (like image grids) will populate `answer.posX` and `answer.posY`.

    This is expected behavior. The collector code uses default values (0.0 for numbers, empty strings for text) for fields that don't apply to the specific poll type.
  </Accordion>

  <Accordion title="Opinary polls not triggering the vote event">
    If polls display but votes are not being tracked:

    * Verify that the `poll.dmpIntegration` setting is enabled in your Opinary poll configuration.
    * Check that you're using a compatible Opinary SDK version that supports the `opinary.vote` event.
    * Test with different poll types to determine if the issue is specific to certain poll formats.
    * Contact Opinary support to verify that your polls are configured correctly for DMP integration.
  </Accordion>
</AccordionGroup>

## Changelog

<Info>
  For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com).
</Info>
