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>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 and variant.unpromoted events. Each request includes an x-sentient-signature header containing the hex digest of the raw body signed with your webhook secret.
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-sentient-signature') ?? '';
const expected = 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 });
}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.Webhook event shape
Both variant.promoted and variant.unpromoted share the same envelope:
{
"type": "variant.promoted",
"payload": {
"componentId": "hero_cta",
"variantId": "variant_a",
"projectId": "proj_xxxxxxxxxx"
}
}Slack via webhook
Once the signature is verified, forward the event to a Slack Incoming Webhook URL to get notified in a channel whenever a variant is promoted to winner.
import { createHmac, timingSafeEqual } from 'node:crypto';
export async function POST(req: Request) {
const raw = await req.text();
const sig = req.headers.get('x-sentient-signature') ?? '';
const expected = 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.type === 'variant.promoted') {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `*${event.payload.componentId}* — variant \`${event.payload.variantId}\` promoted to winner`,
}),
});
}
return new Response(null, { status: 204 });
}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.
