SentientUI

Integrations

Forward SentientUI events to your analytics stack via onAssignment. Verify and react to webhook events with signed payloads.

Segment

Segment routes SentientUI events to Mixpanel, Amplitude, Braze, HubSpot, and 20+ other tools automatically. Pass onAssignment to <AdaptiveProvider> and call analytics.track — Segment takes care of the fan-out.

<AdaptiveProvider
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY}
  context="saas"
  onAssignment={(componentId, variantId) => {
    analytics.track('Variant Assigned', {
      componentId,
      variantId,
    });
  }}
>
  {children}
</AdaptiveProvider>

Forwarding goal events

Fire sentientClient.goal() alongside your own Segment event so that both systems record the conversion at the same time.

// after your trial_started logic:
sentientClient.goal('trial_started', { plan: 'pro' });
analytics.track('Trial Started', { plan: 'pro' });
onAssignment fires at most once per component ID per page load. The Segment event will appear in your downstream destinations within their normal ingestion delay.

Google Analytics 4

Use onAssignment with gtag to push variant assignments as custom events in GA4. You can then use these as dimensions in Explorations or audiences.

<AdaptiveProvider
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY}
  context="saas"
  onAssignment={(componentId, variantId) => {
    gtag('event', 'variant_assigned', {
      component_id: componentId,
      variant_id: variantId,
    });
  }}
>
  {children}
</AdaptiveProvider>
This assumes GA4 (gtag) is already loaded on the page via Google Tag Manager or a <Script> tag. Register component_id and variant_id as custom dimensions in GA4 → Admin → Custom definitions before they appear in reports.

Mixpanel

Use onAssignment with mixpanel.register to set a super-property. Every subsequent event from this session will automatically carry the variant assignment — no need to attach it manually to each mixpanel.track() call.

<AdaptiveProvider
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY}
  context="saas"
  onAssignment={(componentId, variantId) => {
    mixpanel.register({
      [`variant_${componentId}`]: variantId,
    });
  }}
>
  {children}
</AdaptiveProvider>
mixpanel.register persists super-properties to localStorage by default, so they survive page reloads within the same browser. Use mixpanel.register_once if you only want the first-seen value to stick for returning visitors.

Webhooks — verification

The SentientUI API fires HMAC-SHA256-signed webhooks for variant.promoted, variant.unpromoted, and notification.fired events. Each request carries an X-SentientUI-Signature header of the form sha256=<hex digest> — the HMAC-SHA256 of the raw request body signed with your webhook secret — plus X-SentientUI-Event (the event name) and X-SentientUI-Timestamp (Unix milliseconds) headers.

Always verify the signature before processing the payload. The following recipe works in a Next.js App Router API route and any Node.js or Edge runtime that supports node:crypto.

import { createHmac, timingSafeEqual } from 'node:crypto';

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get('x-sentientui-signature') ?? '';
  const expected = 'sha256=' + createHmac('sha256', process.env.WEBHOOK_SECRET!)
    .update(raw)
    .digest('hex');
  if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response('Forbidden', { status: 403 });
  }
  const event = JSON.parse(raw);
  // handle event ...
  return new Response(null, { status: 204 });
}
Always use timingSafeEqual for signature comparison — never a plain string equality check. String equality short-circuits on the first mismatched character, which leaks timing information that can be exploited to forge signatures. Header lookup via req.headers.get() is case-insensitive, so the lowercase name works everywhere.

Webhook event shape

All events share one flat envelope — event at the top level, snake_case fields, no nesting:

{
  "event": "variant.promoted",
  "timestamp": "2026-07-16T12:00:00.000Z",
  "project_id": "proj_xxxxxxxxxx",
  "project_name": "My project",
  "component_id": "hero_cta",
  "variant_id": "variant_a"
}
For notification.fired events the same envelope is reused: component_id carries the notification type and variant_id carries the notification title.

Slack via webhook

The simplest path: register your Slack Incoming Webhook URL directly in the SentientUI dashboard with type Slack — SentientUI posts a formatted message to your channel with no code on your side. If you want custom formatting or routing instead, register a generic webhook pointing at your own endpoint, verify the signature, and forward:

import { createHmac, timingSafeEqual } from 'node:crypto';

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get('x-sentientui-signature') ?? '';
  const expected = 'sha256=' + createHmac('sha256', process.env.WEBHOOK_SECRET!)
    .update(raw)
    .digest('hex');
  if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response('Forbidden', { status: 403 });
  }

  const event = JSON.parse(raw);
  if (event.event === 'variant.promoted') {
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `*${event.component_id}* — variant \`${event.variant_id}\` promoted to winner`,
      }),
    });
  }

  return new Response(null, { status: 204 });
}
Set SLACK_WEBHOOK_URL and WEBHOOK_SECRET as environment variables in your deployment. Generate a Slack Incoming Webhook URL from your Slack app configuration under Incoming Webhooks. The webhook secret is shown once in the SentientUI dashboard when you register the endpoint — store it in a secret manager, not in source control.