Hooks
Hooks allow you to listen to Screeb lifecycle events. You can define hooks globally via ScreebProvider or per-survey/per-message via surveyStart() and messageStart().
Available hooksโ
| Hook Name | ScreebProvider / init | surveyStart | messageStart |
|---|---|---|---|
onReady | โ | โ | โ |
onSurveyDisplayAllowed | โ | โ | โ |
onMessageDisplayAllowed | โ | โ | โ |
onRecordingStarted | โ | โ | โ |
onRecordingStopped | โ | โ | โ |
onSurveyShowed | โ | โ | โ |
onSurveyStarted | โ | โ | โ |
onQuestionReplied | โ | โ | โ |
onButtonNavigateStarted | โ | โ | โ |
onButtonNavigateCompleted | โ | โ | โ |
onSurveyCompleted | โ | โ | โ |
onSurveyHidden | โ | โ | โ |
onMessageShowed | โ | โ | โ |
onMessageStarted | โ | โ | โ |
onMessageCompleted | โ | โ | โ |
onMessageHidden | โ | โ | โ |
Payload structureโ
React reuses the browser hook payload types. Hook payloads are nested:
type Channel = {
id: string;
type: "widget" | "ios" | "android";
};
type User = {
anonymous_id: string;
userId?: string;
};
type Survey = {
id: string;
survey_position:
| "center-left"
| "center-center"
| "center-right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
survey_format: "conversationnal" | "cards";
survey_size: 100;
};
type Message = {
id: string;
messagey_size: 100;
};
type ResponseStatus =
| "displayed"
| "started"
| "ended"
| "closed"
| "interrupted";
type ResponseItemQuestion = {
id: string;
title: string;
type: "text" | "video";
url?: string;
};
type ResponseItemAnswer = {
replied_at?: string;
fields?: {
type: "string" | "number" | "boolean" | "none" | "time" | "file";
text?: string;
number?: number;
boolean?: boolean;
time?: string;
button_url?: string;
button_url_target?: "in-page" | "in-page-spa" | "new-tab";
button_actions?: object[];
}[];
};
type ResponseItem = {
question: ResponseItemQuestion;
answer: ResponseItemAnswer;
};
Decision hooks (onSurveyDisplayAllowed, onMessageDisplayAllowed) must answer within 5 seconds. Past that deadline the display evaluation fails and the survey is not shown โ keep these callbacks fast and avoid slow network calls.
Exact callback payloads:
onReady(payload: { channel: Channel; user: User }): void;
onRecordingStarted(payload: { channel: Channel; user: User }): void;
onRecordingStopped(payload: { channel: Channel; user: User }): void;
onSurveyDisplayAllowed(payload: {
channel: Channel;
user: User;
survey: Survey;
}): boolean;
onMessageDisplayAllowed(payload: {
channel: Channel;
user: User;
survey: Survey;
}): boolean;
onSurveyShowed(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
items: ResponseItem[];
};
}): void;
onSurveyStarted(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
};
}): void;
onSurveyCompleted(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
items: ResponseItem[];
};
}): void;
onSurveyHidden(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
items: ResponseItem[];
hide_reason: ResponseStatus;
};
}): void;
onQuestionReplied(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
status: null;
question: ResponseItemQuestion;
answer: ResponseItemAnswer;
items: ResponseItem[];
};
}): void;
onButtonNavigateStarted(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
};
button: {
url: string;
url_target: "in-page" | "in-page-spa" | "new-tab";
};
}): void;
onButtonNavigateCompleted(payload: {
channel: Channel;
user: User;
survey: Survey;
response: {
id: string;
};
button: {
url: string;
url_target: "in-page" | "in-page-spa" | "new-tab";
};
}): void;
onMessageShowed(payload: {
channel: Channel;
user: User;
message: Message;
response: {
id: string;
items: ResponseItem[];
};
}): void;
onMessageStarted(payload: {
channel: Channel;
user: User;
message: Message;
response: {
id: string;
};
}): void;
onMessageCompleted(payload: {
channel: Channel;
user: User;
message: Message;
response: {
id: string;
items: ResponseItem[];
};
}): void;
onMessageHidden(payload: {
channel: Channel;
user: User;
message: Message;
response: {
id: string;
items: ResponseItem[];
hide_reason: ResponseStatus;
};
}): void;
Global hooks via ScreebProviderโ
Define hooks that apply to all surveys and messages in your application:
import { ScreebProvider } from "@screeb/sdk-react";
export const App = () => (
<ScreebProvider
autoInit
websiteId="<YOUR-CHANNEL-ID>"
userId="<USER-ID>"
hooks={{
version: "1.0.0",
onReady: (payload) => {
console.log("Screeb is ready", payload);
},
onSurveyShowed: (payload) => {
console.log("Survey showed", payload);
},
onSurveyCompleted: (payload) => {
console.log("Survey completed", payload);
},
onQuestionReplied: (payload) => {
console.log("Question answered", payload.response.question, payload.response.answer);
},
}}
>
<YourApp />
</ScreebProvider>
);
Per-survey hooksโ
Define hooks for a specific survey when calling surveyStart():
import { useScreeb } from "@screeb/sdk-react";
const MyComponent = () => {
const { surveyStart } = useScreeb();
const showSurveyWithHooks = async () => {
await surveyStart(
"<SURVEY_ID>",
"<DISTRIBUTION_ID>",
false,
{ article_id: 42 },
{
onSurveyShowed: (payload) => {
console.log("This specific survey showed", payload);
},
onSurveyCompleted: (payload) => {
console.log("This specific survey completed", payload);
},
}
);
};
};
Per-message hooksโ
Define hooks for a specific message when calling messageStart():
import { useScreeb } from "@screeb/sdk-react";
const MyComponent = () => {
const { messageStart } = useScreeb();
const showMessageWithHooks = async () => {
await messageStart(
"<MESSAGE_ID>",
false,
{ article_id: 42 },
{
onMessageShowed: (payload) => {
console.log("This specific message showed", payload);
},
onMessageHidden: (payload) => {
console.log("This specific message hid", payload);
},
}
);
};
};
Hook version fieldโ
version is part of the global HooksInit object passed to ScreebProvider/init(). It is not part of the surveyStart() or messageStart() hook objects.
Full payload definitionsโ
For complete payload type definitions and detailed information about each hook, see the JS tag hooks reference.
SPA navigation (in-page-spa)โ
The in-page-spa button target navigates without a full page reload, so the SDK and the onButtonNavigateCompleted hook survive. It auto-detects the host router: click-interception routers (SvelteKit, Astro, Nuxt) are driven via a real anchor click, and History-API routers (React Router, Angular, Vue) fall back to history.pushState + popstate (with hashchange for hash-mode routers). Cross-origin targets always trigger a normal full navigation.
For a router that can't be auto-detected, register a spaNavigationHandler on ScreebProvider:
import { ScreebProvider } from "@screeb/sdk-react";
<ScreebProvider
autoInit
websiteId="<YOUR-CHANNEL-ID>"
spaNavigationHandler={(url) => navigate(url)} // your router's push (e.g. useNavigate() from React Router)
>
<YourApp />
</ScreebProvider>;
See the JS tag hooks reference for details.