# Knock Documentation # Getting started An introduction to the basics of Knock. ## What is Knock? Learn more about what Knock does and how it helps power your product messaging. --- title: What is Knock? description: Learn more about what Knock does and how it helps power your product messaging. tags: ["getting started", "explainer", "explained"] section: Getting started --- Knock is product and customer messaging infrastructure. You can use Knock to power all of your product's messaging needs, including transactional messaging, lifecycle marketing, one-time announcements, and in-product messaging, without the effort of building and maintaining your own in-house messaging system. In this overview, we'll cover the foundational concepts of Knock. Knock is designed to be used collaboratively by engineering, product, and growth teams. It's quick for developers to implement, and simple for non-technical roles to manage. ## Sending messages with Knock Knock enables three core messaging experiences: - **Workflows.** Transactional and lifecycle messaging flows that respond to user actions and events. - **Broadcasts.** One-time messages sent to groups of users for announcements, updates, and campaigns. - **Guides.** In-app lifecycle messaging that helps users discover features and complete key actions. ### Workflows Workflows enable you to model complex messaging flows across channels using a variety of logical function steps while respecting a user's individual preferences. Workflows power both transactional messaging (such as order confirmations, password resets, and account updates) and lifecycle messaging (such as onboarding sequences, engagement campaigns, and retention flows). An image of a workflow diagram Workflows can be triggered in several ways: via a direct API call, from a source event in a CDP like Segment, on a recurring or one-off schedule, or automatically when a user joins an audience. Trigger workflows programmatically using our [REST API](/reference) or any of our [available SDKs](/developer-tools/sdks) when events happen in your application. Connect a CDP like [Segment](/integrations/sources/segment) as an event source to automatically trigger workflows based on events tracked in your analytics platform. ```javascript title="Segment track call" analytics.track("New Comment", { userId: "user_123", properties: { comment_id: "comment_456", project_name: "My Project", commenter: { id: "user_789", name: "John Hammond" } } }); ``` [Schedule workflows](/concepts/schedules) to run at specific times or intervals for time-based notifications like reminders or recurring updates. ```typescript title="Create a recurring schedule" import Knock from "@knocklabs/node"; const client = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await client.schedules.create({ recipients: ["user_123", "user_456"], workflow: "weekly-digest", repeats: [ { frequency: "weekly", days: ["mon"], hours: 9, }, ], }); ``` Automatically trigger workflows when users are added to an [audience](/concepts/audiences). With static audiences, you add users via the API, a CSV upload, or a reverse ETL tool. With dynamic audiences, users automatically enter and exit based on query rules you define on their properties. ```typescript title="Add users to a static audience" import Knock from "@knocklabs/node"; const client = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await client.audiences.addMembers("new-signups", { members: ["user_123", "user_456"], }); ``` ### Broadcasts Broadcasts enable you to send one-time messages to groups of users for communications that aren't triggered by individual user actions. Unlike workflows that are event-driven, broadcasts are initiated directly from the Knock dashboard and can target specific audiences you've created in Knock. An image of the broadcast editor Broadcasts are perfect for: - Product announcements and feature releases - Marketing campaigns and promotional messages - System maintenance notifications - Company updates and newsletters - Time-sensitive alerts and emergency communications ### Guides Guides power in-app lifecycle messaging using your own components and design system. Unlike traditional messaging that happens outside your app, guides appear contextually within your product interface to provide timely, relevant guidance. An image of the guides editor Guides enable you to: - Create interactive onboarding flows - Highlight new features and updates - Guide users through complex workflows - Drive adoption of key product features - Provide contextual help and tips ## Mapping your data into Knock ### Users In most cases, recipients are [users](/concepts/users) in your application. As you trigger workflows for recipients, Knock creates a cache of the data needed to notify them on different platforms, such as an email address, phone number, avatar URL, or push token. Knock also stores custom properties you pass from your application to customize their notifications, such as a plan type, user role, or timezone. ```javascript title="An object used to create a User" { // Id is a required prop id: "1", // Knock also supports default props for common channels name: "John Hammond", email: "hammondj@ingen.net", phone_number: "555-555-5555", avatar: "https://ingen.net/headshots/hammondj.jpg", timezone: "America/Costa_Rica" // You can add as many custom props as needed. These will be // merged onto the top-level User object properties: { "title": "CEO", "planType": "allAccess", "userType": "admin" } } ``` ### Audiences [Audiences](/concepts/audiences) are defined user segments that can be used to target workflows, guides, and broadcasts. You can build static audiences by manually adding users via a reverse ETL source such as Hightouch or Census, a CSV upload, the API, or manually in the Knock dashboard, or you can build dynamic audiences with a set of query rules on top of the user data in Knock. ### Tenants [Tenants](/concepts/tenants) represent accounts, organizations, or workspaces your users belong to. They allow you to map your product's structure into Knock to power tenant-specific features like branding or preferences. ### Objects [Objects](/concepts/objects) allow you to map custom entities into your project and relate them to users with a subscription. They can help you ensure Knock always has the most up-to-date information required to send your notifications, and they enable you to send notifications to non-user recipients. ## Channels Channels in Knock represent a specific provider you have configured to send messages. You can include channel steps in your workflows or use them in broadcasts to send messages with the providers you already use in production. Knock supports the following channel types and providers: Knock comes with a [built-in email channel](/integrations/email/knock-test) and also supports sending email with [Amazon SES](/integrations/email/aws-ses), [Mailersend](/integrations/email/mailersend), [Mailgun](/integrations/email/mailgun), [Mailjet](/integrations/email/mailjet), [Mailtrap](/integrations/email/mailtrap), [Mandrill](/integrations/email/mandrill), [Postmark](/integrations/email/postmark), [Resend](/integrations/email/resend), [Sendgrid](/integrations/email/sendgrid), [SMTP](/integrations/email/smtp), and [Sparkpost](/integrations/email/sparkpost). Knock supports sending SMS with [Africa's Talking](/integrations/sms/africas-talking), [AWS SNS](/integrations/sms/aws-sns), [Mailersend](/integrations/sms/mailersend), [MessageBird](/integrations/sms/messagebird), [Plivo](/integrations/sms/plivo), [Sinch](/integrations/sms/sinch), [Sinch MessageMedia](/integrations/sms/sinch-message-media), [Telnyx](/integrations/sms/telnyx), [Twilio](/integrations/sms/twilio), and [Vonage](/integrations/sms/vonage). Knock supports sending push messages with [Apple Push Notification Service (iOS)](/integrations/push/apns), [Expo (React Native)](/integrations/push/expo), [Firebase Cloud Messaging (Android)](/integrations/push/firebase), and [OneSignal](/integrations/push/one-signal). Knock supports sending chat messages with [Slack](/integrations/chat/slack), [Discord](/integrations/chat/discord), [Microsoft Teams](/integrations/chat/microsoft-teams), [WhatsApp](/integrations/chat/whatsapp). Knock provides [a real-time in-app feed API](/integrations/in-app/knock) for receiving notifications, along with drop-in components to display them to your users. Knock enables you to build [in-app guides](/in-app-ui/guides/overview) that display contextual messages within your product using your own components and design system. A message that is generated as a part of a workflow or broadcast is called a Message, and Knock enables you to define dynamic message templates using a combination of a drag-and-drop editor and the Liquid templating language. This helps product and marketing teams standardize on one templating system instead of using different templating languages for different providers. It also has the added benefit of lifting these messages out of your codebase so you can iterate quickly on customer communications without a developer. ## Functions Each workflow can combine multiple function steps to model complex logic that creates better notification experiences. You can combine the following function steps with any number of channel steps to create personalized notifications for your users: [A batch step](/designing-workflows/batch-function) condenses multiple activities into one notification, e.g. batch all of the comments on this document for one hour and then send one email with all of the activities. [A delay step](/designing-workflows/delay-function) waits for a specified duration before proceeding to the next step in a workflow, e.g. send the new user a follow-up email ten days after they sign up. [A branch step](/designing-workflows/branch-function) uses multiple conditions to execute different branches of logic, e.g. if `user.planType === 'pro'` send them email A, else send them email B. [An experiment step](/designing-workflows/experiment-function) splits recipients into randomized, percentage-based cohorts for A/B testing and experimentation, e.g. send 50% of recipients email template A and 50% email template B to measure engagement. [A throttle step](/designing-workflows/throttle-function) controls how many times a user is notified for a particular workflow over a specified duration, e.g. trigger the `server is down` workflow every minute while the server is down, but only send a max of one email every 5 minutes. [A fetch step](/designing-workflows/fetch-function) makes an HTTP request to an external service and uses the returned data in subsequent steps, e.g. query an MLS API for recent home sales in the user's zip code and render them in an email. [An AI agent step](/designing-workflows/ai-agent-function) runs a prompt on an AI model and merges the response into workflow state, e.g. enrich recipient data using their domain to personalize welcome messaging. [A trigger workflow step](/designing-workflows/trigger-workflow-function) allows you to trigger another workflow from within the current workflow, e.g. trigger a "welcome_sequence" workflow after recieving an "account_setup" notification. In addition to combining channel steps and function steps to create complex workflows, you can augment these steps with additional logic based on the user recipient, inputs from your application, or the status of previous workflow steps. These are called step conditions. ## Step conditions Step conditions exist across both channel and function steps, and they allow you to conditionally execute steps based on trigger payload data, user properties, or the status of previous steps. **Examples:** - Only send an email message if an in-app message has not been seen - Only send an in-app notification if `recipient.plan === "pro"` - Only execute a delay step if `delay === true` in the trigger payload In addition to giving your technical and non-technical users the ability to construct these workflows via a drag-and-drop editor, Knock also enables your users to exercise control over their own notification experience using a flexible preferences model. ## Preferences In Knock, each workflow run and broadcast is executed on behalf of a recipient, and each recipient can specify their preferences to receive messages across a number of different criteria: channel types, individual workflows, workflow categories, and broadcast types. An image of a preference set Application developers have control over how these preference sets are presented to the user and which options to surface, but Knock enforces these preferences during every workflow run and broadcast automatically. You can learn more about how to set a user's preferences in our [preferences overview](/preferences/overview). ## Next steps Now that you understand some of the core concepts of Knock, you can either start building with Knock or explore some of the more advanced features Knock offers. ### Build something If you want to start by adding Knock to your existing system, you can check out our quick start guide to implement your first workflow. This quick start will help you integrate Knock with your backend codebase. If you want to keep learning about Knock using a curated example application, check out our catalog of [examples apps](/getting-started/example-apps). Here are some recommendations: ### Keep learning While workflows, broadcasts, and guides are at the heart of Knock, our goal is to build a complete messaging system for our customers. Here is an overview of some of the more-advanced features that we provide. #### UI components Knock provides developers with React components like `` and `` to use in their applications. You can read more about building in-app UI with Knock for both web and mobile [here](/in-app-ui/overview). #### Advanced concepts There is a lot more to learn about Knock, and our [concepts overview page](/concepts/overview) is a good place to start. Here are use cases our customers commonly solve with Knock: - Powering [translation and localization](/template-editor/translations) and managing timezone-aware delivery. - Creating advanced messaging logic using [subscriptions](/concepts/subscriptions) and [schedules](/concepts/schedules). - Integrating Knock with your application's data model, using [tenants](/concepts/tenants) and [objects](/concepts/objects) to power customized experiences. - Using [the template editor](/template-editor/overview) to standardize messaging templates across providers. #### Developer tools Knock is a developer-first platform, with both [environment](/concepts/environments) and [commit models](/concepts/commits). If you want to work with Knock resources in code, you can use our [Management API](/developer-tools/management-api) or [CLI](/developer-tools/knock-cli). Knock also supports an [MCP](/ai/mcp-server) server, which allows you to work with Knock resources in your IDE, entirely using natural language. Our MCP server is a great way to migrate from a legacy system to Knock, or to make updates across many Knock resources at once. Once you're sending messages through Knock, we offer observability tools like [workflow run logs](/send-notifications/debugging-workflows) (to examine all steps of workflow execution in your dashboard) and data streaming into a monitoring system like Datadog with [extensions](/integrations/extensions/overview). ## Quick start ## General Set up Knock with an agent, or follow the manual steps. --- title: Get started with Knock description: Set up Knock with an agent, or follow the manual steps. tags: ["getting started"] section: Getting started --- First, create a Knock account if you don't already have one and log into the Knock dashboard. We have SDKs available in [most major languages](/developer-tools/sdks). Don't see your language listed here? [Let us know](mailto:support@knock.app)! You can find your public and secret API keys under the **Platform** section of the Knock dashboard. Since we're working on the backend here, you'll want to use the secret key. As a best practice, your API key should be set as an environment variable and should not be checked into source control. ```bash KNOCK_API_KEY='sk_example_123456789' ``` Next we'll design our first workflow in Knock via the dashboard. A workflow encapsulates a notification in Knock. Each workflow takes a trigger call via the Knock API, runs the data you provide through a set of logic you configure, and outputs the actual messages that will be sent to your end users. All channel routing and message design takes place within the workflow. Here's how to build your first workflow: Click the "+ Workflow" button in the top right corner of the Knock dashboard. Name it whatever you like. Workflow overview screen To send a notification, a workflow needs at least one [channel step](/designing-workflows/channel-step). To add this step, we'll click “edit steps” to enter the workflow canvas editor. Here we can see a number of steps available for us to add to our workflow, including functions (such as [batch](/designing-workflows/batch-function) and [delay](/designing-workflows/delay-function)) and channels. add an channel step to workflow canvas Choose the delivery channel you'd like to use in your workflow and drag it onto the workflow canvas. After adding a channel step, we can configure the notification's content by clicking on "Edit template" in the channel's edit step view to see that step's [message template](/template-editor/overview). edit channel step content template The template starts with default copy, so we'll just use that for now. default template for in-app messages Before we leave the workflow canvas and head back to your backend, let’s click on the trigger step to grab a payload data sample to use when we call Knock. copy trigger payload This sample payload is auto-generated when you create a workflow within the Knock dashboard. It gives us the JSON blob we'll need to pass through as `data` in our trigger call in order to populate any of the custom properties defined in our workflow. Knock follows a versioning model similar to Git. This means that before you can trigger your new workflow via the API, you'll need to commit it to your current environment to activate the workflow. use commit button to commit your changes You do this using the `Commit` button in the header of the workflow canvas. You can review your changes using a diff viewer and leave a meaningful commit message about what has changed. diff your changes and leave a message Now we're ready to trigger our workflow via the Knock API. You can also learn more about workflows and channels in Knock via our [documentation on designing workflows](/send-notifications/designing-workflows). Now, you'll trigger your workflow to notify a set of users. When triggering workflows, you need to provide the following required pieces of data in your call to the Knock API: - `recipients` – The list of users to notify. - `data` – The variable data that will populate your notification templates. Here you'll use the sample data payload we grabbed in the previous step. In the example below, we trigger a new comment notification workflow for two project members, using [inline identification](/managing-recipients/identifying-recipients#inline-identification). Learn more about trigger calls in our [API reference](/api-reference/workflows/trigger). Knock uses [logically separated environments](/concepts/environments) to control the roll-out of your notifications. When you're happy with the way your workflows work and look, ensure they're in your production environment to start sending notifications to your real users. You can create workflows directly in production, or create them in development and promote them to production. See our [going to production](/tutorials/implementation-guide#going-to-production) checklist to review a complete set of steps you'll need to take to push your workflows to production. This was a simple overview to send your first notification with Knock. Read on to see how Knock can drive your notification needs, no matter their complexity. - [Learn about Knock's core data concepts](/concepts/overview) - [Learn how to set up a real-time, in-app notification feed in minutes](/notification-feeds/getting-started) ## Go further ## Next.js Set up Knock with an agent, or follow the manual steps. --- title: Get started with Knock description: Set up Knock with an agent, or follow the manual steps. tags: ["getting started"] section: Getting started --- This page covers how to integrate Knock's `NotificationFeed` component in Next.js and send your first notification using a server action. First, create a Knock account if you don't already have one and log into the Knock dashboard. Run the following command to create a new Next.js application. ```bash title="Command to create a new Next.js app" npm create next-app@latest knock-next-quickstart ``` Follow the CLI prompts and use all of the defaults selections, then run the following command to change into your project directory: ```bash title="Install dependencies and test dev server" cd knock-next-quickstart npm install npm run dev ``` Install the following dependencies so you can use the Knock `NotificationFeed` component in your app and trigger workflows from server actions. ```bash title="Install Knock dependencies" npm install @knocklabs/react @knocklabs/node ``` Run the following command to generate a new `env.local` file and add the following environment variables to authenticate your requests to Knock: ```bash title="Create an env file" touch .env.local ``` You can find your [public and secret API keys](/developer-tools/api-keys) under the **Platform** section of the Knock dashboard. You can find the `id` of your Knock in-app feed channel in **Settings** > **Integrations** > **Channels**. ```javascript title="Add these values to .env.local" KNOCK_API_KEY='sk_example_123456789' NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY='pk_example_123456789' NEXT_PUBLIC_KNOCK_FEED_CHANNEL_ID='f9923d96-bee5-48bd-a1ff-31b6637b7385' ``` You'll need to create a component to render the default Knock components. Run the following command to create a new component file called `inbox.tsx` inside of the `app`: ```bash title="Create an inbox.tsx file" touch ./app/inbox.tsx ``` Since this file uses client-side React APIs, it will need to be a client component. You can add the following code to the empty `inbox.tsx` file: ```javascript title="Add this code to inbox.tsx" "use client"; import { KnockProvider, KnockFeedProvider, NotificationFeed, NotificationIconButton, } from "@knocklabs/react"; // Required CSS import, unless you're overriding the styling import "@knocklabs/react/dist/index.css"; const Inbox = () => { // An example of fetching the current authenticated user const user = { id: "12c23775-5902-481a-b8ea-d1704aabc769", }; return ( <>
{}} />
); }; export default Inbox; ``` In production, you will want to colocate `KnockProvider` and `KnockFeedProvider` at a higher level in your application. If you pass a `user` that doesn't exist, Knock will automatically create one with the specified `id` using [client-side inline identification](/managing-recipients/identifying-recipients#inline-identification).
Next, replace the contents of `/app/page.tsx` with the code below to import the `Inbox` component and render it in `page.tsx`. ```javascript title="Render the Inbox component" import Inbox from "./inbox"; const Page = () => { return (

Knock Inbox Test

); }; export default Page; ``` Next, run your development server to render the `Inbox` component. There will be no messages, but you should be able to switch between tabs. ```bash title="Run Next.js dev server" npm run dev ```
Next we'll design our first workflow in Knock via the dashboard. A workflow encapsulates a notification in Knock. Each workflow takes a trigger call via the Knock API, runs the data you provide through a set of logic you configure, and outputs the actual messages that will be sent to your end users. All channel routing and message design takes place within the workflow. Here's how to build your first workflow: Navigate to the **Workflows** section of the Knock dashboard. Click the "+ Workflow" button in the top right corner of the Knock dashboard. Name it `knock-quickstart` to test your integration. Workflow overview screen To send a notification, a workflow needs at least one [channel step](/designing-workflows/channel-step). To add this step, we'll click “edit steps” to enter the workflow canvas editor. Here we can see a number of steps available for us to add to our workflow, including functions (such as [batch](/designing-workflows/batch-function) and [delay](/designing-workflows/delay-function)) and channels. add an channel step to workflow canvas Drag the 'In-app feed' step onto the workflow canvas. After adding your channel step, you can configure the notification's content by clicking on "Edit content" in the channel's edit step view to see that step's [message template](/template-editor/overview). edit channel step content template The template starts with default copy, so we'll just use that for now. default template for in-app messages Knock follows a versioning model similar to Git. This means that before you can trigger your new workflow via the API, you'll need to commit it to your current environment to activate the workflow. use commit button to commit your changes You do this using the `Commit` button in the header of the workflow canvas. You can review your changes using a diff viewer and leave a meaningful commit message about what has changed. diff your changes and leave a message Now we're ready to trigger our workflow via the Knock API. You can also learn more about workflows and channels in Knock via our [documentation on designing workflows](/send-notifications/designing-workflows). Now, you'll trigger your workflow to notify your user using a server action. Run the following command to create a file named `trigger.action.ts` in the `app` directory: ```bash title="Create a file named trigger.action.ts" touch ./app/trigger.action.ts ``` When triggering workflows, you need to provide the following required data in your Knock API request: - `recipients` – The list of users to notify. This list should contain the same user `id` you used when configuring the `Inbox` component. - `data` – The variable data that will populate your notification templates. Here you'll include a object with a `message` key. Add the code below to `trigger.action.ts` to trigger the `knock-quickstart` workflow. ```javascript title="Add this code to trigger.action.ts" "use server"; import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const user = { id: "12c23775-5902-481a-b8ea-d1704aabc769", }; export default async function triggerWorkflow() { const workflow_run_id = await knock.workflows.trigger("knock-quickstart", { data: { message: "Here's a message" }, recipients: [user.id], }); return workflow_run_id; } ``` Learn more about trigger calls in our [API reference](/api-reference/workflows/trigger). To trigger your workflow, you'll need to connect the server action in `trigger.action.ts` to a UI element in `page.tsx`. First, run the following command to create a new component file called `workflow-trigger.tsx` in the `app` directory: ```bash title="Create a new component file called workflow-trigger.tsx" touch ./app/workflow-trigger.tsx ``` This client component will import the `triggerWorkflow` server action and tie it to the `onClick` prop of a `button`. Replace the contents of `workflow-trigger.tsx` with the following code: ```javascript title="Add this code to workflow-trigger.tsx" "use client"; import triggerWorkflow from "./trigger.action"; export default function WorkflowTrigger() { return (
); } ``` Next, add the `WorkflowTrigger` component to `page.tsx`. You can replace the contents of `page.tsx` with the following code: ```javascript title="Completed page.tsx" import Inbox from "./inbox"; import WorkflowTrigger from "./workflow-trigger"; const Page = () => { return (

Knock Inbox Test

); }; export default Page; ``` With the server started, you can click the **Trigger Workflow** button and the application will run the `triggerWorkflow` server action and produce a notification in the feed.
Knock uses [logically separated environments](/concepts/environments) to control the roll-out of your notifications. When you're happy with the way your workflows work and look, ensure they're in your production environment to start sending notifications to your real users. You can create workflows directly in production, or create them in development and promote them to production. See our [going to production](/tutorials/implementation-guide#going-to-production) checklist to review a complete set of steps you'll need to take to push your workflows to production. This was a simple overview to send your first notification with Knock. Read on to see how Knock can drive your notification needs, no matter their complexity. - [Learn about Knock's core data concepts](/concepts/overview) - [Learn how to set up a real-time, in-app notification feed in minutes](/notification-feeds/getting-started)
## Go further ## React Set up Knock with an agent, or follow the manual steps. --- title: Get started with Knock description: Set up Knock with an agent, or follow the manual steps. tags: ["getting started"] section: Getting started --- This page covers how to integrate Knock's `NotificationFeed` component in a React application and send your first notification using Knock's test runner. First, create a Knock account if you don't already have one and log into the Knock dashboard. Run the following command to create a new React application. ```bash title="Command to create a new React app" npm create vite@latest knock-react-quickstart -- --template react-ts ``` Follow the CLI prompts and use all of the default selections, then run the following command to change into your project directory: ```bash title="Install dependencies and test dev server" cd knock-react-quickstart npm install npm run dev ``` Install the following dependencies so you can use the Knock `NotificationFeed` component in your app and trigger workflows from server actions. ```bash title="Install Knock dependencies" npm install @knocklabs/react ``` Run the following command to generate a new `env.local` file and add the following environment variables to authenticate your requests to Knock: ```bash title="Create an env file" touch .env.local ``` You can find your [public and secret API keys](/developer-tools/api-keys) under the **Platform** section of the Knock dashboard. You can find the `id` of your Knock in-app feed channel in **Settings** > **Integrations** > **Channels**. ```javascript title="Add these values to .env.local" VITE_KNOCK_PUBLIC_API_KEY='pk_example_123456789' VITE_KNOCK_FEED_CHANNEL_ID='f9923d96-bee5-48bd-a1ff-31b6637b7385' ```` You'll need to create a component to render the default Knock components. Run the following command to create a new component file called `inbox.tsx` inside of the `app`: ```bash title="Create an inbox.tsx file" mkdir ./src/components touch ./src/components/inbox.tsx ``` Since this file uses client-side React APIs, it will need to be a client component. You can add the following code to the empty `inbox.tsx` file: ```javascript title="Add this code to inbox.tsx" import { KnockProvider, KnockFeedProvider, NotificationFeed, NotificationIconButton, } from "@knocklabs/react"; // Required CSS import, unless you're overriding the styling import "@knocklabs/react/dist/index.css"; const Inbox = () => { // Use the authenticated user's ID in KnockProvider below to inline identify the current user const user = { id: "12c23775-5902-481a-b8ea-d1704aabc769", }; return ( <>
{}} />
); }; export default Inbox; ```` In production, you will want to colocate `KnockProvider` and `KnockFeedProvider` at a higher level in your application. If you pass a `user` that doesn't exist, Knock will automatically create one with the specified `id` using [client-side inline identification](/managing-recipients/identifying-recipients#inline-identification).
Next, replace the contents of `/src/App.tsx` with the code below to import the `Inbox` component and render it in your `App` component. ```javascript title="Render the Inbox component" import "./App.css"; import Inbox from "./components/inbox"; function App() { return ( <>

Knock Inbox Test

); } export default App; ``` Next, run your development server to render the `Inbox` component in a browser. There will be no messages, but you should be able to switch between tabs. ```bash title="Run Vite dev server" npm run dev ```
Next we'll design our first workflow in Knock via the dashboard. A workflow encapsulates a notification in Knock. Each workflow takes a trigger call via the Knock API, runs the data you provide through a set of logic you configure, and outputs the actual messages that will be sent to your end users. All channel routing and message design takes place within the workflow. Here's how to build your first workflow: Navigate to the **Workflows** section of the Knock dashboard. Click the "+ Workflow" button in the top right corner of the Knock dashboard. Name it `knock-quickstart` to test your integration. Workflow overview screen To send a notification, a workflow needs at least one [channel step](/designing-workflows/channel-step). To add this step, we'll click “edit steps” to enter the workflow canvas editor. Here we can see a number of steps available for us to add to our workflow, including functions (such as [batch](/designing-workflows/batch-function) and [delay](/designing-workflows/delay-function)) and channels. add a channel step to workflow canvas Drag the 'In-app feed' step onto the workflow canvas. After adding your channel step, you can configure the notification's content by clicking on "Edit content" in the channel's edit step view to see that step's [message template](/template-editor/overview). edit channel step content template The template starts with default copy, so we'll just use that for now. default template for in-app messages Knock follows a versioning model similar to Git. This means that before you can trigger your new workflow via the API, you'll need to commit it to your current environment to activate the workflow. use commit button to commit your changes You do this using the `Commit` button in the header of the workflow canvas. You can review your changes using a diff viewer and leave a meaningful commit message about what has changed. diff your changes and leave a message Now we're ready to trigger our workflow via the Knock API. You can also learn more about workflows and channels in Knock via our [documentation on designing workflows](/send-notifications/designing-workflows). Now, you'll trigger your workflow to a user and produce a notification in the feed. You can do that through the test runner in the workflow editor, or via API call. Your recipient should be the `id` of the `user` we identified in our `Inbox` component. When triggering workflows, you need to provide the following required pieces of data in your call to the Knock API: - `recipients` – The list of users to notify. - `data` – The variable data that will populate your notification templates. Here you'll use the sample data payload we grabbed in the previous step. In the example below, we trigger the `knock-quickstart` workflow for the `user` we identified with the feed. ```javascript title="Example server code to trigger workflow" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const user = { id: "12c23775-5902-481a-b8ea-d1704aabc769", }; await knock.workflows.trigger("knock-quickstart", { data: { message: "Here's a message" }, recipients: [user.id], }); ``` Learn more about trigger calls in our [API reference](/api-reference/workflows/trigger). Knock uses [logically separated environments](/concepts/environments) to control the roll-out of your notifications. When you're happy with the way your workflows work and look, ensure they're in your production environment to start sending notifications to your real users. You can create workflows directly in production, or create them in development and promote them to production. See our [going to production](/tutorials/implementation-guide#going-to-production) checklist to review a complete set of steps you'll need to take to push your workflows to production. This was a simple overview to send your first notification with Knock. Read on to see how Knock can drive your notification needs, no matter their complexity. - [Learn about Knock's core data concepts](/concepts/overview) - [Learn how to set up a real-time, in-app notification feed in minutes](/notification-feeds/getting-started)
## Go further ## Example apps Example applications to help you get started with Knock. --- title: Knock example apps description: Example applications to help you get started with Knock. tags: ["nodejs", "using knock", "getting started", "react"] section: Getting started --- Below you'll find a number of Knock example apps to learn from or incorporate into your project. ## In-app notification examples (web) ## Web app examples ## Mobile examples # Concepts Learn about the key concepts in Knock. ## Overview Learn about the key concepts in Knock. --- title: Core concepts description: Learn about the key concepts in Knock. tags: ["how knock works"] section: Concepts --- ## Workflows Workflows are triggered journeys that send notifications to your recipients. They can be triggered via an API call, an event, on a schedule for a recipient, or when a user enters a specific audience. Workflows consists of channel and function steps. Workflows are useful for transactional notifications and lifecycle-based messaging such as onboarding flows. [Learn more →](/concepts/workflows) ## Broadcasts Broadcasts are a way to send one-time, cross-channel notifications to your users through Knock. They use the Knock workflow engine, but are configured in the Knock dashboard to run once. Broadcasts are useful for one-time notifications such as email announcements or system status updates. [Learn more →](/concepts/broadcasts) ## Guides Guides enable you to power in-product messaging using your own components. Unlike workflows which have to be triggered, guides are rendered when eligible users visit relevant pages in your application. Guides are useful for announcements, paywalls, nudges, banners, and other in-product messaging that doesn't fit into a feed-based notification center. [Learn more →](/concepts/guides) ## Channels A channel in Knock represents a configured provider, such as Sendgrid for email, to send notifications to your recipients. Most providers within Knock use credentials that you supply to deliver notifications on your behalf. These credentials and other settings are what make a configured channel. [Learn more →](/concepts/channels) ## Commits Knock uses a commit model to version changes that you make to all of your Knock resources. When you make a change to a workflow or a layout in the Knock dashboard, you'll need to commit it to your development environment before those changes will appear in workflows triggered via the API. [Learn more →](/concepts/commits) ## Environments Knock uses the concept of environments to ensure logical separation of your data and configuration. This means that users and preferences created in one environment are **never** accessible to another. Environments usually map to the environments you have in your software development life cycle (SDLC). [Learn more →](/concepts/environments) ## Recipients A Recipient within Knock is any [User](#users) or [Object](#objects) that may wish to receive notifications. [Learn more →](/concepts/recipients) ## Users A user in Knock represents an individual who should receive a message. A user's profile information contains important attributes about the user that will be used in messages (name, email). The user object can contain other key-value pairs that can be used to further personalize your messages. [Learn more →](/concepts/users) ## Preferences Preferences enable your users to opt-out of the notifications you send using Knock. [Learn more →](/concepts/preferences) ## Objects An object represents a resource in your system that you want to map into Knock. Objects are a powerful and flexible way to ensure Knock always has the most up-to-date information required to send your notifications. They also enable you to send notifications to non-user recipients. You can use objects to: - send in-app notifications to non-user resources in your product (the activity feed you see on a Notion page is a good example) - send out-of-app notifications to non-user recipients (such as a Slack channels) - reference mutable data in your notification templates (such as when a user edits a comment before a notification is sent) [Learn more →](/concepts/objects) ## Subscriptions A subscription represents a relationship between a non-user entity (an Object) and a Recipient (the subscriber). Subscriptions are used to model pub/sub behavior and lists of recipients that Knock will automatically fan out a workflow trigger to on your behalf. [Learn more →](/concepts/subscriptions) ## Schedules A schedule allows you to automatically trigger a workflow at a given time for one or more recipients. You can think of a schedule as a managed, recipient-timezone-aware cron job that Knock will run on your behalf. [Read more →](/concepts/schedules) ## Tenants Tenants represent segments your users belong to. You might call these "accounts," "organizations," "workspaces," or similar. This is a common pattern in many SaaS applications: users have a single login joined to multiple tenants to represent their membership within each. Within Knock you can model your tenant objects as first-class entities and use them to scope features. [Learn more →](/concepts/tenants) ## Messages A message in Knock represents a notification delivered to a recipient on a particular channel. Messages contain information about the request that triggered its delivery, a view of the data sent to the recipient, and a timeline of its lifecycle events. [Learn more →](/concepts/messages) ## Translations Translations support localization in Knock. They hold the translated content for a given locale, which you can reference in your message templates with the `t` Liquid function filter. [Learn more →](/template-editor/translations) ## Conditions Knock uses conditions to model checks that determine variations in your workflow runs. They provide a powerful way to create more advanced notification logic flows. [Learn more →](/concepts/conditions) ## Variables Variables within Knock let you set shared constants or secrets that you can use in all of the workflows and templates under your account. Variables can be overridden at the environment level to set per environment constants. [Learn more →](/concepts/variables) ## Audiences Audiences are user segments that you can notify. You can bring audiences into Knock programmatically with our API or a supported reverse-ETL source. [Learn more →](/concepts/audiences) ## Workflows Learn more about what a workflow in Knock is, and how to think about grouping together your cross-channel notifications into different workflows. --- title: Workflows description: Learn more about what a workflow in Knock is, and how to think about grouping together your cross-channel notifications into different workflows. tags: ["categories", "archive", "archived"] section: Concepts --- Workflows are triggered journeys that send notifications to your recipients. Workflows are represented as a set of steps, which are either function or channel steps. Functions apply logic to your workflow run, like batching and branching. Channel steps produce notifications that are delivered via your [configured channels](/concepts/channels). Workflows in Knock: - Have a unique `key` - Execute for a single recipient at a time - Contain all of the logic and templates for the notifications you send - Evaluate recipient [preferences](/preferences/overview) automatically - Are triggered via API calls, events, audience events, or recipient schedules if you're looking to send a one-time message to a set of recipients, you should use a broadcast instead. } /> You can read more about how to build your workflows and the features available within the workflow builder under the [designing workflows section of the documentation](/designing-workflows). ## Workflows and notification templates Each workflow you build will contain one or more [channel steps](/designing-workflows/channel-step). It's these channel steps that contain the templates that will be rendered to produce a notification sent to the recipient of the workflow run. The templates associated with a channel step **only** exist in the context of that channel step. That means that templates cannot currently be shared across workflows, or even across other channel steps within the same workflow. ## Managing workflows Knock workflows can be managed either via the Knock dashboard or programmatically via the [Management API](/mapi-reference). The [Knock CLI](/cli/overview) offers a convenient way to work with the management API locally to make updates to workflows and their templates. ### Workflow categories Each workflow can have one or more categories associated with it. Categories are useful for grouping related types of workflows together and offer a way to apply a user's preferences across many workflows. To set a `category` for a given workflow, go to that workflow's page in the dashboard, click the "..." menu, and select "Manage workflow." From there, you'll be able to add categories. Workflow categories are case sensitive and may contain special characters and spaces. } /> ### Version control for workflows All changes to workflows, including changes made to the templates inside of a workflow, are version controlled. Versions are stored in commits. You can create workflows directly in production, or, for production-critical use cases, create them in development and promote them to production when you're ready to go live. Read more about [environments](/version-control/environments) and [versioning](/version-control/commits) in Knock. ### Workflow status Each workflow has an `Active`/`Inactive` status that is displayed in your dashboard's **Workflows** section. The status defaults to `Active` and can be set by clicking on the workflow and using the **Status** selector in the **Details** section of the **Overview** tab. This is your kill switch for a given workflow should you need it; any attempt to trigger an `Inactive` workflow will result in a `workflow_inactive` [error](/api-reference/overview/errors) and no workflow runs will be enqueued. The status setting operates independently from the commit model so that you can immediately enable or disable a workflow in any environment without needing to go through environment promotion. **It is environment-specific and will only be applied to the current environment.** See the [frequently asked questions](#frequently-asked-questions) section below for more information on how in-progress workflow runs are affected when you set a workflow's status to `Inactive`. ### Archiving workflows Archiving a workflow allows you to permanently remove a workflow from Knock. When you archive a workflow it will be removed from **all environments** and cannot be called via API. Once a workflow is archived, it **cannot be undone**. If you have delayed runs for a workflow that is archived, when the workflow run resumes after the delay it will immediately terminate. ## Running workflows Workflows defined in Knock are executed via trigger, which starts a workflow run for the recipients specified using the `data` passed to the workflow trigger. it's important to know that in Knock a workflow run is{" "} always executed against a single recipient. Workflows can always be invoked for multiple recipients, but each run will only be for a single recipient. } /> ### Triggering a workflow In Knock, workflows can be triggered in three different ways: - **API call**: workflows can be [triggered directly via an API call](/send-notifications/triggering-workflows) to our workflow trigger endpoint. This is the most common form of integration and means that Knock is integrated into your backend codebase, usually alongside your application logic. - **Events**: using different [event sources](/integrations/sources/overview/), you can connect Knock to CDPs such as Segment and RudderStack and map the events those systems produce to workflows that should be triggered. - **Schedules**: [workflows can be scheduled](/concepts/schedules) to be run for one or more recipients, in a recipient's local timezone on a one-off, or recurring basis. ### Canceling a workflow run Any triggered workflow that has an active delay or batch step can also be canceled to halt the execution of that workflow run. Workflow cancellations today must happen through the cancellation API and can only occur when a `cancellation_key` has been specified on the workflow trigger. [Read more about canceling workflows](/send-notifications/canceling-workflows) ### Workflow runs and recipients When a workflow is triggered via the API we return a `workflow_run_id` via the API response. This ID represents the workflow run for all of the recipients that the workflow was triggered against. For each recipient included in the workflow trigger or that the workflow should fan out to [via subscriptions](/concepts/subscriptions), a new workflow run is enqueued. We call this the recipient workflow run. Recipient runs are visible within the Knock dashboard by going to **Observability** > **Logs**. Each run can be inspected to view its current state as well as the steps executed for the workflow. It's also possible from a workflow run log to see the messages (notifications) produced by the run. ### Workflow run scope When a workflow run is executed, associated state is loaded to be used within the templates and conditions defined in the workflow. This state is known as the workflow run scope. The run scope can be modified during the duration of the workflow run by fetching additional data via the [fetch function](/designing-workflows/fetch-function). [Read more about the properties available](/template-editor/variables) ## Automate workflow management with the Knock CLI In addition to working with workflows in the Knock dashboard, you can programmatically create and update workflows using the [Knock CLI](/developer-tools/knock-cli) or our [Management API](/developer-tools/management-api). If you manage your own workflow files within your application, you can automate the creation and management of Knock workflows so that they always reflect the state of the workflow files you keep in your application code. The Knock CLI can also be used to commit changes and promote them to production, which means you can automate Knock workflow management as [part of your CI/CD workflow](/tutorials/integrating-into-cicd). See the [Workflow file structure](/cli/workflow/file-structure) section in the CLI reference for details on how workflow files are organized when working with the CLI. You can learn more about automating workflow management in the [Knock CLI reference](/cli/overview). Feel free to contact us if you have questions. ## Frequently asked questions No, there's no limit to the number of workflows you can have within your Knock environment. It's highly recommended to build individual workflows for each use case. While it might be tempting to build a single workflow with conditional logic for all of your notification use cases that can be triggered from anywhere within your application with the same workflow `key`, modularizing your workflows by use case allows you to offer the highest level of configurability to your users via [Preferences](/concepts/preferences). Our customers also find that concise, use-case specific workflows are easier to maintain and iterate on. As an example, if we're building a document collaboration app where users can comment on specific documents, we might group all of the logic about the cross-channel comment notifications we have into a single `new-comment` workflow. While it's possible to create per-customer workflows using the management API, we recommend avoiding doing this in favor of using [per-tenant branding](/multi-tenancy/per-tenant-branding) and [preferences](/concepts/preferences) to control individual workflows. Yes, you can set a workflow's [status](/concepts/workflows#workflow-status) to `Inactive` to disable it. While a workflow's status is set to `Inactive`, any new workflow triggers will be rejected with a `workflow_inactive` error. If a workflow step attempts to process for a workflow that is currently set to `Inactive`, the workflow run will be immediately terminated. Workflow runs that are currently in a paused state (due to a [delay](/designing-workflows/delay-function) or [batch](/designing-workflows/batch-function) step) when you set your workflow to `Inactive` are not immediately affected; however, if the paused step completes and the workflow run progresses to the next step while the workflow remains `Inactive`, it will be terminated. If you set a workflow to `Inactive` and back to `Active` while a given in-progress workflow run remains paused, that workflow run will resume at the end of the delay and continue to completion. ## Broadcasts Power one-time, cross-channel messaging to your users through Knock. --- title: Broadcasts description: Power one-time, cross-channel messaging to your users through Knock. tags: [ "segmentation", "user segmentation", "lifecycle", "marketing", "audience", "groups", "segments", "one-time", "one-off", "cross-channel", "broadcast", "broadcasts", ] section: Concepts --- Broadcasts are a way to power one-time, cross-channel messaging to your users through Knock. They are built on Knock's [workflow engine](/concepts/workflows), giving you the power to create intelligent messaging across all of the [channels you've configured in Knock](/concepts/channels). ## Broadcasts and the environment model Unlike workflows and other resources in Knock, broadcasts do not have to be committed or promoted to an environment before they can be used. In fact, unlike workflows, broadcasts are **environment-specific** and can be created in any environment (including production). If you wish to mirror a flow where you create a broadcast in development, and then use it in production you can do so by [cloning the broadcast](/version-control/environments#clone-resources-across-environments) to the production environment. ## Targeting users for a broadcast Broadcasts can be targeted to a specific set of users [via an audience](/concepts/audiences), sent to all users in an environment, or sent to a specific set of users by uploading a CSV. Your users **must** exist within Knock in order to send them a broadcast. If you're looking to send a broadcast to users who don't yet exist within Knock, you can use a CSV to upload a list of users who will first be identified before the broadcast is sent. Knock will upsert the users as they are added to the broadcast and skip any users with malformed or missing IDs. You can also create a new audience on the fly when uploading a CSV of users to a broadcast. You can also include a tenant ID associated with each user when uploading a CSV of users to a broadcast. Each user-tenant pair will be treated as a distinct recipient for the purposes of the broadcast. See [using audiences with tenants](/concepts/audiences#using-audiences-with-tenants) for more details. **Note**: Uploading the same user multiple times with a different tenant ID will always persist the last user properties uploaded in the list onto the user. If you want to use data specific to a tenant, you can categorize your fields as "broadcast data" so that it will be available for that particular user-tenant pair. ## Creating and managing broadcast content To manage the contents of a broadcast, click the "Edit steps" button in the broadcast overview. This will open the broadcast builder, where you can add steps that will be executed for each user the broadcast is sent to. In the broadcast builder, you can add: - [Delay functions](/designing-workflows/delay-function): to pause the execution of a broadcast for a specific amount of time. - [Wait for event functions](/designing-workflows/wait-for-event-function): to pause the execution of a broadcast until a matching event is received or until a wait time expires. - [Branch functions](/designing-workflows/branch-function): to conditionally execute steps based on the value of a variable. - [Experiment functions](/designing-workflows/experiment-function): to randomly assign recipients to percentage-based cohorts for A/B testing and experimentation. - [Channel steps](/designing-workflows/channel-step): to send a message to a user on a specific channel. - [In-app guide step](/designing-workflows/in-app-guide-step): to make a user eligible for an [in-app guide](/concepts/guides). When adding a channel step, you can select from any of the channel types you've configured in Knock to send a message to a user on that channel. Once you've added a channel step, you can design and manage the template for the step by selecting the channel step and clicking the "Edit template" button. ### Working with the template editor The broadcast template editor is the same template editor you've come to know and love from [workflows](/concepts/workflows). You can read more about working in the template editor [here](/template-editor/overview). ### Broadcast state Knock will automatically expose the following variables to the broadcast builder and template editor which you can use to personalize and control the broadcast: - `recipient`: the user that the broadcast is being sent to. - `vars`: the environment variables available to the broadcast. - `tenant`: in the event that your broadcast has been triggered to a user-tenant audience member, this will be the full tenant object linked. You can access the broadcast state in the broadcast builder by toggling the state pane. All of these variables are also available [in the template editor](/template-editor/variables). ### Per-recipient data When you upload a CSV of users to a broadcast, you can map any columns of your CSV to either the "User" or "Broadcast" schema. When you map them to the user, these properties will be persisted to the user object in Knock. When you map them to the broadcast, these properties will be available in the template editor for this broadcast only and cannot be reused in other broadcasts. You can access these properties in the template editor by using the `data` variable as you would with workflow data. Due to the nature of the CSV format, all data values (user or broadcast) that are upserted via CSV as part of a broadcast will be treated as strings. You'll need to account for this when using your data in your templates or conditions. } /> ## Scheduling broadcasts Broadcasts can optionally be scheduled to send at a specific time. When a broadcast is scheduled, you will see the status of the broadcast reflected as scheduled on the date and time you specified. Scheduled broadcasts can be canceled before they are sent, should you wish to make any changes. ## Broadcast analytics Broadcasts produce messages, exactly as workflows do. As such, messages sent as part of a broadcast will be visible in the **Analytics** page within the Knock dashboard. Additionally, broadcast engagement metrics are available in any [connected data warehouses](/integrations/extensions/data-sync). Sent broadcasts include a recipient summary that highlights key delivery and engagement metrics, including: - **Sent**: The number of messages that were successfully sent to the delivery provider. - **Delivered**: The number of messages that were successfully delivered, as reported by the delivery provider. This may vary based on the configured channel. Learn more about the [delivered status](/send-notifications/message-statuses#7-delivered). - **Bounced**: The number of messages that were dropped by the delivery provider due to bad recipient data. - **Read**: The number of messages that were opened and read by the recipient at least once. - **Interacted**: The number of messages that were interacted with (e.g. the recipient clicked a link). To learn more, see our [message status documentation](/send-notifications/message-statuses). Broadcast analytics summary ## Testing broadcasts You can test a broadcast by clicking the "Run test" button in the broadcast overview or on the broadcast builder. This will allow you to select a specific test user to send the broadcast to. When you've run a test, you'll see the run log for the broadcast in the "Runs" tab. You'll also see any messages generated as part of the test run in the "Messages" tab. ## Debugging and observing broadcasts Broadcasts are processed in the same way as workflows, and as such, you can use the same tools to debug and observe broadcasts as you can workflows. - **Messages** generated from a broadcast are available under the "Messages" logs, which you can find in the main sidebar on the dashboard, under a user's profile, or within a specific broadcast. - **Runs** generated from executing broadcasts are available under the "Runs" tab, under a user's profile, or within a specific broadcast. Additionally, developer tools and extensions are available for broadcasts as they are workflows. - [Outbound webhooks](/developer-tools/outbound-webhooks/overview): all messages produced by a broadcast will emit outbound webhook events. - [Observability extensions](/integrations/extensions/overview): all processing metrics produced by a broadcast will be sent to the observability tools you have configured (Datadog and New Relic). - [CDP and analytics extensions](/integrations/extensions/overview): all message events produced by a broadcast will be sent to the CDP and analytics tools you have configured (Segment and Heap). - [Data warehouse sync](/integrations/extensions/data-sync): all messages produced by a broadcast will be visible within your Knock `messages` table. ## Broadcasts and user preferences Broadcasts respect all [user preferences](/concepts/preferences) that are configured in Knock. That means for any categories added to a broadcast, preferences linked to those categories will be respected. Additionally, any channel type preferences set for a user will be respected during the execution of a broadcast. Knock also supports [commercial unsubscribe](/preferences/commercial-unsubscribe) preferences that will automatically add a 1-click unsubscribe header and link to your email messages, to respect CAN-SPAM compliance. ## Frequently asked questions `owner`, `admin`, and `member` [roles](/manage-your-account/roles-and-permissions) can create and send broadcasts to users. No, broadcasts are not callable via the API. No, currently broadcasts cannot be managed via the Knock CLI. No, currently broadcasts can only be sent to a set of users, not objects. If you have an object sending use case, we'd [love to hear from you](mailto:support@knock.app?subject=Broadcast%20to%20objects). Your users must be identified as users in Knock before you can send them a broadcast. When you upload a CSV of users to a broadcast, Knock will automatically identify the users. The maximum size of a CSV upload is capped at 10MB. ## Guides Power in-product messaging such as announcements, paywalls, and banners using your own components. --- title: Guides description: Power in-product messaging such as announcements, paywalls, and banners using your own components. tags: [] section: Concepts ---

## Frequently asked questions Many of our [supported integrations](/integrations/overview) offer native link or engagement tracking that can be enabled in your Knock dashboard under that integration's [channel settings](/concepts/channels#channel-settings). Knock supports enabling these native tracking solutions alongside or instead of Knock tracking. There are several Knock-tracking-specific features to keep in mind when configuring your channel's tracking settings: - **Cross-channel reporting.** Knock link and open tracking allows you to analyze user engagement across all of your delivery channels in a single tool. - **Step conditions.** With Knock tracking, you can use message engagement events to [power conditional logic in your workflows](/designing-workflows/step-conditions). - **Capture events via webhooks.** When Knock tracking is enabled, you can power other event-based flows in your product with Knock's [outbound webhooks](/developer-tools/outbound-webhooks/overview). No. While some providers offer native open and link tracking that can be enabled in your Knock dashboard under that integration's [channel settings](/concepts/channels#channel-settings), these native tracking solutions will not report back to Knock and do not enable the same functionality. ## Analytics Learn how to measure message volume, delivery, and engagement across your workflows, guides, and channels. --- title: Analytics description: Learn how to measure message volume, delivery, and engagement across your workflows, guides, and channels. tags: [ "analytics", "metrics", "engagement", "message volume", "delivery rates", "dashboard", ] section: Send notifications --- The [Analytics](https://dashboard.knock.app/~/analytics) page in the Knock dashboard gives you a high-level view of message volume, delivery, and engagement across your account. Use it to see how many messages you're processing, track delivery and engagement rates over time, and find the workflows, guides, and channels that drive the most activity. ## Overview The overview section of the Analytics page in the Knock dashboard Metrics found on the Analytics page are scoped to the [environment](/concepts/environments) you're viewing and reflect your account's [data retention period](/manage-your-account/data-retention). The top of the page summarizes message activity for your selected time range and filters. Knock generates a [message](/concepts/messages) each time a workflow or broadcast runs a channel step for a recipient and the summary metrics roll those messages up by [delivery status](/send-notifications/message-statuses#delivery-status). Below the summary metrics, you'll find two additional views: - **Total messages processed chart.** A time series of message volume across the selected range. You can break this chart down by dimension — see [grouping data](#grouping-data) for details. - **Top results tables.** Breakdown tables that rank your highest-volume workflows, guides, and channels for the current range and filters. ## Filtering data The time range and the filter menu scope every metric, chart, and table on the page to your selections. Use the time range selector to choose your reporting window, such as **Last 7 days**. You can report on any window within your account's data retention period. Use the **Filter** menu to narrow the data to a subset of your messages. You can filter by: - **Channel.** A specific configured [channel](/concepts/channels). - **Channel type.** A class of channel, such as email, in-app feed, push, SMS, or chat. - **Delivery status.** A point in the delivery lifecycle, such as `delivered` or `undelivered`. - **Tenant ID.** The [tenant](/concepts/tenants) a message is scoped to. - **Workflow.** The [workflow](/concepts/workflows) that generated the message. - **Guide.** The [guide](/concepts/guides) that generated the message. Select your filters and choose **Apply** to update the page, or **Clear** to reset them. ## Grouping data The total messages processed chart supports grouping, which splits the single line into one line per value of the chosen dimension. Open the **Group by** menu on the chart and select one dimension: - **Channel.** One line per configured channel. - **Channel type.** One line per class of channel. - **Delivery status.** One line per delivery status. - **Engagement status.** One line per [engagement status](/send-notifications/message-statuses#engagement-status), such as seen, read, link clicked, interacted, or archived. - **Workflow key.** One line per workflow. - **Guide key.** One line per guide. For example, grouping by engagement status renders a separate line for read, link clicked, seen, archived, and interacted, so you can compare how recipients engage over time. You can also set the chart type used to render the series. The total messages processed chart grouped by engagement status ## Understanding message events Knock analytics are based on [message events](/send-notifications/message-statuses#message-events) which affects how engagement is counted and dated. ### Event counts Engagement metrics include every event, not just the first occurence. A recipient who clicks the same message three times is reported as three clicks rather than one. ### Event dates Engagement metrics are dated to when the event occurred, not when the message was created. For a message sent on March 1st and clicked on March 2nd, Knock shows a click event on March 2nd. ### Unique recipients Unique recipients is a top-level metric that counts the number of distinct recipients across all messages processed for your selected time range and filters. ## Related docs - [Message statuses](/send-notifications/message-statuses) — how Knock tracks delivery and engagement for each message. - [Link and open tracking](/send-notifications/tracking) — capture the link clicks and email opens that feed engagement metrics. - [Guide analytics and observability](/in-app-ui/guides/analytics-and-observability) — per-guide metrics for in-product messaging. ## Testing workflows Learn more about how to test workflows you build in Knock. --- title: Testing workflows description: Learn more about how to test workflows you build in Knock. tags: ["test", "testing", "CI", "continuous integration"] section: Send notifications --- Once you've built your workflow, you'll want to test it to make sure it works as expected. Knock provides a number of tools to help you test your workflows. ## The workflow test runner You can use the Knock workflow test runner to test an end-to-end workflow and verify that it works as expected. To use the workflow test runner, navigate to the workflow you want to test and click "Run a test." You'll have options to select the workflow's recipient, actor, tenant, and input any data that you'd like to pass to the workflow. Two things to know about workflow test configuration: - The data field is populated using the workflow's schema as defined in your templates. You can click "Reset" at any time to reset the data field to the latest and greatest schema for your workflow. - The recipient and actor fields can contain either a [user](/concepts/users) or an [object](/concepts/objects). Use the toggle above the field to switch between these options. After clicking "Run test," you will see a confirmation and a link to see the log to review the output and what was sent. You can learn more about Knock logs and the debugger [here](/send-notifications/debugging-workflows). Note that [trigger frequency](/send-notifications/triggering-workflows/overview#controlling-workflow-trigger-frequency) is not enforced during test runs. Settings like "once per recipient" are bypassed — each time you run a test, the workflow will execute for the selected recipient regardless of your frequency setting. To validate trigger frequency as it behaves in production, use the [trigger API](/send-notifications/triggering-workflows/api). You can also use the workflow test runner to run a test payload for a [source event trigger](/integrations/sources/overview#workflow-triggers). If your workflow is triggered by an event, you will automatically see a JSON payload of the last received event that you can use to run a test. You can edit this payload or click "Fetch the latest event" to get the most recent from your source. ### Test run settings The test runner includes a few settings that control how the workflow executes during a test run: - **Sandbox mode.** When enabled, all messages sent from the workflow are forced to send in [sandbox mode](/integrations/overview#sandbox-mode). Knock generates and previews the messages without delivering them to the underlying providers, regardless of how sandbox mode is configured on each channel. This is useful for testing a workflow end-to-end without sending real notifications to your recipients. - **Skip delays.** When enabled, all [delay steps](/designing-workflows/delay-function) in the workflow are force skipped. The workflow runs straight through without waiting, which lets you verify the full execution path without waiting for delays to elapse. You can also apply both of these settings when triggering a workflow via the API using the `settings.sandbox_mode` and `settings.skip_delay` properties, which is useful when testing workflows programmatically. The workflow test runner uses the last saved version of the workflow, not the last committed version. This means that you don't need to commit workflow changes before testing them using the test runner. This is different than calling the workflow with the API, which will always use the last committed version of the workflow. } /> ## Testing workflows using the Knock CLI You can also generate workflow runs using the `workflow run` command from the Knock CLI. You can learn more in our [CLI reference](/cli#workflow-run). ## Debugging workflows Learn more about how to work with Knock's workflow debugger and API logs to easily debug your notification workflows. --- title: Debugging workflows description: Learn more about how to work with Knock's workflow debugger and API logs to easily debug your notification workflows. tags: ["debugger", "logs", "errors"] section: Send notifications --- Sometimes you'll encounter issues with a workflow run that require more visibility into the Knock engine. Knock comes pre-built with a powerful workflow debugger that you can use to understand the state of individual workflow runs. Using the workflow debugger, you can answer questions such as: - What messages did this workflow run generate? - Why did this step not produce any messages? - Did this recipient have preferences set that opted them out from receiving a notification? - What prevented this step from executing? You can see a video of our workflow debugger in action here:
## Accessing the workflow debugger See the{" "} data retention docs for more details on how Knock enforces this policy. } /> You can access the workflow debugger from the "Runs" tab on a given workflow or the "Workflow runs" tab on a recipient record. You can also access it under **Observability** > **Runs** in the dashboard sidebar, which lists every workflow run in the current environment. Filter that list if desired, then select a run to open it in the debugger. Finally, you can reach the debugger under **Observability** > **API logs** in the dashboard sidebar. From there: 1. Find an API log that triggered a workflow run (**hint**: you can use the filters to find only workflow API requests) 2. In the right hand panel, click the "Workflow runs" tab 3. Select a workflow run for any recipient to view the debugger ## Understanding workflow execution behavior When a workflow runs, each step is executed in sequence. Sometimes an individual step is skipped or encounters an error, and it's important to understand how this affects the workflow run as a whole. If you're reviewing a{" "} test run, keep in mind that{" "} trigger frequency {" "} is not enforced by the dashboard test runner. Settings like {'"'}once per recipient{'"'} are bypassed during test runs, so the workflow will execute for the selected recipient each time regardless of your frequency setting. This is expected behavior. To validate trigger frequency as it works in production, trigger the workflow using the{" "} API directly. } /> ### When a workflow step is skipped There are several controlled scenarios where a step is skipped and the workflow run continues to execute subsequent steps. Steps are skipped when: - **Step conditions are not met.** If a step has [conditions](/designing-workflows/step-conditions) configured and they evaluate to false, the step is skipped. - **Channel configuration is missing.** If the channel associated with a step has not been configured in the current environment, the step is skipped. - **Recipient preferences opt out.** If the recipient has set [preferences](/preferences/overview) that opt them out of this notification type or channel, the step is skipped. - **Recipient is missing required data.** If the recipient lacks the data required for delivery on this channel (for example, no `email` address for an email step, or no [channel data](/managing-recipients/setting-channel-data) for a push step), the step is skipped. - **A dynamic [batch](/designing-workflows/batch-function), [delay](/designing-workflows/delay-function), or [throttle](/designing-workflows/throttle-function) step encounters an invalid window value.** If a workflow step encounters a missing or invalid dynamic window value (like a timestamp in the past), the step is skipped. The workflow debugger will indicate when a step has been skipped and provide the reason why. This can help you understand why a recipient did not receive a notification on a particular channel, or why a workflow function was not processed as expected. ### When a workflow step fails When a workflow step encounters an error during execution (such as a template rendering error or an error response from a [fetch](/designing-workflows/fetch-function) step), Knock will retry the step **up to 3 total times**. If all retry attempts fail, the step is marked as failed and **the workflow run is halted.** No subsequent steps will execute. Common causes of workflow step failures include: - **Template rendering errors.** Invalid Liquid syntax or missing required variables that prevent the message template from rendering. - **Fetch step errors.** Errors or timeouts from the HTTP request made by the fetch step. You can identify failed steps in the workflow debugger by looking for error states on individual steps. The debugger will show you the error details to help diagnose and fix the issue. ## Debugging workflows with a batch step A [batch step](/designing-workflows/batch-function) aggregates many workflow runs into a single notification. The first trigger for a recipient opens the batch window. Every trigger that arrives while that window is open is added to the batch as an `activity`, and its own workflow run terminates at the batch step. When the window closes, the run that opened the batch continues to the next step. This means that a workflow with a batch step produces two types of runs: - **Runs that open a batch.** These runs pause at the batch step, then resume and execute the remaining steps when the window closes. Look here for any messages that were generated after the batch closed, along with any errors. - **Runs that join a batch.** These runs terminate at the batch step. Steps after the batch step show "Will not execute" with the log "This workflow run was added to the batch function of a pre-existing workflow run." See the instructions below for finding the run that opened the batch and any messages that it generated after the batch window closed. The batch step is what links runs together, and the workflow debugger surfaces that link in both directions. ### Finding the run that executed Open any workflow run and **select the batch step** in the debugger. What you see depends on which type of run you selected: | Label | Application | Description | | ------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Batched by** | Shown on a run that joined an open batch | Displays a link to the workflow run that started the batch. That run is where the rest of the workflow executes after the batch window closes. | | **Batch closes at** | Shown on a run that opened a batch, while its window is still open | Displays a timestamp indicating when the batch window will close. The run remains in a `Paused` status until the window closes. Subsequent runs that are added to the batch will not be listed here until the window closes and the batch is finalized. | | **Batched workflow runs** | Shown on a run that opened a batch, after its window has closed | Displays a list of up to ten of the runs collected into the batch, followed by the total number of runs in the batch (including the run that opened the batch; for example, "Showing 4 of 5 total batched workflow runs"). Follow any link to open a batched run in the debugger. | ### Batches that immediately flush the leading item When a batch step is configured to [immediately flush its leading item](/designing-workflows/batch-function#immediately-flushing-the-first-item-in-a-batch), the run that opens the batch continues past the batch step right away and is not included in the final batch. Additional batched triggers are aggregated on the _second_ run added to the batch, so that is the run where you will find the batched workflow runs list and any messages that the batch generated. # Recipients Learn more about how to manage notification recipients with Knock. ## Overview Learn more about managing recipients within Knock. --- title: Overview description: Learn more about managing recipients within Knock. section: Managing recipients --- [Recipients](/concepts/recipients) in Knock are the [users](/concepts/users) and [objects](/concepts/objects) that receive notifications. In this section, we walk you through managing recipient data for your environment, using both our API and Dashboard. you may think it's odd to think about Objects as a recipient of a notification. We use Objects as a way to model non-user recipients that you may need to send a notification to from your system. } /> #### Quick links - [Identifying recipients](/managing-recipients/identifying-recipients) - [Setting preferences](/preferences/custom-preference-center#set-user-preferences) - [Setting channel data](/managing-recipients/setting-channel-data) ## Recipients and environments Your recipient data exists **per environment**, meaning that each [environment](/concepts/environments) you have in your Knock account will contain a unique set of recipient data. If you have recipients that need to span across environments, then you should identify those recipients across each environment. ## Storing recipient data in Knock In Knock, we refer to the process of storing recipient data as "identifying" one or many recipients. Your recipient data must exist in Knock to send notifications to those recipients or to reference the recipient within a notification template. Identifying recipients is done programmatically via our REST API, either individually, in bulk, or inline when specifying a recipient in other calls. [Learn more about identifying recipients ->](/managing-recipients/identifying-recipients) ### Why store recipient data in Knock If you're used to sending notifications via single-channel APIs, the idea of storing recipient data in a messaging platform such as Knock may sound odd to you. Here are a few reasons why we store recipient data in Knock: - **Multi-channel notifications.** When you're using a single-channel API, you can pass through the recipient's email address or phone number when you trigger a message. In a multi-channel platform like Knock, that would mean passing through _all_ of a recipient's channel information every time you trigger a notification. By keeping a recipient model in Knock, you can update a recipient's channel information once, then reference them via their recipient ID from that point on. We take care of the rest. - **Stateful in-app notifications.** The Knock Feed API returns a stateful feed of the in-app notifications a given recipient has received from your product. The Knock recipient model is used to store a given recipient's notification feed and to give you a way to retrieve that feed via the recipient identifier you keep for them in your product. - **Preferences model.** The Knock recipient model enables advanced functionality such as preferences support, where you store a given recipient's notification preferences in Knock and we reference that recipient's preferences during the run of a given notification workflow. - **Leverage recipient traits in notification templates.** The Knock recipient model also enables you to store custom traits on a given recipient that you can reference in a notification template. This is helpful when you want to add conditional copy to a notification based on a recipient's role or plan. We do not take storing your recipient data lightly. You can learn more about our security posture and best practices on [our security page](/security). ## Fetching recipient data From the API you can retrieve information about the recipient data that you have stored inside of Knock. Recipient information is accessible from both the client-side and server-side using the appropriate [API keys](/developer-tools/api-keys). You can find more about retrieving recipients in the [API reference](/api-reference). ## Working with recipients in the dashboard The Knock dashboard also provides access to the Users and Objects that you have identified within each environment. From the dashboard you can: - Search for a specific recipient by id, name, or email - View the recipient, including any custom properties set - View recipient channel data - View and manage recipient preferences - View recent messages sent to the recipient - View recent workflow runs for the recipient - View any schedules for the recipient ## Identifying recipients Learn more about how to identify your user and object recipients to power your notifications. --- title: Identifying recipients description: Learn more about how to identify your user and object recipients to power your notifications. tags: [ "recipients", "inline identify", "identify", "bulk identify", "import users", "create users", "users", "objects", ] section: Managing recipients --- To send notifications to a recipient or reference them as an actor in a notification, their data must be stored in Knock. This process is called **identifying** and works across user and object recipients. Recipient properties enable Knock to deliver personalized notifications. Properties like `email` and `phone_number` are essential for message delivery, while `name` and custom properties power personalization in your notification content and workflow logic. Properties like `timezone` and `locale` enable you to design mindful notifications for users around the world via [send windows](/designing-workflows/send-windows) and [translations](/template-editor/translations). Knock also tracks [delivery and engagement analytics](/send-notifications/message-statuses) for each identified recipient, giving you insights into notification performance and user behavior. There are three ways to identify recipients: - **Direct identification**. Make a single server-side API request (`PUT /v1/users/{user_id}`) to upsert one recipient. - **Inline identification**. Upsert recipients by including recipient data in other server-side API requests (e.g., `POST /v1/workflows/{workflow_key}/trigger`) or when initializing the `KnockProvider` client-side. - **Bulk identification**. Make a single server-side API request (`POST /v1/users/bulk/identify`) to upsert many recipients at once. All identification methods in Knock use an upsert approach. This means any existing data for a recipient is merged during the upsert operation. } /> ## Direct identification Make a server-side API request to upsert a single recipient in Knock. This is useful to ensure that recipient data is updated on an ongoing basis, like reflecting updates to user information or changes to object properties in your system. [API reference ->](/api-reference/users/update) ## Inline identification You can also identify recipients during other operations, eliminating the need for separate identification API calls. When using inline identification, Knock guarantees that recipients are identified before executing any other action. This enables lazy recipient creation within Knock and ensures that your recipients exist within Knock before executing calls that reference them. Inline identification is available server-side for both users and objects, and client-side for users only. Inline identification requires a list of [recipient objects](/api-reference/recipients/schemas/recipient_request) where each object must include an `id`, but may include other properties to be upserted. Server-side, inline identification works with any endpoint that accepts a list of recipients: - [Workflow triggers](/api-reference/workflows/trigger) - [Schedules](/api-reference/schedules/create) - [Bulk schedules](/api-reference/schedules/bulk/create) - [Subscriptions](/api-reference/objects/add_subscriptions) - [Bulk subscriptions](/api-reference/objects/bulk/add_subscriptions) [API reference ->](/api-reference/workflows/trigger) Client-side identification is handled automatically when you initialize the `KnockProvider` with user data provided. When mounted, the provider will identify the user in Knock by upserting the provided user information. This approach is particularly useful for in-app UI components like [feeds](/in-app-ui/react/feed#rendering-the-component) and [guides](/in-app-ui/guides/render-guides#getting-started), where user data is immediately required in your frontend application. Note that client-side identification is currently only available for users. ```jsx title="Client-side identification with KnockProvider in React" import { KnockProvider } from "@knocklabs/react"; const YourAppLayout = ({ user }) => { return ( {/* Your app content with Knock guides or feed components */} ); }; ``` Client-side identification should only be used with your{" "} public API key and is best suited for use cases where you're displaying in-app notifications. For more sensitive operations or when you need to set extensive user properties, use server-side identification methods. } /> ## Bulk identification The bulk identification endpoint enables you to upsert many recipients in a single server-side request. This is ideal for initial data imports or large-scale updates. [API reference ->](/api-reference/users/bulk/identify) ## Setting recipient properties When identifying recipients, you pass a set of properties that are persisted. We recommend using your internal user identifier for the `id` value, which is the only required property. Although additional properties aren't required for identification, some properties such as `email` or `phone_number` are required for certain [channel steps](/designing-workflows/channel-step). ### Reserved properties Recipients have some reserved property names: | Property | Description | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | The name of the recipient. | | `email` | A valid email address to deliver email notifications to. | | `avatar` | A URL for the avatar of the recipient. | | `locale` | A locale code for the recipient, used for internationalizing content. | | `phone_number` | An E.164 compliant phone number field used when sending SMS messages. | | `timezone` | A valid tz database time zone string used for [send windows](/designing-workflows/send-windows) and [schedules](/concepts/schedules). | | `created_at` | An ISO-8601 datetime indicating when the recipient was created. **Note:** This is an optional property that you must explicitly set from your system - Knock does not handle this implicitly and it will be `null` unless provided. | ### Custom properties Recipients also accept any number of custom properties as key-value pairs that you define. Custom properties enable you to reference recipient attributes when sending notifications. When directly identifying your recipients, an update that would result in a total custom properties size exceeding this limit will result in a{" "} 422 response. } /> ## When to identify Knock provides a flexible set of APIs for you to manage your user data as you scale with us. Ultimately, it's up to you to decide upon the best approach for how you manage your user data with Knock. ### Initial setup When getting started with Knock, you'll likely have existing recipients to migrate into Knock: - **Quick start**. Use [inline identification](#inline-identification) to start calling your workflows without any prerequisite calls to the Knock API. - **Data import**. Use our [bulk identification](/api-reference/users/bulk/identify) to import all recipient data into Knock first. ### Ongoing updates After identifying your current recipients in Knock, you'll want to continue to update this data when: - New recipients sign up for your product. - Knock-relevant data (e.g., `name` or `email`) about a user changes. A common approach is making subsequent calls to Knock's user identify API following such events. Many customers do this via a deferred job in their backend systems. Another option is to offload these updates to your workflow trigger calls via inline identification. If you always send Knock the full set of data for your recipients [via workflow trigger calls](/api-reference/workflows/trigger), you can keep your user data up to date in Knock without any additional handling on your end. ## Frequently asked questions A common mistake when implementing inline identification is accidentally passing a list of user ID strings instead of user objects. Inline identification requires user objects (even if they only contain an `id` property), not ID strings. ```json title="Incorrectly passing ID strings" { "recipients": ["user-123", "user-456"] } ``` ```json title="Correctly passing user objects" { "recipients": [ { "id": "user-123" }, { "id": "user-456" } ] } ``` Yes, preferences can be set during identification, but it's important to understand the implications. Unlike our preferences endpoints, setting preferences during identification will perform a deep merge of the provided preferences into any existing preferences (just like any other properties stored on the recipient). Unless you intend to leverage this behavior for a specific use case, we generally recommend updating preferences directly via the preferences endpoints. To set preferences during identification, provide a dictionary under the `preferences` key with each key representing a preference set ID. Unlike the preferences [endpoints](/preferences/overview), setting preferences during identification requires explicitly providing the `default` preference set key when updating default preferences. ```json title="Providing preferences during identification" { "id": "user-123", "name": "John Doe", "preferences": { "default": { "channel_types": { "email": true, "sms": false } } } } ``` For workflow triggers with [inline identify](/send-notifications/triggering-workflows/api#identifying-recipients-inline), one or more preference sets (including [per-tenant preferences](/multi-tenancy/per-tenant-preferences)) can be upserted by passing a dictionary of [PreferenceSets](/preferences/overview). [API reference ->](/api-reference/workflows/trigger) Yes, channel data can be set during identification by passing a dictionary under the `channel_data` key, where each key represents the channel ID and the value contains the channel-specific data. ```json title="Setting channel data during identification" { "id": "user-123", "name": "John Doe", "channel_data": { "some-uuid-for-a-channel": { "tokens": ["my-push-token"] } } } ``` This is particularly useful for setting push notification tokens, device information, or other channel-specific configuration data when identifying users. ## Recipient schemas Curate the schemas Knock infers for your users, tenants, and objects. Hide properties, add labels and descriptions, and set preview text. --- title: Recipient schemas description: Curate the schemas Knock infers for your users, tenants, and objects. Hide properties, add labels and descriptions, and set preview text. section: Managing recipients --- Knock builds a schema for each type of recipient in your environment (your [users](/concepts/users), your [tenants](/multi-tenancy/overview), and each [object](/concepts/objects) collection) by inferring the properties, types, and preview text from the recipient data you send. Schema management gives you control over that schema. You can hide properties you don't want to surface, add human-readable labels and descriptions, and set preview text that is used when previewing templates. Curating a schema shapes how recipient properties appear across the Knock dashboard: in the template editor, in condition and segment builders, and in recipient tables. It also makes your data legible to the Knock agent, which reads your schema to understand what properties exist and what they mean. ## How recipient schemas work As you [identify recipients](/managing-recipients/identifying-recipients), Knock observes the properties on that data and records them on the schema for that recipient type. Each property carries: - **Key.** The property name, such as `plan` or `address.city`. - **Type.** The data type Knock inferred from your data, such as `string`, `number`, `boolean`, `object`, or `array`. Types are read-only. - **Source.** Whether Knock inferred the property from your data or it is a built-in system property, such as `email` on a user. This is read-only. - **Preview text.** A value Knock has seen for the property, used to render realistic previews of templates that reference it. Schemas are logically isolated per [environment](/version-control/environments), the same way recipient data is. A property that only appears in production will not show up on your development schema until Knock sees it there. Schema management applies to recipient data: users, tenants, and object collections. Source events, workflow trigger data, and broadcast data are not part of recipient schemas. } /> ## Curate a schema in the dashboard Go to **Settings** > **Schemas** and select a recipient type: users, tenants, or one of your object collections. You will see every property Knock has inferred for that type, along with its type, source, and preview text. ### Hide or show a property Toggle a property's visibility to control where Knock offers it. When you hide a property, Knock stops offering it across the dashboard: template variable suggestions, condition and audience segment builders, and recipient table columns no longer list it. Hiding a property controls where it is offered, not whether it exists. Anything that already references a hidden property keeps working, and the property is still returned by the API. Because nothing can break, you can hide properties to tidy up what your team sees. } /> ### Add a label and description Give a property a **label** to set a human-readable display name, and a **description** to explain what the property is for and how it should be used. Labels and descriptions help your team choose the right property, and they give the Knock agent context about your data. ### Set the preview text Set the **preview text** for a property to control how it renders in template previews. When you preview a template that references the property, Knock uses this value so the preview reflects realistic data. In the Management API and CLI, this is the property's `preview_text` field. ## Where curated schemas appear Once you curate a schema, it drives every place Knock surfaces recipient properties, including: - **Template editor.** Variable suggestions offer only visible properties, and previews use your preview text. - **Condition and segment builders.** Property pickers for [conditions](/designing-workflows/step-conditions) and [audiences](/concepts/audiences) list only visible properties. - **Recipient tables.** Column options reflect the curated schema. ## Permissions Access to schema management is tied to your [role](/manage-your-account/roles-and-permissions). | Capability | Roles | | --------------------------------------------------- | -------------------- | | View schemas | Member, Admin, Owner | | Set the preview text, or add a property | Member, Admin, Owner | | Hide or show a property, add a label or description | Admin, Owner | ## Environments and branches Schema configuration is scoped to an environment and applies immediately when saved, the same way [environment variables](/concepts/variables) do, rather than through commit and promote. Curate the schema in the environment whose data you want to manage. [Branch](/version-control/branches) environments inherit the schema and curation from their parent environment and are read-only for schema changes. Editing a schema from a branch is rejected with a pointer to the parent environment. Manage the schema from the parent environment instead. ## Sync schemas with the Management API You can read and update recipient schemas with the [Management API](/developer-tools/management-api). This is useful for version-controlling your schema configuration or for applying the same curation across environments. | Method and path | Description | | -------------------------------------- | ---------------------------------------------- | | `GET /v1/schemas` | List the schemas in an environment. | | `GET /v1/schemas/{item_type}` | Retrieve the schema for a recipient type. | | `PUT /v1/schemas/{item_type}` | Update the schema for a recipient type. | | `PUT /v1/schemas/{item_type}/validate` | Validate a schema payload without applying it. | `item_type` is `user`, `tenant`, or `object`. For object schemas, pass the collection with a `collection` query parameter. Each schema is a list of properties: ```json title="A user schema" { "item_type": "user", "item_id": null, "properties": [ { "key": "plan", "type": "string", "preview_text": "\"enterprise\"", "visible": true, "label": "Plan", "description": "The account's current billing plan." } ] } ``` The curatable fields on a property are: | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------------- | | `visible` | boolean | Whether the property is offered across the dashboard. Defaults to `true`. | | `label` | string | An optional human-readable display name. | | `description` | string | An optional description of the property. | | `preview_text` | string | The preview text, JSON-encoded, used to preview templates. | A property's `type` and `source` are inferred by Knock and are read-only. The same role-based permissions govern the Management API: any member can read schemas, set preview text, and add properties, while hiding or showing a property or setting a label or description requires an admin or owner. } /> ## Sync schemas with the CLI The [Knock CLI](/cli/overview) can pull schemas from an environment to local files and push local changes back, so you can keep your schema configuration in version control alongside your other Knock resources. ```bash title="Pull and push schemas" # Pull every schema in the environment into a local schemas directory knock schema pull --all # Push a single recipient schema to an environment knock schema push user --environment=production # Push an object collection schema knock schema push object --collection=projects --environment=production ``` Schemas are stored in a `schemas/` directory, one file per recipient type. See the [schema CLI reference](/cli/schema) for the full set of commands and flags. ## Limitations - Hidden properties are still returned by the API. Hiding affects where a property is offered in the dashboard, not API responses. - Property types are inferred and read-only. - Schemas cover recipients only: users, tenants, and objects. Source events, workflow trigger data, and broadcast data are not yet included. - Knock does not yet track where a property is used or validate incoming data against your schema. ## Setting channel data Learn about how to set channel data for your recipients and users to make it easy to connect recipients with push and chat channels. --- title: Setting channel data description: Learn about how to set channel data for your recipients and users to make it easy to connect recipients with push and chat channels. tags: ["channels", "slack", "push", "tokens", "recipients", "msteams", "teams"] section: Managing recipients --- Some channel integrations require user and channel-specific data to send notifications. Push channels like APNs (Apple Push Notification Service) and FCM (Firebase Cloud Messaging) are good examples, where both require that there are device-specific tokens that target the user in a push notification. Slack is another good example, where the channel data from a Slack integration in your product is stored on a Knock [object](/concepts/objects). At Knock we call this concept `ChannelData`. For most channels, `ChannelData` lives under a [user](/concepts/users) or an [object](/concepts/objects) and stores channel-specific data to be used when that user or object is included as a recipient on a [triggered workflow](/send-notifications/triggering-workflows). For chat providers that support tenant-stored credentials, `ChannelData` can also live on a [tenant](/multi-tenancy/overview) to provide shared auth while the recipient stores the destination. ## Things to know about channel data - For channel types that require channel data (such as [push](/integrations/push/overview) channels and [chat](/integrations/chat/overview) channels like Slack), the channel step will be skipped during a workflow run if the required `channel_data` is not stored on the recipient. - Knock stores channel data for you but makes no assumptions about whether the stored channel data is valid. That means that if a push token expires, it's your responsibility to omit/update that token for future notifications. - For push providers, Knock offers an opt-in [token deregistration](/integrations/push/token-deregistration) feature that automatically removes invalid tokens from a recipient's channel data when messages bounce. - Setting channel data always requires a `channel_id`, which can be obtained in the Dashboard under the **Channels and sources** page in your account settings. A channel ID is always a UUID v4. ## Setting channel data Before getting or setting channel data, you must first configure that channel in your environments. You can do this inside the Knock dashboard under the **Channels and sources** page in your account settings. Once the channel for which you want to store channel data has been created, you're ready to store the channel data for your users and objects. There are three ways of setting channel data for a given recipient: 1. Explicitly using the set channel data method 2. Inline through a workflow trigger 3. When identifying a recipient You can set channel data for a given user using the `users.setChannelData` method. Please note that the channel data will always be overwritten with each `set` call. If no user exists in the current environment for the given `user_id`, Knock will create the user entry as part of this request. In the example below, we're setting a user's device token when they download our mobile app so we can send them push notifications. If this token wasn't set for the user, they wouldn't receive push notifications from our notification workflows. To directly set user channel data from iOS, Android, or Flutter applications, see [iOS push notifications](/in-app-ui/ios/sdk/push-notifications), [Android push notifications](/in-app-ui/android/sdk/push-notifications), and [Flutter push registration](/in-app-ui/flutter/sdk/reference#registertokenforchannel). You can set channel data for a given object using the `objects.setChannelData` method. Please note that the channel data will always be overwritten with each `set` call. In the example below, we're setting an object's Slack channel ID and access token, presumably after a user in our product has decided to connect the object to their Slack workspace. This enables us to send Slack notifications to the connected Slack channel when an event is triggered within the scope of the object. You can learn more about objects in [the objects concept overview](/concepts/objects) and [API reference](/api-reference/objects). For both user and object recipients, channel data can be specified inline during a [workflow trigger call](/managing-recipients/identifying-recipients#inline-identification). When setting channel data inline for a recipient entity, you must supply the channel data as a dictionary containing the channel ID as a key, and a dictionary of channel data to set for that channel. For both [user](/api-reference/users/update) and [object](/api-reference/objects/set) recipients, channel data can be specified as a recipient property on an identify request. When setting channel data for a recipient entity on an identify request, you must supply the channel data as a dictionary containing the channel ID as a key, and a dictionary of channel data to set for that channel. The below example is for a `User`, but the same pattern can be followed for an `Object`. ## Getting channel data To retrieve the currently set channel data, you can use the `getChannelData` method on `users` and `objects`. If channel data is not set for the recipient you'll receive a `404` response. ## Clearing channel data Any previously set channel data can be cleared by issuing an `unsetChannelData` call. Unsetting channel data for a recipient requires a valid channel ID to be passed. For push providers, Knock can automatically remove invalid tokens from a recipient's channel data when messages bounce. Learn more about this opt-in feature in our{" "} token deregistration documentation . } /> ## Provider data requirements Channel data requirements for each channel type and provider are listed below. Typically `channel_data` comprises a `token` or other value that is used to uniquely identify a user's device. ### Push channels You can set push channel data by passing either: - A list of `tokens` (or `target_arns` for [Amazon SNS](/integrations/push/aws-sns)) strings - A list of `devices` objects for supported push providers. If set, this [device-level metadata](/integrations/push/device-metadata) will be used when evaluating [translations](/template-editor/translations) and [send windows](/designing-workflows/send-windows). #### The `PushDevice` object The `PushDevice` object is used to optionally set device-level metadata for a push channel. It contains the following properties: | Property | Type | Description | | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | token\* | `string` | The device token to send the push notification to. Required for providers other than Amazon SNS. | | target_arn\* | `string` | The ARN of a platform endpoint associated with a platform application and a device token. Required for Amazon SNS. | | locale | `string` | The [locale](/template-editor/translations#supported-locales) of the device. | | timezone | `string` | The timezone of the device. Must be a valid tz database time zone string. | #### Provider-specific requirements You must provide one of `tokens` or `devices`. | Property | Type | Description | | --------- | ---------------------------------------- | --------------------------- | | tokens\* | `string[]` | One or more device tokens. | | devices\* | [`PushDevice[]`](#the-pushdevice-object) | One or more device objects. | You must provide one of `tokens` or `devices`. | Property | Type | Description | | --------- | ---------------------------------------- | --------------------------- | | tokens\* | `string[]` | One or more device tokens. | | devices\* | [`PushDevice[]`](#the-pushdevice-object) | One or more device objects. | You must provide one of `tokens` or `devices`. | Property | Type | Description | | --------- | ---------------------------------------- | --------------------------- | | tokens\* | `string[]` | One or more device tokens. | | devices\* | [`PushDevice[]`](#the-pushdevice-object) | One or more device objects. | You must provide one of `target_arns` or `devices`. | Property | Type | Description | | ------------- | ---------------------------------------- | ------------------------------- | | target_arns\* | `string[]` | One or more device target ARNs. | | devices\* | [`PushDevice[]`](#the-pushdevice-object) | One or more device objects. | | Property | Type | Description | | ------------ | ---------- | ---------------------- | | player_ids\* | `string[]` | One or more player_ids | ### Chat app channels | Property | Type | Description | | ----------- | ------------------- | -------------------------------- | | connections | `SlackConnection[]` | One or more connections to Slack | A `SlackConnection` can have one of two schemas, depending on whether you're using standard Slack OAuth scopes or an incoming webhook. We cover Slack app scopes in detail in our [Slack scopes documentation](/in-app-ui/react/slack-kit). If you're using standard Slack OAuth with access token scopes, your `SlackConnection` schema looks like this. You'll use either a `channel_id` or `user_id` depending on whether you're storing connection data to message a channel or user in Slack: | Property | Type | Description | | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | access_token | `string` | A bot access token. Not required when the token is [stored on a tenant](/integrations/chat/slack/overview#tenant-channel-data-requirements). | | channel_id | `string` | A Slack channel ID | | user_id | `string` | A Slack user ID | If you're using a Slack app with the `incoming-webhook` scope your `SlackConnection` schema is quite simple: | Property | Type | Description | | -------------------- | -------- | --------------------------------------------------------------------------- | | incoming_webhook.url | `string` | The Slack incoming webhook URL (to be used instead of the properties above) | | Property | Type | Description | | ----------- | --------------------- | ---------------------------------- | | connections | `DiscordConnection[]` | One or more connections to Discord | A `DiscordConnection` has the following schema: | Property | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------- | | channel_id | `string` | A Discord channel ID | | incoming_webhook.url | `string` | The Discord incoming webhook URL (to be used instead of the properties above) | | Property | Type | Description | | ----------- | --------------------- | ---------------------------------- | | connections | `MsTeamsConnection[]` | One or more connections to MsTeams | An `MsTeamsConnection` can have one of two schemas, depending on whether you're using a Microsoft Teams bot or an incoming webhook. If you're using a Microsoft Teams bot, your `MsTeamsConnection` schema looks like this. You'll use either `ms_teams_channel_id` or `ms_teams_user_id` depending on whether you're storing connection data to message a channel or user in Microsoft Teams: | Property | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | ms_teams_tenant_id | `string` | A Microsoft Entra tenant ID. Not required when stored on a [tenant](/integrations/chat/microsoft-teams/overview#tenant-channel-data-requirements). | | ms_teams_team_id | `string` | A Microsoft Teams team ID | | ms_teams_channel_id | `string` | A Microsoft Teams channel ID | | ms_teams_user_id | `string` | A Microsoft Teams user ID | If you're using an incoming webhook, your `MsTeamsConnection` schema is quite simple: | Property | Type | Description | | -------------------- | -------- | --------------------------------- | | incoming_webhook.url | `string` | The Microsoft Teams incoming webhook URL (to be used instead of the properties above) | ## Deleting users Learn more about how user deletions work within Knock and how deletion can help with data privacy controls. --- title: Deleting users description: Learn more about how user deletions work within Knock and how deletion can help with data privacy controls. tags: ["right to be forgotten", "gdpr", "rtbf", "forgotten"] section: Managing recipients --- Knock provides a programmatic user deletion API that you can use to hard delete users and the data associated with users. This can be especially useful in fulfilling GDPR right-to-be-forgotten requests. deleting users is a destructive operation and once deleted, the data for that user cannot be recovered. } /> ## What data is deleted for a user? When a user is deleted in Knock, we will **hard delete**: - All system and custom properties set on the user - All preferences associated with the user - All channel data associated with the user - All subscriptions associated with the user - All schedules associated with the user - All messages sent to that user, including the content of those messages and any debugging events associated - All activity associated with a user, including the workflow run logs the user was a recipient of **Note**: deletion requests can take up to 10 minutes to process. During this time, you may still see some data associated with the user. ## Frequently asked questions Yes, you can manually delete individual users from the Knock dashboard. Yes, Knock is GDPR compliant. Absolutely. You can use our single-user deletion endpoint, or batch up deletion requests per-day using our bulk delete endpoint. Please get in touch with us if you have this requirement. You can re-add the same user using the previous user's identifier. The new user will not inherit any of the past user's data, given that will have been deleted. ## Merging users Learn more about merging user data. --- title: Merging users description: Learn more about merging user data. section: Managing recipients --- You might run into the scenario where you've identified an invited user to send them a notification and then that user "graduates" to a fully-fledged user after they sign up, leaving you with two users in Knock. That's where the merge users method comes in handy. Merging two users will merge the `secondary` user (the invited user in our example) into the `primary` user (the signed-up user), and the secondary user will be deleted in the process. performing a merge is a destructive operation and cannot be undone. } /> ## What's merged? - **Properties**: Properties are deep merged, but if there are any conflicts between the secondary and primary user then the value is selected from the primary user. - **Preferences**: Preference sets are shallow merged between the users. Any preference sets that don't exist on the primary from the secondary are added. - **Channel data**: Channel data is shallow merged from the secondary to the primary. Any channel data that doesn't exist on the primary from the secondary is added, determined by the channel_id. - **Message history**: The past 30 days of message history of the secondary user will now be owned by the primary recipient. - **Activities**: Any activities from the past 30 days that the secondary user was an `actor` or `recipient` of will be transferred to the primary user. If you need to retain more than 30 days worth of history, please contact us. ## Frequently asked questions Merging does not transfer in-flight workflow or broadcast runs to the primary user. If a run for the secondary user is paused at a delay, batch, or wait-for-event step, it remains associated with that user. When the run resumes, it is stopped before sending a notification because the secondary user has been deleted. Knock does not redirect the run to the primary user. To make sure an in-flight notification is still delivered, wait for the workflow or broadcast to complete before merging the users. # Multi-tenancy Learn how to use Knock's multi-tenancy features to power per-tenant notification experiences. ## Overview Learn how to map your multi-tenant application into Knock using tenants. --- title: Tenants description: Learn how to map your multi-tenant application into Knock using tenants. tags: ["tenant", "tenancy", "saas", "how knock works", "custom brand", "branding"] section: Multi-tenancy --- Tenants represent segments your users belong to. You might call these "accounts," "organizations," "workspaces," or similar. This is a common pattern in many SaaS applications: users have a single login joined to multiple tenants to represent their membership within each. You use tenants in Knock to: - [Scope in-app feeds](/multi-tenancy/tenant-scoped-messaging) so users only see notifications relevant to their active workspace. - Apply [per-tenant branding](/multi-tenancy/per-tenant-branding) in emails. - Define [per-tenant preference defaults](/multi-tenancy/per-tenant-preferences#create-a-per-tenant-default-preferenceset) that apply to all users within that tenant and [per-user, per-tenant preferences](/multi-tenancy/per-tenant-preferences#set-a-per-tenant-user-preferenceset). - Apply [per-tenant translations](/multi-tenancy/per-tenant-translations). - Hold shared channel credentials as [channel data](/managing-recipients/setting-channel-data), so one integration connection can serve recipients and objects in the tenant. ## How tenants work Each tenant is uniquely identified by an `id` [per-environment](/concepts/variables) — in most cases the same ID you use in your own system. Tenants can store any number of custom properties alongside branding overrides and preference defaults, and are fully manageable via the API. Behind the scenes, a tenant is a system-level [Object](/concepts/objects) in a special collection called `$tenants`, which means anything you can do with an object you can do with a tenant. By default, Knock creates a stub tenant object for any tenant ID you pass when triggering a workflow. You can also use the [tenant APIs](/api-reference/tenants) to create and manage tenants explicitly. ## Creating tenants Use the [tenant API methods](/api-reference/tenants) to create or update a tenant, including any custom properties and settings. ### Required attributes | Property | Description | | -------- | ---------------------------------------- | | `id` | A string to uniquely identify the tenant | ### Optional attributes | Property | Description | | ---------- | ----------------------------------------------------- | | `name` | An optional name to associate with the tenant | | `*` | Any custom properties you wish to store on the tenant | | `settings` | A `TenantSettings` object to apply (see below) | ### `TenantSettings` | Property | Description | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `branding.logo_url` | A fully qualified URL for an image to use as the logo of this tenant | | `branding.icon_url` | A fully qualified URL for an image to use as the icon of this tenant | | `branding.dark_logo_url` | A fully qualified URL for the logo to use in dark mode. Defaults to `logo_url` if not set | | `branding.dark_icon_url` | A fully qualified URL for the icon to use in dark mode. Defaults to `icon_url` if not set | | `branding.primary_color` | A hex value for the primary color. Defaults to `#000000` | | `branding.primary_color_contrast` | A hex value for the contrasting color to use with the primary color. Defaults to `#FFFFFF` | | `branding.dark_primary_color` | A hex value for the primary color in dark mode. Defaults to `#FFFFFF` | | `branding.dark_primary_color_contrast` | A hex value for the contrasting color in dark mode. Defaults to `#000000` | | `preference_set` | A complete `PreferenceSet` to use as a default for all recipients with workflows triggered for this tenant | In Knock's Python SDK v1.0+, the optional name property and any custom properties provided must be passed in via the{" "} extra_body parameter. See the{" "} Python SDK documentation {" "} for details. } /> ## Tenant-scoped workflows Tenants **do not** have a relationship to the [users](/concepts/users) and [objects](/concepts/objects) you've identified in Knock — Knock does not know which tenant to associate with a given set of users. Instead, you must explicitly pass a tenant when triggering a workflow. Tenants have a loose coupling to your users so Knock does not need to know anything about the roles and permissions model associated with your product. This means you have less data to synchronize to Knock and reduces the risk of drift between what's current in your system and what's reflected in Knock. If you need to model groups or lists of users, you can use our [subscriptions model](/concepts/subscriptions) to do that. Once a workflow run has been triggered with a `tenant`, the Knock workflow engine will: - Find the tenant or create an empty `tenant` object if one does not exist. - Expose the tenant object to the workflow run scope as a `tenant` variable. - Tag all messages produced in the workflow run with the tenant `id`. - Apply any branding overrides to templates rendered. - Apply any preference defaults to the recipient's preference set. - Fetch any recipient-specific tenant preference sets. Tagging messages by tenant makes it possible to query for tenant-specific messages in both the API and the dashboard, and to scope in-app feeds to a specific tenant. ## Tenant data in templates The full tenant object, including any custom properties, is available in your templates under the `tenant` [namespace](/template-editor/variables#tenant). ```markdown title="Using tenant data in a notification template" # Hello from {{ tenant.name }} This is a message directly from {{ tenant.name }} going to {{ recipient.email }}. ``` You can also use the tenant in step conditions to only trigger steps for particular tenants. ## Per-tenant preferences Per-tenant user preferences and tenant preference defaults are only available on our{" "} Enterprise plan . } /> You can manage different sets of preferences for each user-tenant pair. A user may have different preferences configured for "Acme Fish Co." than they do for "Bell's Bagels," two workspaces within the same product. You can also set per-tenant defaults, where an admin in a tenant can set the default preferences for all users within that tenant. You can learn more in [our per-tenant preferences documentation](/multi-tenancy/per-tenant-preferences). ## Frequently asked questions You can find tenant information on the **Tenants** page under the **Recipients** section in the main sidebar. From there you can view custom properties, message logs, workflow run history, branding settings, and default preferences for any tenant. There are no limits associated with tenants. Yes, you can still use our APIs to work with tenant data, and trigger workflow runs for specific tenants. However, per-tenant preferences and custom branding are features gated for enterprise plans only. Knock does not know anything about the mapping between your users and your tenant entities, meaning you do not need to map user permissions. Absolutely, you can use a tenant as a `recipient` or `actor` in a workflow trigger by referencing it as an object with the structure `{ collection: "$tenants", id: "tenant-id" }`. Yes, you can subscribe recipients to a tenant by setting the collection of the object to subscribe to as `$tenants` and using the `id` of the tenant as the object id. Per-tenant template overrides at the workflow step level are not yet supported. As an alternative, [per-tenant translations](/multi-tenancy/per-tenant-translations) enables you to customize template content on a per-tenant basis, even if you're not working with multiple locales. If this is blocking your adoption of Knock, [please get in touch](mailto:support@knock.app?subject=Per-tenant%20templates). While it's technically possible to create per-tenant workflows in Knock, we recommend not doing this where possible and opting to use our step conditions, preferences, and per-tenant templates to provide the customizations you need. The reason is creating and managing per-tenant workflows increases the surface area of the number of notifications you need to support, and more commonly what we've found from working with customers is there are more similarities between per-customer workflows than differences, which can usually be encapsulated in our workflow model. If you find that you have different needs here, we'd love to speak with you. Please [get in touch](mailto:support@knock.app) and we can arrange a consultation with a notification support specialist on the Knock team to walk through your use case. No, today it's only possible to have a single-level of hierarchy for your tenants. If you need to apply deeper hierarchy to your tenant objects, please [get in touch](mailto:support@knock.app) and we can discuss your use case further. ## Tenant scoping Learn how to use tenants to scope in-app feeds so users only see notifications relevant to their active workspace. --- title: Tenant scoping for in-app messaging description: Learn how to use tenants to scope in-app feeds so users only see notifications relevant to their active workspace. tags: ["tenant", "tenancy", "in-app feed", "multi-tenancy", "scoping", "workspace"] section: Multi-tenancy --- When your users belong to multiple workspaces or organizations, you'll want their [in-app feed](/in-app-ui/feeds/overview) to only show notifications relevant to their currently active workspace. You can do this by associating workflow runs with a tenant and passing that tenant when initializing the feed. ## How it works Pass a `tenant` identifier in your workflow trigger call. The tenant does not need to be configured beforehand — any unique string you use to identify the workspace will work. When retrieving the feed, pass the same tenant identifier so Knock scopes the feed to only show messages associated with that tenant: ```jsx title="Scope the feed client to a tenant" // If you're using our `client-js` SDK: import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); const feedClient = knockClient.feeds.initialize( process.env.KNOCK_FEED_CHANNEL_ID, { // Scope all requests to the current workspace tenant: currentWorkspace.id, }, ); // Or if you're using the React SDK: ... ; ``` Knock will ensure that badge counts are scoped to the active workspace, and that real-time notifications are not received for messages belonging to other tenants. ## Example Imagine a SaaS application, Collaborato, where users belong to one or more workspaces. When a user is active in the "Acme Fish Co." workspace, they should only see notifications relevant to that workspace. By passing `tenant: "acme-fish-co"` in both the workflow trigger and the feed initialization, Knock will filter all feed requests and real-time events to that tenant — even if the same user has notifications from other workspaces. ## Per-tenant branding Learn how to use tenants to apply custom branding to email notifications on a per-tenant basis. --- title: Per-tenant branding description: Learn how to use tenants to apply custom branding to email notifications on a per-tenant basis. tags: [ "tenant", "tenancy", "branding", "email", "multi-tenancy", "white-label", "custom brand", ] section: Multi-tenancy --- Per-tenant branding enables you to override your account-level email branding settings for individual tenants. When you trigger a workflow with a `tenant`, Knock uses any branding settings defined on that tenant in place of your account defaults. Per-tenant branding is only available on our{" "} Enterprise plan . } /> ## Branding settings Branding settings are stored on the tenant's `settings` object. You can set them via the [tenant API](/api-reference/tenants) or directly in the dashboard. | Property | Description | | --------------------------------- | -------------------------------------------------------------------- | | `branding.primary_color` | A hex value for the primary color | | `branding.primary_color_contrast` | A hex value for the contrasting color to use with the primary color | | `branding.logo_url` | A fully qualified URL for an image to use as the logo of this tenant | | `branding.icon_url` | A fully qualified URL for an image to use as the icon of this tenant | ## Setting branding via the API Pass branding settings in the `settings` object when creating or updating a tenant: ## Setting branding in the dashboard Navigate to the **Tenants** page under **Recipients** in the dashboard sidebar. Click into a tenant, then select the **Branding** tab to upload a logo, icon, and set primary colors directly from the interface. ## Using per-tenant branding Once branding settings are configured on a tenant, pass the tenant `id` when triggering a workflow with an email step. Knock will automatically apply the tenant's branding to the email layout for that workflow run. If no branding settings are defined on the tenant, Knock falls back to your account-level branding defaults. ## Example Let's say you're a hospitality company with two boutique hotels — "The Black Lodge" and "The Great Northern" — each with its own brand identity for reservation update emails. Create each hotel as a tenant and configure their branding in the dashboard or via the API. When triggering a reservation reminder, pass the relevant tenant `id` (e.g. `black-lodge`) and Knock will style the email with that tenant's logo and colors instead of your account defaults. ## Per-tenant translations Learn how to create tenant-scoped translation files that override your base translations for specific tenants. --- title: Per-tenant translations description: Learn how to create tenant-scoped translation files that override your base translations for specific tenants. tags: [ "tenant", "translations", "i18n", "localization", "l10n", "multi-tenancy", "per-tenant", ] section: Multi-tenancy --- Per-tenant translations are only available on our{" "} Enterprise plan . } /> Per-tenant translations enable you to override specific translation keys for individual tenants. When Knock renders a notification for a workflow run associated with a tenant, the tenant's translation content are deep-merged on top of your base translation files. The tenant-specific translations take precedence while everything else falls back to your base translations. ## How it works When a notification is rendered, Knock will [translate](/template-editor/translations) the content if a `t` tag or `t` filter is found, following the [locale prioritization chain](/template-editor/translations#locale-prioritization) to identify the appropriate translation file to use. When a notification is scoped to a tenant, that flow prioritizes tenant-specific translations over base translations. Keys not present in the tenant-scoped file fall back to the base translation. Given a tenant-scoped translation for `acme-corp`: ```json title="Tenant-scoped en translation for acme-corp" { "greeting": "Greetings from Acme Corp!" } ``` And a base `en` translation: ```json title="Base en translation" { "greeting": "Hello from Knock", "order_ready": "Your order is ready." } ``` When a notification is sent for a workflow run associated with the `acme-corp` tenant, the resolved translations are: ```json title="Resolved translations for acme-corp" { "greeting": "Greetings from Acme Corp!", "order_ready": "Your order is ready." } ``` The `greeting` key is overridden by the tenant-scoped file; `order_ready` falls back to the base translation. ## Tenant-scoped translations Tenant-scoped translations are managed via the [Management API](/developer-tools/management-api) using the existing translations endpoints. Add the `tenant` query parameter to scope a translation to a specific tenant. ### Create translations ```http title="Create a tenant-scoped translation" PUT /v1/translations/:locale_code?tenant= ``` The request body is the same as a standard [translation upsert](/mapi-reference/translations/upsert): ```json title="Request body" { "translation": { "content": "{\"greeting\": \"Greetings from Acme Corp!\"}", "format": "json" } } ``` ### Get translations #### List all translations for a tenant Use the `tenant` query parameter to filter the translations list to a specific tenant: ```http title="List translations for a tenant" GET /v1/translations?tenant= ``` #### Get a specific translation for a tenant Use the `tenant` query parameter and the `locale_code` path parameter to get a specific translation set: ```http title="Get a tenant-scoped translation" GET /v1/translations/:locale_code?tenant= ``` ## Translations in templates In your templates, use the `t` filters or `t` tags to indicate content blocks that should be translated to tenant-specific copy. Knock will resolve the appropriate translation based on the tenant provided in the workflow trigger and, if applicable, the recipient's locale. ```liquid title="Using the t filter" {{ "greeting" | t | default: "Hello" }} ``` ```liquid title="Using the t tag" {% t %}Hello from Knock{% endt %} ``` When these templates are rendered for a workflow run associated with the `acme-corp` tenant, Knock uses the tenant-scoped value if one exists for the recipient's locale. ```markdown title="Resulting copy for acme-corp" Greetings from Acme Corp! ``` See [translations](/template-editor/translations) for more information on using translations in templates. ## Frequently asked questions Yes. By default, the Management API enforces environment restrictions on translation upserts. To write directly to production, pass `force=true` as a query parameter. This bypasses the development-only environment check and origin environment checks, allowing you to upsert tenant-scoped translations directly to any environment. Yes. You can manage tenant-scoped translations from the **Translations** page in the Knock dashboard. Filter the translations list by tenant using the tenant filter, click into a translation to view its tenant scope, and edit and save changes directly from the detail view. Yes, per-tenant translations can be used to customize templates on a per-tenant basis, even if you're not working with multiple locales. Set all of your customizable content as translation keys in your templates, then upsert tenant-scoped translation files that override those keys for each tenant. This gives you per-tenant control over notification content without requiring separate templates or workflows per tenant. ## Per-tenant preferences Learn how to enable your customer admins to set default preferences for users in their tenant. --- title: Per-tenant preferences description: Learn how to enable your customer admins to set default preferences for users in their tenant. tags: [ "tenant", "tenant preferences", "per-tenant preferences", "per tenant preferences", "preferences", ] section: Multi-tenancy --- Per-tenant user preferences and tenant preference defaults are only available on our{" "} Enterprise plan . } /> You can use tenant preferences to enable your customers' admins to create a tenant-specific default `PreferenceSet` for users in their tenant. If you're a B2B application or a multi-tenant SaaS product, you can use tenant preferences to allow your customers to set default preferences for their users. For example, in Slack, your notification preferences are set _per Slack workspace_ (that is, per-tenant), not as global preferences that apply across all of your Slack workspaces. This documentation assumes you know about tenants in Knock and what they do. If you're new to tenants, we recommend familiarizing yourself with the concept of tenants before continuing. } /> ## Overview Here is how tenant preferences work and the steps you'll take to implement them: 1. Enable your tenant admins to create a default `PreferenceSet` for their tenant. 2. Enable your users to override that tenant default `PreferenceSet` with their own preferences. 3. Trigger your workflows with a `tenant` parameter to apply tenant-specific preferences. ## Create a per-tenant default `PreferenceSet` You set the default `PreferenceSet` for a tenant via the API by calling the `tenants.set` [method](/api-reference/tenants/set). The preferences should follow the format of a recipient PreferenceSetRequest. ```javascript title="Set the default preferences for a tenant" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const preferences = { workflows: { "new-comment": { channel_types: { email: false, sms: true, chat: false, }, channels: { "550e8400-e29b-41d4-a716-446655440000": true, "6ba7b810-9dad-11d1-80b4-00c04fd430c8": false, }, }, }, }; await knock.tenants.set("tenant-id", { settings: { preference_set: preferences, }, }); ``` ### Updating the per-tenant default `PreferenceSet` You can update the per-tenant default `PreferenceSet` by calling the `tenants.set` method with the same `tenant_id` and the updated preferences that you'd like to set. The default behavior for this action is to merge the new preferences with any existing preferences. This means that any existing preferences will only have their values updated but not removed. See the [frequently asked questions](#frequently-asked-questions) section below for more information on how you can use a "replace" persistence strategy to overwrite existing preferences instead, or how you can remove a tenant's default preference set entirely. ## Set a per-tenant user `PreferenceSet` A `PreferenceSet` has an `id`. When you [set a given user's preferences](/api-reference/users/set_preferences) in Knock, you'll use the `default` ID to apply the preferences universally for the user. When using one of our [SDKs](/developer-tools/sdks), the `default` preference set is used if you don't provide an `id`. You'll encounter "default" in a few places in the Knock preferences model:
  1. At the environment-level when you set your default{" "} PreferenceSet for all users.
  2. At the tenant-level when you set the default{" "} PreferenceSet for all users in a tenant.
  3. At the user-level when you set a user's PreferenceSet{" "} without providing a tenant ID.
} /> To set per-tenant preferences for a recipient, you'll use the ID of the tenant that they should be associated with as the `PreferenceSet`'s ID. The examples below use the server-side Node SDK with a secret API key. To manage a user's own per-tenant preferences from your frontend, use a public API key with a signed user token and pass the tenant ID as the `preferenceSet`. See [Custom preference center](/preferences/custom-preference-center) for details. ```javascript title="Set tenant preferences for a user" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.users.setPreferences( "user-id", { channel_types: { email: true, sms: false, chat: true, }, channels: { "f47ac10b-58cc-4372-a567-0e02b2c3d479": false, "123e4567-e89b-12d3-a456-426614174000": true, }, }, { preferenceSet: "tenant-id", }, ); ``` You can also get a user's tenant-specific preferences. ```javascript title="Get tenant preferences for a user" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const preferences = await knock.users.getPreferences("user-id", { preferenceSet: "tenant-id", }); ``` ## Trigger per-tenant workflows When you trigger a workflow run, you pass a `tenant` parameter to tell Knock which tenant in your application the workflow is executing for. ```javascript title="Trigger a workflow with a tenant" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.workflows.trigger("workflow-name", { tenant: "spotify", }); ``` The Knock workflow engine uses that `tenant` parameter to evaluate the user's `PreferenceSet`. If the user has a tenant-specific preference set, Knock uses that to determine whether to send the notification. If the user does not have a `tenant`-specific preference set, Knock uses the tenant's default preference set. ## Tenant preference evaluation rules Here are a few things to keep in mind when using tenant preferences. You can learn more about how preferences are merged and evaluated [here](/preferences/overview#tenant-specific-preference-merge-hierarchy). - When executing a workflow trigger, passing in a `tenant` will automatically load that tenant's default `PreferenceSet` (if one exists) for all recipients of the workflow. These tenant-level defaults will override a recipient's own `default` preferences. - If the recipient has any per-tenant preferences set for that `tenant.id`, they will take precedence over the tenant-level default preferences. For more information on how to override a recipient's per-tenant preferences to respect the tenant-level default preferences, see the [frequently asked questions](#frequently-asked-questions) below. - If there is no default `PreferenceSet` on the tenant AND the recipient has no per-tenant preferences set, the recipient's `default` preferences will be used. As always, the recipient's `default` preferences are [merged](/preferences/overview#tenant-specific-preference-merge-hierarchy) with the environment-level preference defaults. ## Frequently asked questions Yes. You can do this by setting the `__persistence_strategy__` key on your `PreferenceSet` to `"replace"` when calling the `tenants.set` method. This will overwrite any existing preferences with the new preferences provided. ```javascript title="Replacing a per-tenant default preference set using the Node SDK" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const preferences = { __persistence_strategy__: "replace", workflows: { "new-comment": { channel_types: { email: false, sms: true, chat: false, }, channels: { "550e8400-e29b-41d4-a716-446655440000": true, "6ba7b810-9dad-11d1-80b4-00c04fd430c8": false, }, }, }, }; await knock.tenants.set("tenant-id", { settings: { preference_set: preferences, }, }); ``` To remove a tenant's default `PreferenceSet` entirely, call the `tenants.set` method with `settings.preference_set` set to `null`. This clears the tenant's default preferences. ```javascript title="Remove a tenant's default preference set using the Node SDK" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.tenants.set("tenant-id", { settings: { preference_set: null, }, }); ``` By default, a recipient's individual preferences will always take the highest precedence in the merge when preferences are evaluated (according to the hierarchy outlined [here](/preferences/overview#merging-preferences)). Some applications have use cases where it's necessary for a preference that is set at the tenant level to override a user's individual preference. For example, you may want to allow a team admin to disable a certain type of notification for their entire team, regardless of whether an individual user has opted in to that notification. To achieve this, you can set the `__strategy__` key on the preference you'd like to override to a value of `"replace"`. In the following example, although the user has specifically opted into receiving email notifications for the `collaboration` category, email notifications with that category will not send because the `tenant`'s default preference set has set the `collaboration` category to `false`. Consider the following preference sets: ```json title="A user's tenant-specific preference set" { "categories": { "collaboration": { "channel_types": { "email": true } }, "project-updates": { "channel_types": { "email": true } } } } ``` ```json title="The tenant's default preference set" { "categories": { "collaboration": { "__strategy__": "replace", "channel_types": { "email": false } }, "project-updates": { "channel_types": { "email": false } } } } ```
The merge during preference evaluation will look like this: {/* Note: The original image can be found in Figma here: https://www.figma.com/board/LlFCmkX4jgGDRcfJuTqkhl/Preferences-diagrams */} A visual of the tenant's default preferences merging into the recipient's tenant-specific preferences with the replace strategy applied
The recipient's preference for the `project-updates` category is preserved in the merge, but their preference for `collaboration` notifications is overridden by the tenant's preference due to the `replace` strategy. This `replace` strategy will also be reflected when you request the user's preferences via API. Although the user's tenant-specific preferences have explicitly been set to the above, the `__strategy__` key will be present on the `PreferenceSet` returned, indicating that their preference has been overridden by the tenant's: ```json title="The returned user preference set" { "categories": { "collaboration": { "__strategy__": "replace", "channel_types": { "email": false } }, "project-updates": { "channel_types": { "email": true } } } } ```
You can also use the `replace` strategy to override top-level `channel_types` or `channels` preferences: ```json title="Tenant default preference set with top-level channel_types override" { "channel_types": { "__strategy__": "replace", "email": false, "sms": true } } ```
Here are some important things to keep in mind when using the `replace` strategy: - This strategy will only apply to the specific preference(s) where it's included; there's no way to override the entire `PreferenceSet`. - When the preference with the `replace` strategy is removed, any existing recipient preferences that were overridden by the `replace` strategy will be immediately restored to their original values. For the example above, the user will once again be opted in to `collaboration` notifications. #### Removing the `replace` strategy Setting preferences on a tenant has [the default behavior](#updating-the-per-tenant-default-preferenceset) of merging the new preferences with any existing preferences. This means that once a preference has been set with the `replace` strategy, simply omitting the `__strategy__` from your next request will not remove it. There are two ways to remove it from the preference set: - Explicitly set the `__strategy__` key to `"merge"` for the preference(s) that should no longer override the user's preferences. This is the default behavior (a user's tenant-specific preferences will take precedence over the tenant's own default). - Use the `__persistence_strategy__` key on your request to replace the entire preference set rather than merging your new preferences with the existing ones. See the FAQ above for more information.
A `commercial_subscribed` value on a recipient's `default` preference set always takes precedence over tenant and environment defaults. However, in a multi-tenant application you may want to give tenant admins a switch that turns off [commercial messages](/preferences/commercial-unsubscribe) for everyone in their tenant, even for users who are individually opted in. You can model this by storing a user's opt-in as a [condition](/preferences/preference-conditions) on their `commercial_subscribed` preference that references the tenant's setting at send time. First, set the tenant's `commercial_subscribed` preference with a boolean value according to the admin's setting: ```javascript title="Opt a tenant out of receiving commercial messages using the Node SDK" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.tenants.set("tenant-id", { settings: { preference_set: { commercial_subscribed: false, }, }, }); ``` Then, a recipient's `commercial_subscribed` opt-in can be stored as a condition that dynamically references a tenant's setting, rather than as an explicit `true` setting: ```json title="A recipient preference set that defers to the tenant" { "id": "default", "commercial_subscribed": { "conditions": [ { "variable": "tenant.settings.preferences.commercial_subscribed", "operator": "not_equal_to", "argument": "false" } ] } } ``` When a commercial workflow runs for this recipient, Knock evaluates the condition at send time. If a `tenant` is applied to the workflow trigger and its `commercial_subscribed` preference is set to `false`, the user will not receive the message. If there is no `tenant` applied, or the tenant's `commercial_subscribed` preference is set to anything other than `false`, the user will receive the message. To comply with CAN-SPAM regulations, you should never use preference conditions to opt a recipient in to commercial messages under a given tenant when they have explicitly opted out under their own settings. Always set a false value on a user's preferences when they opt out of commercial messages. } />
# Preferences Learn how to power notification preferences with Knock. ## Overview Learn how to implement notification preferences in Knock. --- title: "Preferences overview" description: "Learn how to implement notification preferences in Knock." tags: ["recipients", "conditions", "prefs", "preferences"] section: Preferences --- [Preferences](/api-reference/recipients/preferences) enable your users to opt-out of the notifications you send using Knock. ## How preferences work A user has a `PreferenceSet`. A `PreferenceSet` is a JSON object that tells Knock which channels, categories, and/or workflows a user has opted out of receiving. When Knock runs a workflow for a user, we evaluate their `PreferenceSet`. A message will not send if the user has opted out of receiving it. A preference set is built using four keys: `categories`, `channels`, `channel_types`, `workflows`. These keys resolve to boolean values to determine if a user has opted out of receiving a notification. A recipient's `default` preference set also has a `commercial_subscribed` key which determines if the recipient should receive notifications sent by commercial workflows or broadcasts. Read more about commercial unsubscribe [here](/preferences/commercial-unsubscribe). A few examples: ```json title="Unsubscribe user from admin category notifications" { "categories": { "admin": false } } ```
```json title="Unsubscribe user from email notifications" { "channel_types": { "email": false } } ```
```json title="Unsubscribe user from a specific email channel" { "channels": { "550e8400-e29b-41d4-a716-446655440000": false } } ```
```json title="Unsubscribe user from at-mention notifications" { "workflows": { "mention": false } } ``` ## Channels vs. channel types Channel preferences provide more granular control than channel types by allowing you to specify preferences for individual channels rather than entire channel types. - **Channel type preferences** (`channel_types`) control broad categories like `email`, `sms`, or `push` - **Channel preferences** (`channels`) control specific channel instances using their UUID identifiers Channel preferences always take precedence over channel type preferences because they are more specific. See [preference evaluation rules](#preference-evaluation-rules) below for more information. You can combine these keys to create preference grids like the one in the image below: ```json title="A preference grid for categories and channels" { "categories": { "collaboration": { "channel_types": { "email": true, "in_app_feed": true }, "channels": { "550e8400-e29b-41d4-a716-446655440000": false, "6ba7b810-9dad-11d1-80b4-00c04fd430c8": true } }, "project-updates": { "channel_types": { "email": false, "in_app_feed": true } } }, "workflows": { "invoice-issued": { "channel_types": { "email": true }, "channels": { "6ba7b810-9dad-11d1-80b4-00c04fd430c8": true, "f47ac10b-58cc-4372-a567-0e02b2c3d479": false } } } } ``` The `PreferenceSet` above models this preference grid in your application: An image of a preference set Check out{" "} our interactive example app {" "} to see how making changes to the preference center UI updates the values of a  PreferenceSet } /> ## Environment-level default preferences You can set an environment-level default `PreferenceSet` in the Knock dashboard that applies to all recipients in an environment. When Knock evaluates preferences for a recipient who has not set any preferences of their own, the environment default serves as the baseline. This is useful when you want to establish a baseline that differs from Knock's default of opting all recipients in to all notifications. The environment-level default is always the lowest priority in the [merge hierarchy](#merging-preferences). Any preferences set at the recipient or tenant level will take precedence over it. To set up an environment-level default preference set, see [Create a default preference set](/preferences/custom-preference-center#create-environment-level-default-preferences). ## Merging preferences When a default `PreferenceSet` exists for an environment or tenant, Knock will merge all applicable preferences for a recipient when evaluating whether or not to send a notification. Any preferences set at the recipient level will take precedence in the merge, according to the hierarchy outlined below. ### Merge hierarchy The following hierarchies are used when merging preferences. {/* Note: The original diagrams for the merge hierarchy images can be found in Figma here: https://www.figma.com/board/LlFCmkX4jgGDRcfJuTqkhl/Preferences-diagrams */} The following standard hierarchy is used to merge preferences when no `tenant` is applied to the workflow trigger. Each item in the list takes precedence over the ones that follow it: 1. A [recipient's default preference](/preferences/overview#how-preferences-work) set 2. The [environment-level default preference](/preferences/custom-preference-center#create-environment-level-default-preferences) set Let's take a look at an example to visualize this merge. Suppose that you have the following preference sets: A visual of the environment default and recipient default preference sets prior to merging
To visualize the resulting merged preferences, we'll start at the bottom of the hierarchy and work our way up, adding any preferences from the environment default preference set that are not already present in the recipient's `default` preference set. Any preferences that are set in both places will keep the value from the recipient's `default` preference set: A visual of the environment default preferences merging into the recipient's default preferences
The resulting merged preferences will look like this: A visual of the combined environment default and recipient default preference sets after merging
The environment-level setting to opt all users into `collaboration` notifications will be respected because the recipient doesn't have an explicit preference for that category, but the recipient's preference to specifically opt in to SMS messages will override the environment-level setting to opt out.
The following hierarchy is used to merge preferences when a `tenant` is applied to the workflow trigger. Each item in the list takes precedence over the ones that follow it: 1. A recipient's [tenant-specific preference](/multi-tenancy/per-tenant-preferences) set 2. The [tenant's default preference](/multi-tenancy/per-tenant-preferences#create-a-per-tenant-default-preferenceset) set 3. The [environment-level default preference](/preferences/custom-preference-center#create-environment-level-default-preferences) set This merge works in the same way as the standard merge above, but it evaluates different preference sets. Let's take a look at an example to visualize this merge. Suppose that you have the following preference sets: A visual of the tenant-specific preference sets prior to merging
To visualize the resulting merged preferences, we'll start at the bottom of the hierarchy and move upward—first adding preferences from the environment default to the tenant's default preference set, then adding any of those preferences that are not already present in the recipient's tenant-specific preference set. At each step, if a preference exists in both sets, the value from the higher level in the hierarchy takes precedence: A visual of the tenant-specific preferences merging into the tenant's default preferences
The resulting merged preferences will look like this: A visual of the combined tenant-specific and tenant default preference sets after merging
The environment-level settings to opt all users out of SMS messages and into `collaboration` notifications will be respected because neither the tenant nor the recipient have an explicit preference for these items. The tenant's preference to opt users in to `reminders` notifications will be overridden by the recipient's setting to opt out. If a tenant is applied to the workflow trigger but there are no tenant-specific preferences set on the tenant or the recipient, the recipient's default preference set will be evaluated according to the standard merge hierarchy, as if no tenant were applied. } />
The `commercial_subscribed` preference is an exception to both hierarchies above. When it's set on a recipient's `default` preference set, it always takes precedence over tenant and environment defaults. This keeps a user's commercial opt-out with them across every tenant they belong to. For more information on how you can override these hierarchies, see the [frequently asked questions](#frequently-asked-questions) section below. ## Preference evaluation rules When a workflow is triggered, Knock will evaluate the preferences for each `recipient` of the workflow and send notifications for each channel step in the workflow based on that evaluation. There are some important rules and caveats to consider: - If you do not set a preference for a given channel, workflow, or workflow category, Knock defaults them to `true`. - When a recipient clicks an unsubscribe link, their `default` preference set will be updated, marking `commercial_subscribed` as `false`. They will be opted-out of commercial messages, and they will continue to receive transactional messages based on their other preferences. Read more about commercial unsubscribe [here](/preferences/commercial-unsubscribe). Knock only sends a notification if all preference combinations that exist on the recipient evaluate to `true`. - A workflow can belong to multiple `categories`. Only one of those category preferences needs to evaluate to `false` for the notification not to send. - If a workflow's `category` is set to `false`, the notification will not send even if a `channel_type` or `channel` on the workflow is explicitly set to `true`. - Channel preferences take precedence over channel type preferences. If a specific `channel` is set to `true` but the `channel_type` is set to `false`, the notification will still send through that specific channel. - If both `channel` and `channel_type` preferences exist for the same notification, the `channel` preference is evaluated first and takes priority. Our [Preferences API](/api-reference/recipients/preferences) provides endpoints for retrieving all of the preferences that have been set on a recipient. You can also use the [workflow debugger](/send-notifications/debugging-workflows) in your dashboard to view the preferences that were evaluated for the recipient on a given workflow run. An image of a workflow run's evaluated preferences ## Going live with preferences Once you've configured your preference model, you need a way for users to manage their preferences. Knock offers two approaches: - **Hosted preference center.** Knock hosts and renders a preference page for each user, accessible via a signed URL. You configure rows, labels, and branding in the dashboard - no code required. Supports the user's `default` preference set only. See [hosted preference center](/preferences/hosted-preference-center) for more information. - **Custom preference center.** You build the UI and embed it in your product, using Knock's preferences API to read and write preference values. This gives you full control over layout, UX, and which preferences you surface—including [per-tenant](/multi-tenancy/per-tenant-preferences) and [object](/preferences/object-preferences) preferences. You can optionally use the dashboard-managed configuration to drive your row definitions without hardcoding them. See [custom preference center](/preferences/custom-preference-center) for more information. ## Bulk set user preferences You can update the preferences of up to 1000 users in a single batch by using the `users.bulkSetPreferences` method. This executes an asynchronous job which will overwrite any existing preferences for the users provided. You can track the progress of the `BulkOperation` returned via the [bulk operation API](/api-reference/overview/bulk-endpoints).
## Advanced concepts - [Per-tenant preferences](/multi-tenancy/per-tenant-preferences). In multi-tenant B2B applications, an advanced use case is customer admins who want to set the tenant-level default `PreferenceSet` for new users within their tenant. - [Object preferences](/preferences/object-preferences). You can set preferences for object recipients, just as you can for users. - [Preference conditions](/preferences/preference-conditions). You can build advanced conditions and store them on Knock’s preference model to power use cases such as per-resource muting (example: mute notifications about this task) or threshold alerts (example: only notify me if my account balance is below $5). - Merge strategy. It's possible to configure the merge strategy on specific preferences within a `PreferenceSet`. This allows you to override the default merge hierarchy when preferences are evaluated. See the [frequently asked questions](#frequently-asked-questions) section below for more details and use cases. - Workflow overrides. If you need to override a recipient's notification preferences to send notifications like a password reset email, you can override the preferences model. To do this, go to your workflow, click "Manage workflow," and enable "Override recipient preferences." You will need to commit this change for it to take effect. When enabled, the workflow will send to all of its channels, regardless of the recipient's preferences. - [Commercial unsubscribe](/preferences/commercial-unsubscribe). You can configure 1-click unsubscribe links to help users opt-out of commercial or promotional notifications and comply with CAN-SPAM requirements. ## Frequently asked questions Yes. While our preferences API endpoints will default to overwriting the existing `PreferenceSet` with the new preferences provided, it's possible to update a single preference using either of the two options below. <> **1. Setting a `__persistence_strategy__` key on your `PreferenceSet` to `"merge"`** You can provide an optional `__persistence_strategy__` key when providing your `PreferenceSet` to our preferences API endpoints. Setting this key to `"merge"` will perform a deep merge of the provided preferences into any existing preferences. Here's what this looks like in practice. If a user has these existing `default` preferences: ```json title="An existing default preference set" { "categories": { "collaboration": { "channel_types": { "email": true, "in_app_feed": true } } } } ``` and they would like to update their email preference for the `collaboration` category to `false`, you can do so by providing the following `PreferenceSet` to our [user preferences endpoint](/api-reference/users/set_preferences): ```javascript title="Updating a single preference using the Node SDK" import Knock from '@knocklabs/node'; const client = new Knock({ apiKey: 'My API Key', }); const preferenceSet = await client.users.setPreferences('user_id', 'default', { __persistence_strategy__: 'merge', categories: { collaboration: { channel_types: { email: false } } } }); ``` This will result in the following `PreferenceSet` being set on the recipient: ```json title="The resulting default preference set" { "categories": { "collaboration": { "channel_types": { "email": false, "in_app_feed": true } } } } ```
<> **2. Inline identifying preferences** You can update a single preference inline by using any of our [recipient identification methods](/managing-recipients/identifying-recipients), including while triggering a workflow (as seen in the example below). This will perform a deep merge of the provided preferences into any existing preferences, just like any other [properties stored on the recipient](/concepts/users#storing-user-properties). Note that the syntax for providing the `id` of the preference set on an [`InlinePreferenceSetRequest`](/api-reference/recipients/preferences/schemas/inline_preference_set_request) is slightly different than the way that our preferences endpoints handles them; you'll simply provide a key-value pair where the key is the `id` of the preference set and the value is the `PreferenceSet` you'd like to merge. Here's what this looks like in practice. If a user has these existing `default` preferences: ```json title="An existing default preference set" { "categories": { "collaboration": { "channel_types": { "email": true, "in_app_feed": true } } } } ``` and they would like to update their email preference for the `collaboration` category to `false`, you can do so by providing the following `InlinePreferenceSetRequest` while triggering a workflow: ```javascript title="Triggering a workflow with inline preferences using the Node SDK" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.workflows.trigger("new-comment", { data: { project_name: "My Project" }, recipients: [ { id: "1", preferences: { default: { categories: { collaboration: { channel_types: { email: false } } } } } }, ], }); ``` This will result in the following `PreferenceSet` being set on the recipient prior to the workflow being triggered: ```json title="The resulting default preference set" { "categories": { "collaboration": { "channel_types": { "email": false, "in_app_feed": true } } } } ```
By default, a recipient's individual preferences will always take the highest precedence in the merge when preferences are evaluated (according to the hierarchy outlined under [Merging preferences](#merging-preferences) above). Some applications have use cases where it's necessary for a [preference that is set at the tenant level](/multi-tenancy/per-tenant-preferences#set-a-per-tenant-user-preferenceset) to override a user's individual preference. For example, you may want to allow a team admin to disable a certain type of notification for their entire team, regardless of whether an individual user has opted in to that notification. To achieve this, you can set the `__strategy__` key on the preference you'd like to override to a value of `"replace"`. In the following example, although the user has specifically opted into receiving email notifications for the `collaboration` category, email notifications with that category will not send because the `tenant`'s default preference set has set the `collaboration` category to `false`. Consider the following preference sets: ```json title="A user's tenant-specific preference set" { "categories": { "collaboration": { "channel_types": { "email": true } }, "project-updates": { "channel_types": { "email": true } } } } ``` ```json title="The tenant's default preference set" { "categories": { "collaboration": { "__strategy__": "replace", "channel_types": { "email": false } }, "project-updates": { "channel_types": { "email": false } } } } ```
The merge during preference evaluation will look like this: {/* Note: The original diagram for this image can be found in Figma here: https://www.figma.com/board/LlFCmkX4jgGDRcfJuTqkhl/Preferences-diagrams */} A visual of the tenant's default preferences merging into the recipient's tenant-specific preferences with the replace strategy applied
The recipient's preference for the `project-updates` category is preserved in the merge, but their preference for `collaboration` notifications is overridden by the tenant's preference due to the `replace` strategy. This `replace` strategy will also be reflected when you request the user's preferences via API. Although the user's tenant-specific preferences have explicitly been set to the above, the `__strategy__` key will be present on the `PreferenceSet` returned, indicating that their preference has been overridden by the tenant's: ```json title="The returned user preference set" { "categories": { "collaboration": { "__strategy__": "replace", "channel_types": { "email": false } }, "project-updates": { "channel_types": { "email": true } } } } ```
You can also use the `replace` strategy to override top-level `channel_types` or `channels` preferences: ```json title="Tenant default preference set with top-level channel_types override" { "channel_types": { "__strategy__": "replace", "email": false, "sms": true } } ```
Here are some important thing to keep in mind when using the `replace` strategy: - This strategy will only apply to the specific preference(s) where it's included; there's no way to override the entire `PreferenceSet` at the top level. - When the preference with the `replace` strategy is removed, any existing preferences that were overridden by the `replace` strategy will be immediately restored to their original values. For the example above, the user will once again be opted in to `collaboration` notifications. #### Removing the `replace` strategy Setting preferences on a tenant has [the default behavior](/multi-tenancy/per-tenant-preferences/#updating-the-per-tenant-default-preferenceset) of merging the new preferences with any existing preferences. This means that once a preference has been set with the `replace` strategy, simply omitting the `__strategy__` from your next request will not remove it. There are two ways to remove it from the preference set: - Explicitly set the `__strategy__` key to `"merge"` for the preference(s) that should no longer override the user's preferences. This is the default behavior (a user's tenant-specific preferences will take precedence over the tenant's own default). - Use the `__persistence_strategy__` key on your request to replace the entire preference set rather than merging your new preferences with the existing ones. See the FAQ [here](/multi-tenancy/per-tenant-preferences#frequently-asked-questions) for more information.
In the workflow debugger, Knock will show you the preferences that were evaluated for the recipient on a given workflow run. You may notice `channel_types` preferences, even if you didn't explicitly set them on the recipient or in your environment default preference set. An image of a workflow run's evaluated preferences
This is because Knock automatically opts all recipients in to all `channel_types` by default, to ensure that the recipient receives notifications unless they have been explicitly opted out. If you want to override this behavior, you can set the `channel_types` preference to `false` for all channel types in your environment default preference set. This will opt the recipient out from all notifications.
Channel preferences and channel type preferences work together to provide different levels of granularity: - **Channel type preferences** (`channel_types`) control broad categories like `email`, `sms`, or `push` - **Channel preferences** (`channels`) control specific channel instances using their UUID identifiers Channel preferences always take precedence over channel type preferences. This allows users to: - Opt out of all email notifications but still receive critical transactional emails from a specific channel - Opt into SMS notifications generally but exclude marketing messages from a particular provider - Have fine-grained control over their notification experience For example, if a user has: ```json { "channel_types": { "email": false }, "channels": { "550e8400-e29b-41d4-a716-446655440000": true } } ``` They will not receive email notifications in general, but will still receive notifications sent through the specific channel with ID `550e8400-e29b-41d4-a716-446655440000`.
## Hosted preference center Learn how to use Knock's hosted preference center to enable your users to manage notification preferences without writing any code. --- title: "Hosted preference center" description: "Learn how to use Knock's hosted preference center to enable your users to manage notification preferences without writing any code." tags: ["preferences", "preference center", "hosted", "unsubscribe", "commercial"] section: Preferences --- Knock's hosted preference center is a page where your users manage their `default` notification preferences. Knock hosts and renders the page, so you can launch a preference center without building or hosting any UI yourself. - **Knock hosts the page.** Users access their preferences through a signed URL. - **Configure in the dashboard.** Set rows, labels, and branding under **Platform** > **Preferences** > **Preference center**. - **No code required.** Enable the preference center and [link to it from your notifications](#linking-to-the-preference-center). - **User default preferences only.** The hosted preference center does not support [per-tenant](/multi-tenancy/per-tenant-preferences) or [object](/preferences/object-preferences) preferences. The hosted preference center enables you to give users a way to manage their notification preferences without building or hosting the UI yourself. If you need more control over the experience, see the [custom preference center](/preferences/custom-preference-center). Not sure which approach fits? See [Going live with preferences](/preferences/overview#going-live-with-preferences). The hosted preference center currently supports user recipients only. It doesn't support managing preferences for{" "} objects or{" "} tenants. } /> ## How it works Knock hosts the preference center at `p.knock.app` by default. Each user accesses their own preferences through a signed link that looks like this: ``` https://p.knock.app/p/ ``` You can also serve the preference center from your own subdomain. See [Custom domain for the preference center](#custom-domain-for-the-preference-center) below. The token identifies the recipient, so users don't need to log in to your product to view or update their preferences. When a user opens the link, they see their preference settings based on the configuration in your dashboard, with your account branding, preference options, and the ability to opt out of commercial messaging. When the user clicks **Save preferences**, Knock writes their changes back to their `default` preference set. The link never expires. If a preference row, or a channel type within a row, currently has{" "} conditional preferences for a given user, the preference center renders that key but the user can't modify their preference for it. } /> ## Configure your preference center You configure the preference center from the Knock dashboard under **Platform** > **Preferences** > **Preference center**. Your configuration is environment-specific. When the preference center is saved and enabled, changes go live in the environment right away. When the preference center is disabled, users can only edit their [commercial messages opt-out](/preferences/commercial-unsubscribe) preference. Toggle **Show account name** to display your account name alongside your logo at the top of the page. Set the **title** and **body** copy that appear at the top of the page. Use these to tell your users what the page does, for example "Notification preferences" and "Manage your preferred notification channels and categories." Each preference option is a row that your users can toggle. For each row, you configure: - **Type.** Map the row to a [category](/preferences/overview#how-preferences-work), a [workflow](/concepts/workflows), or a [channel](/concepts/channels). For categories and workflows you can select an existing key or create a new one. - **Display name and description.** The label your users see for this row, for example "Comments and replies," and an optional line of supporting text shown beneath it. - **Channel types.** For category or workflow rows, limit the row to specific channel types, such as email, in-app, push, or SMS. When you select one or more channel types, the row renders a toggle per channel type so users can opt in or out of each one. This doesn't apply to channel rows, which already map to a single channel type. The commercial messages row gives your users a single place to opt out of promotional or non-essential notifications. This is backed by the same `commercial_subscribed` preference used for commercial unsubscribe, so opting out here also satisfies one-click CAN-SPAM requirements for your commercial messaging. The commercial messages row is mandatory and always renders in the preference center, whether the preference center is enabled or disabled. You can change the title and body copy for the row, but you can't remove it. The preference center uses the branding configured for your account, including your logo, icon, and primary color. See [branding](/template-editor/branding) for more on how branding works in Knock. Before you save, you can preview the preference center as a specific user. This renders that user's page exactly as they would see it with their current preferences and your latest configuration, so you can confirm the experience before it goes live. When a custom domain is assigned to the current environment, the preview uses that domain. Toggle **Enabled** to turn the preference center on for the environment. ## How configuration maps to preferences Each row in your preference center maps to a key in the recipient's `PreferenceSet`. A row can point at a `category`, a `workflow`, or a `channel`, and can scope to one or more channel types. The hosted preference center doesn't support top-level{" "} channel type{" "} preferences. Channel types can only be configured as part of a category or workflow row. } /> When a user saves their preferences, Knock updates the corresponding keys on the recipient's `default` preference set. These changes are merged with any environment or tenant defaults during [preference evaluation](/preferences/overview#preference-evaluation-rules), with the recipient's preferences taking precedence according to the [merge hierarchy](/preferences/overview#merging-preferences). ## Linking to the preference center ### In workflow templates To link a user directly to their preference center from a notification, include the URL in your message template or [email layout](/integrations/email/layouts) using the built-in variable: ```liquid title="Link to the hosted preference center in an email template" Manage your preferences ``` Knock renders the user-specific link in the notification. Use `vars.manage_preferences_url` when you want to give users full control over their preferences via the hosted preference center. For single-click opt-out of commercial messages via the commercial unsubscribe feature, use `vars.commercial_unsubscribe_url` instead. ### In your application If you need to link a user to their preference center from outside of a notification (for example, from your app settings page or an admin panel), you can generate a link to the hosted preference center. Call [`POST /v1/users/{user_id}/preference_center/signed_url`](/api-reference/users/preference_center/generate_signed_url) from your backend with a **secret API key**. The response includes: - **`url`.** The full hosted preference center URL for the user. ```bash title="Generate a preference center signed URL" curl -X POST "https://api.knock.app/v1/users/user_id/preference_center/signed_url" \ -H "Authorization: Bearer $KNOCK_API_KEY" \ -H "Content-Type: application/json" ``` If your environment uses a [custom preference center domain](/preferences/hosted-preference-center#custom-domain-for-the-preference-center), the returned `url` reflects that domain. See the [create preference center signed URL API reference](/api-reference/users/preference_center/generate_signed_url) for details. ## Copying configuration between environments Your preference center configuration is environment-specific, just like your [preference defaults](/preferences/overview#environment-level-default-preferences). You can copy it from one environment to another using the "Copy to..." button in the upper right corner of the preference center settings. This enables you to keep your environments in sync as you move from development to production. ## Custom domain for the preference center By default, the hosted preference center is served at `p.knock.app`. You can configure a custom domain to serve it from your own subdomain instead (i.e. `prefs.yourcompany.com`). Once a custom domain is assigned to an environment, your templates will automatically resolve `{{vars.manage_preferences_url}}` variables with your custom domain. For configuration steps, see [Setting up a custom domain](/manage-your-account/custom-domains#setting-up-a-custom-domain). Assignment changes take effect immediately. ## Learn more To learn more about the preferences model behind the preference center, see the [preferences overview](/preferences/overview). ## Custom preference center Learn how to build and embed a custom notification preference center in your application using Knock's preferences API. --- title: "Custom preference center" description: "Learn how to build and embed a custom notification preference center in your application using Knock's preferences API." tags: ["preferences", "preference center", "custom", "in-app"] section: Preferences --- Build a custom preference center when you need to embed notification settings directly in your product, control the layout and UX, or support [per-tenant preferences](/multi-tenancy/per-tenant-preferences) or [object preferences](/preferences/object-preferences). If you want a no-code option instead, see the [hosted preference center](/preferences/hosted-preference-center). Not sure which approach fits? See [Going live with preferences](/preferences/overview#going-live-with-preferences). ## Build a custom preference center There are four steps to building a preference center with Knock. The examples on this page focus on a user's default preference set. For other implementations, see [per-tenant preferences](/multi-tenancy/per-tenant-preferences) and [object preferences](/preferences/object-preferences). An environment-level default preference set is the `PreferenceSet` users fall back to when they first sign up for your product. Any user who doesn't have a preference set of their own will use these environment defaults. You can create your environment default preferences in the Knock dashboard under **Platform** > **Preferences**. Creating a default preference set in Knock dashboard Each environment has its own default preferences. You can copy the default preference set from one environment to another to keep your environments in sync. If you create either an environment or tenant default{" "} PreferenceSet those preferences will be merged with changes a user makes in the UI, with the user-specified changes taking precedence. See{" "} Merging preferences{" "} for more information. } /> Once you have your environment-level default preferences created, use the Knock client to retrieve the authenticated user's own `PreferenceSet`. If the user hasn't set any preferences of their own, `getPreferences()` returns the environment default set you created in the step above (merged with any tenant default), so you always have values to render. ```javascript title="Get preferences in your application" import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knockClient.authenticate({ id: user.id }, userToken); const preferences = await knockClient.user.getPreferences(); ``` For per-tenant preferences, pass the tenant id as the `preferenceSet` parameter on `knockClient.user.getPreferences()`. See [Per-tenant preferences](/multi-tenancy/per-tenant-preferences) for details. Once you have loaded a user's preferences, you'll need to render an interface in your application so they can update their notification preferences. Typically you encapsulate all of the getting and setting of preferences in a single component. There are two options for driving the rows in your UI: - **Define labels and rows in application code.** Map preference keys to labels, descriptions, and channel type toggles directly in your component. See [Implementation examples](#implementation-examples) below for in-depth React walkthroughs. - **Fetch dashboard-managed configuration from Knock.** Pull the row definitions you configured in the dashboard so non-engineers can manage the layout. See [Enable dashboard-managed configuration](#enable-dashboard-managed-configuration) below. A basic preference center When a user makes changes to their preferences in your application, you will use the `setPreferences` method to save those changes back to Knock. By default, `setPreferences` replaces any existing `PreferenceSet`, so include the complete set in your request. In this example, the user has opted out of collaboration emails: ```javascript title="Set preferences in your application" await knockClient.user.setPreferences({ categories: { collaboration: { channel_types: { email: false, // Changed to false after the user opted out. in_app_feed: true, }, }, "project-updates": { channel_types: { email: false, in_app_feed: true, }, }, }, }); ``` To learn more about how to update just a single preference, see the [frequently asked questions](/preferences/overview#frequently-asked-questions) on the preferences overview. ## Enable dashboard-managed configuration Instead of hardcoding row labels and structure in your application, you can fetch your preference center configuration from Knock and use it to drive your UI. This allows your team to manage rows, labels, and branding from the dashboard without shipping a code change. You configure the preference center from the Knock dashboard under **Platform** > **Preferences** > **Preference center**. Note that this configuration is environment-specific. Fetch the configuration using the same credentials as `getPreferences()`: ```javascript title="Get preference center config in your application" const response = await fetch( `https://api.knock.app/v1/users/${user.id}/preference_center/config`, { headers: { Authorization: `Bearer ${process.env.KNOCK_PUBLIC_API_KEY}`, "X-Knock-User-Token": userToken, }, }, ); const { config, branding, enabled } = await response.json(); ``` The [response](/api-reference/users/preference_center/get_config) returns everything you need to render and brand your UI: | Field | Description | | -------------- | ------------------------------------------------------------------------- | | `config` | The `title`, `body`, and `rows` that make up the preference center. | | `branding` | Your logo, icon, and brand colors, with an optional `dark` mode override. | | `enabled` | Whether the preference center is enabled for this environment. | | `account_name` | The account name to display alongside your logo. | | `user_email` | A display label for the user, resolved as email, then user id. | Each row in `config.rows` describes one preference control: | Field | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `type` | The kind of control: `workflow`, `channel`, `category`, `channel_types`, or `commercial_subscribed`. | | `name` | The display name for the row. | | `description` | Supporting text shown below the name. | | `identifier` | The category name, workflow key, or channel ID the row controls. Present for `workflow`, `channel`, and `category` rows. | | `channel_types` | The channel types the row is scoped to. An empty list or `null` means all channel types. Present for `workflow`, `category`, and `channel_types` rows. | ## Implementation examples The following tutorials provide an in-depth walkthrough of how to build a preference center UI with React: - **[React tutorial](/in-app-ui/react/preferences).** Build a `PreferenceCenter` component with `@knocklabs/client`. - **[Headless tutorial](/in-app-ui/react/headless/preferences).** Build a custom preference interface with the `usePreferences` hook from `@knocklabs/react`. ## Object preferences Documentation about storing preferences on objects. --- title: Object preferences description: Documentation about storing preferences on objects. tags: ["object preferences", "preferences"] section: Preferences --- You can set a `PreferenceSet` on an object, just as you would for a user.
## Preferences conditions Power advanced conditional logic in your preferences. --- title: Preference conditions description: Power advanced conditional logic in your preferences. tags: ["preference conditions"] section: Preferences --- Preference conditions are [Knock condition models](/concepts/conditions) that are evaluated when computing the current state of your preferences during workflow execution. ## Overview Typically, a `PreferenceSet` evaluates to boolean values representing whether your recipient has opted in or out of receiving notifications on a given channel, workflow, or category. With preference conditions you can add additional custom expressions to a `PreferenceSet`, where the notification is only sent if all preferences (including every condition in the preference `conditions` list) evaluate to `true` at runtime. When using a{" "} batch function, preference conditions for subsequent channel steps will only be evaluated against the first activity in the batch. } /> Below is an example of a workflow preference that has conditions applied to determine if the preference is `true` or `false`: ```json title="An example of preferences with conditions" { "id": "default", "workflows": { "dinosaurs-loose": { "conditions": [ { "variable": "recipient.muted_dinos", "operator": "not_contains", "argument": "data.dino" } ] } } } ``` ## Set preference conditions for a user To set preference conditions for a user, include a `conditions` array when you update the user's `PreferenceSet`. Conditions use the same preferences APIs as channel, category, and workflow preferences. You can call these endpoints from your backend with a secret API key, or from your application with a public API key and signed user token. See [Security and authentication](/in-app-ui/security-and-authentication) for details. Follow this{" "} link {" "} to see an example of how to set preferences with conditions. } /> ## Frequently asked questions A condition can be applied at different points in the preference set: - Inside `workflows`, `categories`, `channel_types`, or `channels` preferences. - Inside an individual `workflows[workflow].channel_types` or `workflows[workflow].channels` preference. - Inside an individual `categories[category].channel_types` or `categories[category].channels` preference. - On the `commercial_subscribed` key of a recipient's `default` preference set. Useful for [suppressing commercial messages under a tenant](/multi-tenancy/per-tenant-preferences#frequently-asked-questions). Knock will capture conditions evaluation details for all preference conditions resolved while executing your workflows. See the [documentation on debugging conditions](/concepts/conditions#debugging-conditions) for more info. Yes. You can use multiple conditions within a single preference `conditions` array. Note that all conditions in the array must evaluate to `true` in order for the notification to be sent; this is a logical `AND` operation. ```json title="An example of multiple conditions on a single preference" { "conditions": [ { "variable": "recipient.muted_dinos", "operator": "not_contains", "argument": "data.dino" }, { "variable": "recipient.id", "operator": "not_equal_to", "argument": "actor.id" } ] } ``` No, this is not currently supported. Each condition in the `conditions` array must evaluate to `true` for a notification to be sent. ## Commercial unsubscribe Learn how to manage commercial email unsubscribe functionality in Knock. --- title: "Commercial unsubscribe" description: "Learn how to manage commercial email unsubscribe functionality in Knock." tags: ["preferences", "unsubscribe", "commercial", "broadcasts", "workflows"] section: Preferences --- Knock provides built-in support for commercial email unsubscribe functionality, allowing recipients to opt out of promotional or commercial messages with a single click. ## How commercial unsubscribe works When you mark a workflow or broadcast as commercial, Knock automatically handles the necessary unsubscribe functionality: 1. Adds required unsubscribe headers to all emails sent through that workflow or broadcast. 1. Provides an unsubscribe URL variable that can be included in your email templates. 1. Manages recipient opt-outs during [preference set evaluation](/preferences/overview#preference-evaluation-rules). ## Configuring commercial workflows To enable commercial unsubscribe functionality for a workflow or broadcast: 1. Navigate to the workflow or broadcast. 1. For a workflow, click "Manage workflow." For a broadcast, click "Edit details." 1. Toggle "Commercial." 1. Save your changes. 1. For a workflow, commit your changes. Once enabled, Knock will automatically include the necessary unsubscribe headers in all emails sent through that workflow. ## Adding unsubscribe links to emails ### Using footer links When configuring an email layout using the visual editor, you can add an unsubscribe link to your email footer by clicking the "Add link" dropdown and selecting "1-click unsubscribe". Adding a commercial unsubscribe link from the Add link dropdown in the email layout editor ### Using the code editor You can add an unsubscribe link to your email layouts or templates using the built-in variable: ```liquid title="Show unsubscribe link" Unsubscribe ``` You can conditionally include the link by checking if the variable is present: ```liquid title="Show unsubscribe link only for commercial messages" {% if vars.commercial_unsubscribe_url %} Unsubscribe {% endif %} ``` ## Configuring the confirmation page When a user unsubscribes by clicking the unsubscribe link, Knock displays a confirmation page showing they have been successfully unsubscribed from commercial messages. You can customize this page by navigating to **Platform** > **Preferences**, then clicking the **Unsubscribe** tab. You can customize the title and body text that will appear on the Knock confirmation page. Customizing the standard unsubscribe confirmation page You can provide a URL that recipients should be redirected to after unsubscribing. Setting a custom redirect URL for unsubscribe ## Preference evaluation rules When a recipient clicks the unsubscribe link, their `default` preference set will be updated, marking `commercial_subscribed` as `false`. They will be opted-out of commercial messages, and they will continue to receive transactional messages based on their other preferences. This recipient-level preference will take precedence over other environment or tenant preferences. Learn more about [preference merging](/preferences/overview#preference-evaluation-rules). ## Setting `commercial_subscribed` via API The unsubscribe link writes this preference for you, but you can also set it yourself. This is useful to sync an opt-out you captured elsewhere in your product, or to backfill preferences across your existing users. `commercial_subscribed` lives on the recipient's `default` preference set. Call the [set user preferences endpoint](/api-reference/users/set_preferences) with a `merge` persistence strategy to update this key without replacing the rest of the preference set. ```javascript title="Opt a user out of commercial messages" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.users.setPreferences("user-id", "default", { __persistence_strategy__: "merge", commercial_subscribed: false, }); ``` To update many users at once, use [bulk set preferences](/preferences/overview#bulk-set-user-preferences). ## Learn more To learn more about managing recipient preferences and building preference centers with Knock, visit our [preferences overview](/preferences/overview). # Version control Learn how versioning works within your Knock resources. ## Environments Learn how to use Knock environments. --- title: Environments description: Learn how to use Knock environments. tags: [ "env", "version control", "variables", "promote", "promotion", "staging", "production write access", "production member role", "clone", "cloning", "duplicate", "rename", "renaming", "slug", ] section: Concepts --- Knock environments enable you to test and review changes before you ship them to production. Each Knock environment has its own isolated data (such as users, tenants, and objects) and its own version-controlled content (such as workflows, guides, and layouts.) ## How environments work Your Knock account starts with two environments: development and production. You can [create additional environments](/version-control/environments#create-additional-environments) (for example, a Staging environment to mirror your own development lifecycle.) Environments contain isolated data (such as users, objects, and logs) and version-controlled content (such as workflows, layouts, and partials.) With Knock environments you: - Use environment-specific API keys to send data to your environments. - Create, update, and version content within any environment. - For production-critical use cases, create content in your development environment, then **promote** it to production. Here's a typical setup for customers using Knock with two environments. 1. **Development environment.** Usually used by engineering and product teams. Contains your development data, and any transactional workflows or other content you do not want people updating directly in production. 2. **Production environment.** Usually used by growth and marketing teams. Contains your production data, and your lower-risk content that does not need to follow promotion, such as lifecycle workflows and one-time announcements. ### Send data to environments Each environment has a set of **data resources** that are per-environment and will never be shared between environments. These data resources are not version-controlled. The following data resources are per-environment and not version-controlled: - Users - Audiences - Tenants - Objects - Messages - Analytics - Logs - Events - Broadcasts An example: the users you identify in Knock. Your real production users are identified in your production environment, while your test development users are identified in your development environment. Each environment has its own set of API keys which you use to send data to the environment. You can find your environment-specific API keys under "Platform" > "API keys" in the Knock dashboard. ### Create content in environments Each environment has a set of **content resources** that are managed via version control. These are resources associated with the creation and orchestration of the content you send to your customers. The following content resources are version-controlled: - Workflows - Guides - Layouts - Partials - Translations - Reusable requests Here's how content resources work: - Content can be created in any environment. - Content can be promoted upwards through environments. - Content can only be edited in the environment in which it was created. - Content is versioned with [commits](/version-control/commits). You can roll back to earlier commits at any time. These rules enable two ways of working with content and shipping it to your end users in production: For content managed by engineering, such as high-volume transactional workflows, this path ensures that content can only be updated in development before being promoted to production. 1. Create and update the content in your development environment 2. Commit the changes to your development environment 3. Promote the changes to your production environment when ready For content managed by growth, marketing, and product, such as feature announcements and lifecycle workflows, this path is faster and doesn't require the promotion step. 1. Create and update the content in your production environment 2. Publish (commit) the changes to your production environment when ready If you want to disable the ability to create content directly in production, you can do so in account settings under the Permissions page. } /> If you are a pre-January-2026 customer, your account will default to having production write access disabled. This means that your users will only be able to create content in the development environment, then promote it to production.

You can enable production write access in account settings under the{" "} Permissions page, at which point your users will be able to create content directly in production. } /> ## Clone resources across environments You can clone supported resources from one environment to another in the Knock dashboard. Cloning creates a new copy of the resource in the destination environment. The clone gets a new key by default (with a `-copy` suffix) and is independent from the original. Open a resource's action menu and select **Clone** (or **Duplicate** for email layouts) to start a clone. When your account has more than one writable destination environment, you can choose which environment to clone into. Cloning is a dashboard-only feature. It is not available through the [Knock CLI](/cli/overview) or [Management API](/developer-tools/management-api) at this time. ### Supported resources You can clone the following resources across environments: - Workflows - Broadcasts - Guides - Message types - Email layouts - Audiences - Partials - Translations - Reusable requests - Source event mappings Archived resources cannot be cloned. ### Clone vs promote Clone and promote solve different problems: 1. **Clone.** Creates a new resource in another environment. Use this when you want a copy to exist in both environments, or when you need to copy content to an environment lower in the promotion chain (for example, from production to development). 2. **Promote.** Moves committed changes for an existing resource up through your environment chain, keeping the same key. Use this when you are following the development-to-production workflow and want the same resource to exist across environments. When you clone a workflow, Knock also copies its channel step templates and workflow schema into the destination environment. ### Environment targeting Which environments you can clone into depends on your account's **production write access** setting on the **Permissions** page in your account settings: 1. **Production write access enabled.** You can clone into any environment in your project. 2. **Production write access disabled.** You can only clone into your Development environment. This follows the same rules as creating and editing content in an environment. If production write access is disabled, environments other than Development are read-only and cannot be selected as a clone destination. Branch environments cannot be used as clone targets. ### Common use cases 1. **Copy a broadcast from development to production.** Create and test a broadcast in development, then clone it to production when you are ready to send it to real users. 2. **Copy production content back to development.** If you created a workflow directly in production and want to edit it using the promotion model, clone it into your development environment. You can give the clone the same key as the production resource if you plan to promote it back and overwrite the production version later. 3. **Duplicate a workflow for experimentation.** Clone a workflow within the same environment (or into another environment) to try changes without affecting the original. ## Create additional environments If you're looking for a way to isolate changes per-developer working within Knock, you may want to consider using{" "} branches. } /> By default your Knock account comes with two environments: development and production. If you need an additional environment in Knock to mirror your own development lifecycle (for example, a staging environment) you can add it on the settings page of the Knock dashboard. To create a new environment, go to the **Environments** page under the **Version control** section of your account settings. You'll see a button to "Create environment." ### Choose where the new environment goes Environments are ordered, and that order determines your promotion path. When you create an environment, the **Insert after** select in the create modal determines where it lands. Choose the environment that the new environment should come directly after, and Knock inserts it one "level" above that environment. Two constraints apply to the position you choose: - **You cannot insert an environment above production.** Production is always the highest environment, so it isn't available in the select. - **You cannot insert an environment below development.** Development is always the lowest environment, so the new environment will always sit above it. By default the select is set to the second-to-last environment, which places the new environment directly below production. In a new account with only development and production, that means a new environment is inserted between the two. Environments cannot be re-ordered after they're created, as this would break the promotion model for previously-promoted changes. ### What's in a new environment A new environment is not empty. Knock seeds it from the environment directly below it when you create it, and different parts of your account are seeded in different ways: | Resource | What the new environment starts with | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Workflows, guides, layouts, partials, translations, and reusable requests | Promoted from the environment directly below it (the latest committed version of each item; archived items are skipped). | | Environment variable values | Empty. Values are per-environment and you need to set them. | | Channel settings | Provider credentials are not copied and you need to configure them. | | Source settings | Every source in your project appears in the new environment with default settings and its own newly generated source URL. No settings are copied, so you need to point your provider at the new environment's URL. | | Hosted preference center configuration | The default configuration. | | API keys | A new public and secret key pair. | | Users, tenants, objects, messages, and logs | Nothing. Data is never copied between environments. | A new environment can look fully configured in the dashboard because its variable keys and channels are all present, but it will not send anything until you set your variable values and reconfigure your provider credentials for each channel. } /> ## Rename an environment You can rename any additional environment that you created. Your Development and Production environments cannot be renamed. {/* prettier-ignore */} Navigate to the **Environments** page under the **Version control** section of your dashboard's account settings. Open the action menu next to the environment you want to rename, select **Edit environment**, update the **Name** field, and click **Update**. Knock derives an environment's slug from its name, so renaming an environment also changes its slug. Update the following after you rename an environment:
  • **CLI commands and CI pipelines.** Any `--environment` flag that uses the old slug, such as `knock workflow list --environment=staging`.
  • **Management API requests.** Any `environment` or `to_environment` query parameter that uses the old slug.
A renamed environment keeps its unique ID, its API keys, and its position in your promotion path. Workflows, guides, layouts, and other content are untouched, and no data moves between environments. Any environment-specific [custom permissions](/manage-your-account/roles-and-permissions#custom-permission-groups) are also preserved on the renamed environment. Renaming an environment requires the `owner` or `admin` role, or a custom role with environment management permissions. A branch takes the name you give it when you create it, and that name is fixed for the life of the branch. If you need a different name, delete the branch and create a new one. } /> ## Environment access controls We recognize the importance of protecting your sensitive data, so we designed Knock from the ground-up with privacy and security in mind. There are two tools you can use to control access to your data in the Knock dashboard: - [Roles and permissions.](/manage-your-account/roles-and-permissions) Knock offers built-in roles for common team functions, and on Enterprise plans [custom permission groups](/manage-your-account/roles-and-permissions#custom-permission-groups) with per-environment access controls. For example, you can give support team members access to debug issues without permission to change notification logic, or deny access to an environment entirely. - [Customer data obfuscation.](/manage-your-account/data-obfuscation) Use environment-level controls to hide customer data for every team member in an environment. On Enterprise plans, [custom permission groups](/manage-your-account/roles-and-permissions#customer-data-obfuscation) can also set per-environment obfuscation rules. ## Frequently asked questions Not at this time. You can only create, rename, and delete environments through the Knock dashboard. Yes. Use [cloning resources across environments](/version-control/environments#clone-resources-across-environments) to copy the resource into your development environment. By default a `-copy` suffix will be added to the key of the cloned resource. Set the key to match the production resource that you're cloning if you want promotions from the lower environment to overwrite the production version later on. ## Branches Learn how to use branches to isolate changes to your Knock resources. --- title: "Branches" description: "Learn how to use branches to isolate changes to your Knock resources." tags: ["version control", "branches", "environments", "commits"] section: Version control --- ## What are branches in Knock? Branches in Knock are a way to isolate changes to your Knock resources, like workflows, layouts, and guides. They're like sandboxes for your changes, allowing you to make changes to your versionable resources without affecting the `main` branch (your `development` environment), or other branches in your account. Knock branches are conceptually similar to Git branches, and are designed to be used in a similar way as part of your development workflow with Knock. You can create a branch, make changes to your resources, and merge those changes into the main branch when you're ready. Your Knock branches can mirror your Git branches, so that you can coordinate feature changes between your application and Knock. Branches are a completely optional feature in Knock. If you don't use branches, you can still use Knock's [commit model](/version-control/commits) to version changes to your resources [in isolated environments](/version-control/environments). ## Create a branch **In the dashboard** You can create a new branch in the Knock dashboard by going to the **Branches** page in your account settings and clicking the "Create branch" button. Alternatively, you can type into the branch selector at the top of the Knock dashboard to quickly create a new branch. By default, you'll be on the `main` branch.
The branch selector in the Knock dashboard.
The branch selector in the Knock dashboard.
**In the CLI** ```bash title="Creating a new branch in the CLI" knock branch create my-branch ``` ## Making changes on a branch Once you create a branch, Knock will copy over all of the resources from the main branch into your new branch so they're available to be worked on. You can then select the branch you want to work on in the branch selector at the top of the Knock dashboard, or alternatively you can work with your resources on the branch via the [Knock CLI](/developer-tools/knock-cli) or [Management API](/developer-tools/management-api). **In the dashboard** You can select a branch via the branch selector at the top of the Knock dashboard. Once you've selected a branch, making changes to your resources on that branch is the same as making changes to your resources on the main branch. You can edit workflows, layouts, audiences, and other resources just as you would on the main branch. Any changes you've made to your resources will not be able to be called via the API until you commit those changes to the branch. You can commit changes under the "Commits" section of the dashboard, or under each resources "Changes" tab. **In the CLI** To work with your resources on a branch via the CLI, you can use the `knock branch switch` command to switch to the branch you want to work on. ```bash title="Switching to a branch in the CLI" knock branch switch my-branch ``` Once you've switched to the branch you want to work on, you can make changes to your resources just as you would on the main branch. You can then push and commit those changes to the branch. ```bash title="Pushing and committing changes in the CLI for a branch" knock workflow push my-workflow --commit ``` You can also pass in the optional `--branch` flag to specify the branch you want to work with. ```bash title="Pushing and committing changes in the CLI for a specific branch" knock workflow push my-workflow --branch my-branch --commit ``` If you need to overwrite existing content in Knock (for example, when local changes should replace what is currently stored), you can add the `--force` flag to the push command. ## Rebase a branch When `main` moves ahead while you're working on a branch, you can rebase the branch to bring in the latest changes from `main` while preserving your branch's commits. Rebase updates your branch in place. It does not promote your branch's changes to `main` — use [merge](#merging-changes-from-a-branch) for that. Rebasing always syncs a branch onto `main` in the `development` environment. You cannot rebase onto staging, production, or another branch. **In the dashboard** When your branch is behind `main`, Knock shows a banner on the **Commits** page indicating how many commits behind you are. You can rebase from there, from the branch selector, or when resolving a merge conflict. **In the CLI** ```bash title="Rebasing a branch in the CLI" knock branch rebase my-branch ``` See the [CLI reference](/cli/branch/rebase) for flags and error handling. ### How rebase works During a rebase, Knock compares each resource on `main` with the same resource on your branch and applies automatic resolution: | Situation | What happens | | ------------------------------------------------------ | ------------------------------------------------------------ | | You have unmerged commits for a resource on the branch | Your branch version is preserved | | You have not changed a resource on the branch | The branch receives the latest committed version from `main` | | A resource exists on `main` but not on the branch | The resource is copied into the branch | Knock does not perform a Git-style three-way merge or per-field conflict resolution during rebase. For resources you have changed on the branch, your branch's version wins automatically. Rebasing also resets the conflict-detection baseline for your branch. If the branch and `main` both changed the same resource and Knock previously flagged a merge conflict, rebasing can clear that conflict by absorbing the changes from `main` that your branch had not modified. ### Before you rebase Rebase may be blocked when you have **unpublished changes on shared resources** where `main` has moved ahead since your branch last synced. In that case, commit or discard the blocking drafts before rebasing. Knock returns an error listing the affected resources. Rebase is **not** blocked by: - Unpublished changes on resources you have already committed to on the branch - Unpublished changes on resources that exist only on your branch - Unpublished changes on shared resources where `main` has not changed ## Merging changes from a branch **In the dashboard** You can view the changes made to a branch in the Knock dashboard by going to the **Commits** page. From there you can navigate to the "Unmerged changes" tab to see all changes that have not been merged into the main branch. When you're ready to merge the changes from a branch into the main branch, you can click the "Merge all changes" button to merge all commits from the branch into the main branch, or you can merge individual commits from the branch into the main branch. ## Delete a branch Once a branch is merged into the main branch, you may wish to delete the branch from your account. Deleting a branch is a permanent operation and cannot be undone. Doing so will delete all of the resources in the branch, including all commits and changes made to your resources on the branch that have not been merged into `main`. **In the dashboard** You can delete a branch in the Knock dashboard by going to the **Branches** page in your account settings and clicking the "Delete branch" button next to the branch you want to delete. **In the CLI** ```bash title="Deleting a branch in the CLI" knock branch delete my-branch ``` ## Working with branches in the API Branches are fully supported in the Knock API so that you can execute workflows, create users, and manage preferences in a branch-specific environment. When making requests to the API for your branch, you **must use the API key for your `development` environment**, along with a special `X-Knock-Branch` header to specify the branch you want to work with. ```bash title="Specifying a branch in the API" curl -X POST "https://api.knock.app/v1/workflows/my-workflow/trigger" \ -H "Authorization: Bearer $KNOCK_API_KEY" \ -H "X-Knock-Branch: my-branch" \ -d '{"recipient": "jhammond"}' ``` Branches are fully supported in our server-side and client-side SDKs as well. ```javascript title="Specifying a branch in the SDK" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY, branch: "my-branch", }); await knock.workflows.trigger("my-workflow", { recipient: "jhammond", }); ``` ## Working with branches in the CLI Branches are supported in the Knock CLI for all commands that interact with resources in your Knock account. You can always use the `--branch` flag to specify the branch you want to work with or set a `.knockbranch` file in your home directory to specify the branch you want to work with. ```bash title="Specifying a branch in the CLI" knock workflow list --branch my-branch ``` It's also possible to use the CLI to programmatically create, update, delete, rebase, and merge branches. ## Working with branches via the Management API When using the management API, you can specify the branch you want to work with by passing the `branch` query parameter to the API endpoint, similar to how you would specify the environment. ```bash title="Specifying a branch in the Management API" curl -X GET "https://control.knock.app/v1/workflows?branch=my-branch" \ -H "Authorization: Bearer $KNOCK_SERVICE_TOKEN" ``` It's also possible to use the management API to programmatically create, update, delete, and rebase branches. ```bash title="Rebasing a branch via the Management API" curl -X PUT "https://control.knock.app/v1/branches/my-branch/rebase?environment=development" \ -H "Authorization: Bearer $KNOCK_SERVICE_TOKEN" ``` ## Recipients and audiences Branches share the same set of [users, objects, and tenants](/concepts/recipients) as the main branch. In other words, if you add, remove, or update a user, object, or tenant on the main branch, the change will be reflected on all branches, and vice versa. This means you can instantly start testing the changes you make on a branch: as soon as you create a branch, you can send notifications to the same users, objects, and tenants as you would on the main branch. Similarly, [audience](/concepts/audiences) membership changes in the main branch are reflected in all branches: if a user is added to an audience on the main branch, they will be added to the same audience on all branches. ## Limitations There are a few limitations to branches in Knock: - Branches are **only** available in the `development` environment. - Rebase is **only** available in the `development` environment and always targets `main`. - Rebase uses automatic branch-wins resolution. Knock does not provide per-field merge or conflict resolution during rebase. - Rebase may be blocked when you have unpublished changes on shared resources where `main` has moved ahead. Commit or discard those drafts before rebasing. - If you have merge conflicts on a resource that is part of a branch, you can rebase the branch to absorb non-conflicting changes from `main`, or resolve the conflicts outside of the dashboard via the CLI in order to continue. - [Audiences](/concepts/audiences) cannot be created or updated on a branch. - Branches are not supported for [broadcasts](/concepts/broadcasts). - Branches are not supported for [sources](/integrations/sources/overview). - Branches are not supported for [webhooks](/integrations/webhooks/overview). ## Frequently asked questions A branch is a way to isolate changes to your Knock resources. It's like a sandbox for your changes. Branches will always use the channel settings configured for the `development` environment (the `main` branch). No, you cannot yet block changes being made directly to your development environment. However, we're considering this feature and would love to hear your use case if you'd like to see this feature. Please [get in touch](mailto:support@knock.app) and we can discuss your use case further. No, there's not currently a way to put a review process in place before merging changes from a branch into the main branch. No, there's no limit to the number of branches you can have in your account. ## Commits Learn about how Knock's commit and promotion model works. --- title: Commits description: Learn about how Knock's commit and promotion model works. tags: [ "branches", "env", "version control", "versions", "commit", "promote", "promotion", "revert", "rollback", "staging", "active", "inactive", "diffs", "push", ] section: Concepts --- To version the content changes you make in your [environments](/version-control/environments), Knock uses a commit model. ## How commits work When you make a change to a content resource (like a workflow or a layout) in the Knock dashboard, you need to commit it to your current environment before those changes will appear in workflows triggered via the API. Commits can be promoted to higher environments, such as production, and they can also be rolled back to prior commits. Lastly, commits provide a comprehensive version history for all content resources in your account. A few additional notes: - **A commit is not a save.** When you're drafting a workflow or a template, you can save and test your changes. Saved changes apply when you're using the test runner, but they do not apply to workflow runs or guide renders that are invoked via the API. To test changes via the API, you must commit them. - **"Publish" in production = commit to production.** If you're working [directly in production](/version-control/environments), the "Publish" button is the same as the "Commit" button. You'll still get a commit history and be able to roll back to prior versions if needed. - **Account-level resources are not version controlled.** Channel configurations, branding, and variables do not need to be committed, as they live at the account-level. This means that if you make a change to a channel configuration, it will update immediately on notifications sent in that environment. - **You can commit changes using our CLI and management API.** We offer both a [Management API](/developer-tools/management-api) and a [command line interface](/developer-tools/knock-cli) for interacting with Knock resources programmatically, both of which support programmatic commits. ## Empty commits An empty commit lets you commit (publish) a versioned resource without changing its content, similar to `git commit --allow-empty`. Empty commits are useful when you need to promote a resource to another environment, but the resource has no unpublished changes in the current environment. Knock tracks promotable changes by comparing each resource's identifier (`ref`) and version (`version_id`). If a resource already points to the published version (`current_version_id == published_version_id`), a standard commit does not create a new promotable change. An empty commit solves this by creating a new version with the same content, then publishing that version, giving Knock a new `version_id` to include in promotion diffs, so you can promote the commit downstream even though the resource content has not changed. What happens when you create an empty commit depends on the resource's current state: - **The resource has unpublished changes.** The empty-clone step is skipped, and a normal publish happens instead. - **The resource is already published with no changes.** An empty commit creates a new version with identical content. Commit log entries for empty commits include audit metadata with `empty: true` and an optional message. You can create empty commits using the [CLI](/cli/commit/all) or the [Management API](/developer-tools/management-api#committing-changes). The bulk commit endpoint (`PUT /v1/commits`) supports the following resource types: - `workflow` - `email_layout` - `partial` - `translation` - `message_type` - `guide` - `audience` For translations, the `resource_id` can be a locale (`es`), a locale and namespace (`es/courses`), or a locale, namespace, and tenant (`es/courses~tenant`). The bulk commit endpoint doesn't support reusable_step. To create an empty commit for a reusable step, use a resource upsert with{" "} commit=true and allow_empty=true, or publish via the dashboard. } /> ## Visualizing commit changes Clicking the "Commit to development" button will show you a view of changes between your current commit and the most recent version of the resource that you're updating. Commit diffs are also available on your full commit log (viewable on the "Commits" page in your dashboard), so you can view the commit history for a resource and know exactly what was changed with each commit. ![Commit diffs in Knocks version control](/images/commit-diff-showcase.gif) ## Promoting commits If you're working in a non-production environment such as development, you can promote your changes to the production environment when you're ready to go live. You can promote your commits from a given resource's page. You can also view all commits in one place in the **Commits** page, where you can promote individual or multiple commits at once. Learn more about promoting commits in our [environments documentation](/version-control/environments). ## Reverting a commit If you've made a change in a commit that you want to revert, you can use the "Revert commit" feature to "undo" that change. You can find the revert commit action on the **Commits** page in the dashboard, under the "Unpromoted changes" and "Commit log" tabs. For resources promoted from development, reverts must be made in the development environment and then promoted to production to revert the production commit. } /> **Reverting a commit will**: - Create a new commit with a message that indicates the commit reverts a preceding commit - Wind back the state of the resource to the change that precedes the commit - Undo any uncommitted changes on the resource Because the revert will produce a new commit, you can then promote that commit to other environments to make that change live in those environments. # Manage your account Learn more about the tools available in managing your Knock account. ## Authentication methods Learn more about the authentication methods available to members on your Knock account. --- title: Authentication methods description: Learn more about the authentication methods available to members on your Knock account. tags: ["auth", "login", "sso", "security"] section: Manage your account --- Understand and select your preferred authentication method in Knock. ## Overview We support three methods of authenticating users to get access to the Knock dashboard: email based passwordless authentication, Google SSO, and SAML 2.0 SSO. You can add an extra layer of security on top of email and Google login with [multi-factor authentication (MFA)](/manage-your-account/multi-factor-authentication), which supports authenticator apps and passkeys. ## Email (passwordless) With this authentication method users will receive an email to authenticate themselves in order to access the Knock dashboard. The email contains a "magic link" that expires after 5 minutes of being generated. Clicking the link will authenticate the user and grant access to the Knock dashboard. ## Google SSO Users can authenticate using their Google account to access their Knock account. Google SSO works for both personal and organizations using Google Workspace (formerly G Suite). ## SAML 2.0 SSO Users can authenticate with their chosen corporate identity provider (i.e. Okta) to access their Knock account. Once SSO is enabled for an account, all members are redirected through that identity provider's authentication flow. Moving forward, they must pass through SSO to access their Knock account. **Note**: SAML 2.0 SSO is only available to **Enterprise** plan customers. Enabling SAML 2.0 SSO will force all members of your account on a matching domain to authenticate via your identity provider. ## Multi-factor authentication Add an extra layer of security to your Knock account by enabling multi-factor authentication (MFA) with an authenticator app or passkey for dashboard login. --- title: Multi-factor authentication description: Add an extra layer of security to your Knock account by enabling multi-factor authentication (MFA) with an authenticator app or passkey for dashboard login. tags: [ "mfa", "2fa", "two-factor", "authenticator", "passkey", "webauthn", "biometric", "backup codes", "security", "login", ] section: Manage your account --- ## Overview Multi-factor authentication (MFA) adds a second verification step on top of your existing [login method](/manage-your-account/authentication-methods). After you sign in with email or Google, Knock prompts you to verify your identity before granting access to the dashboard. Knock supports two MFA methods: 1. **Authenticator app (TOTP).** Enter a one-time 6-digit code from an app such as 1Password, Authy, or Google Authenticator. 2. **Passkey.** Authenticate with a WebAuthn-compatible device using biometrics, a device PIN, or a hardware security key. MFA applies to email (passwordless) and Google logins. If your account uses [SAML SSO](/manage-your-account/saml-sso), your identity provider handles MFA for those users and Knock does not prompt for an additional factor. ## Enroll in MFA Any member can enroll in MFA from their profile settings in the Knock dashboard. Account owners and admins who want to require MFA for all members use a separate account-level setting (see [Enforce MFA for your account](#enforce-mfa-for-your-account) below). If your account owner or admin has enabled account-wide MFA enforcement, you are prompted to complete enrollment the next time you log in before you can access the dashboard. ### Enroll with an authenticator app 1. Click **Overview** in the sidebar, then open your **profile settings** and navigate to the **Security** section. 2. Under **Two-factor authentication**, click **Set up**. 3. Scan the QR code with an authenticator app (such as 1Password, Authy, or Google Authenticator). 4. Enter the 6-digit code from your authenticator app to confirm enrollment. 5. Save your backup codes in a secure location. Knock shows these codes once during enrollment. ### Enroll with a passkey 1. Click **Overview** in the sidebar, then open your **profile settings** and navigate to the **Security** section. 2. Under **Two-factor authentication**, click **Add passkey**. 3. Select your preferred authenticator when prompted. Available options depend on your browser and device (for example, Touch ID, Face ID, Windows Hello, a hardware security key, or a password manager). 4. Follow the on-device prompts to complete registration. 5. The passkey appears in your list of enrolled MFA methods in **Security** settings. You can enroll multiple passkeys, such as one for your laptop and one for your phone. Remove any passkeys you no longer use from your profile **Security** settings. ## Backup codes When you enroll with an authenticator app, Knock generates a set of backup codes you can use to sign in if you lose access to your app. Each backup code is single-use. You can regenerate backup codes from your profile **Security** settings at any time. Regenerating codes invalidates any previously issued backup codes. If you enroll with a passkey, you verify your identity with your enrolled device or security key at login instead of using backup codes. ## Logging in with MFA When MFA is enabled on your account, Knock prompts you to verify your identity after you complete the initial sign-in step (email magic link or Google SSO). 1. **Authenticator app.** Open your authenticator app and enter the current 6-digit code on the MFA challenge screen. 2. **Passkey.** Click **Sign in with passkey** on the MFA challenge screen and complete the WebAuthn prompt on your device. 3. **Backup codes.** If you cannot access your authenticator app, click **Use a backup code** and enter one of your saved backup codes instead. After you verify your identity, Knock authenticates your session and redirects you to the dashboard. ## Enforce MFA for your account Account owners and admins can require all members to enroll in MFA before they can access the dashboard. This is separate from enrolling MFA for your own login on the **Profile** page. 1. Log in to your Knock dashboard. 2. Navigate to the **Security** page under **Admin** in your account settings (`dashboard.knock.app//settings/security`, where `` is your account identifier). 3. Toggle **Require multi-factor authentication** to enable enforcement for all members. When enforcement is enabled, members who have not yet enrolled in MFA are prompted to enroll at their next login or token refresh. They can choose an authenticator app or a passkey. They cannot access the dashboard until enrollment is complete. The require MFA toggle is not available for accounts with SAML SSO enabled. Your identity provider manages authentication, including MFA, for members who sign in through SSO. } /> ## Reset a member's MFA If a member loses access to their MFA methods, an account owner or admin can reset their MFA from the account **Security** settings. Resetting a member's MFA removes all enrolled factors, including authenticator apps, passkeys, and backup codes. The member must enroll again at their next login. ## Frequently asked questions Knock supports TOTP-based authenticator apps, including 1Password, Authy, Google Authenticator, and other apps that scan QR codes or accept `otpauth://` URIs. Passkeys work in WebAuthn-compatible browsers and devices. You can use biometrics such as Touch ID or Face ID, a device PIN, a password manager, or a hardware security key. Yes. You can enroll an authenticator app, one or more passkeys, or both. At login, Knock prompts you to verify with whichever method you choose on the MFA challenge screen. Yes. You can remove individual passkeys from your profile **Security** settings at any time. Yes. You can disable MFA from your profile **Security** settings. You must verify your identity to confirm, such as by entering a current code from your authenticator app or using a passkey. If your account owner or admin has enabled account-wide MFA enforcement, you cannot disable MFA until enforcement is turned off. Contact an account owner or admin to reset your MFA. After the reset, you enroll a new MFA method at your next login. Contact an account owner or admin to reset your MFA. After the reset, you enroll a new MFA method at your next login. No. MFA applies to interactive dashboard login only. API keys, service tokens, and other machine-to-machine credentials are not affected. ## SAML SSO How to configure SAML SSO on your account. --- title: SAML SSO description: How to configure SAML SSO on your account. tags: ["authentication", "SAML", "SSO"] section: Manage your account --- SAML SSO is only available on our{" "} Enterprise plan. } /> ## SSO configuration To configure SAML SSO, an **account owner** will need to complete the following steps: 1. Log in to your Knock dashboard 2. Navigate to the **Security** page under the account settings section of your dashboard 3. Locate the "Enable SAML SSO" panel 4. Click on the "Connect SSO provider" button to begin the configuration process. This will launch our SAML SSO configuration wizard where you can select and configure your identity provider.
SAML SSO configuration wizard
SAML SSO configuration wizard
After completing the steps of the configuration walkthrough, SAML will be successfully configured for your account. This will allow users with emails under your domain to be able to log in successfully through the provider you have configured. Self-service SAML SSO configuration will allow users to authenticate using emails under the same domain as your own. If your setup requires support for more than one domain, contact support. } /> ## SSO authentication Once SAML SSO is configured on your account, all members will be required to log in using the provider you configured. When visiting the login screen, users must use the email field to start the authentication flow. Users will **not be allowed to authenticate** using other authentication methods while SAML SSO is enabled on your account. ## Directory sync (SCIM) How to configure directory sync on your account to automate the management of users and their permissions from your identity management platform to Knock. --- title: Directory sync (SCIM) description: How to configure directory sync on your account to automate the management of users and their permissions from your identity management platform to Knock. tags: ["authentication", "directory sync", "SCIM", "ULM", "permission groups"] section: Manage your account --- Directory sync is only available on our{" "} Enterprise plan. } /> ## Overview Directory sync allows you to automatically provision users and manage their permissions in Knock by leveraging the identity provider your organization is using (e.g. Okta) as the single source for user and group information. Once configured, it enables automated syncing of user identity information from identity providers to Knock using SCIM (System for Cross-domain Identity Management), an open standard for managing automated user and group provisioning. Any users that are assigned in the Knock application in your identity provider will be created in Knock (or vice versa), with their roles and permissions automatically configured based on their group memberships. Group memberships can map to built-in roles or to [custom permission groups](/manage-your-account/roles-and-permissions#custom-permission-groups). See the default group to [role mapping](/manage-your-account/directory-sync#group-to-role-mapping) for more details. ## Directory sync configuration Contact the [Knock support team](mailto:support@knock.app) to set up directory sync for your account. You'll receive a custom link to step-by-step instructions for configuring the directory sync for your specific identity provider. We support many common identity providers. For detailed, provider-specific setup documentation, please refer to the following: - [CyberArk](https://workos.com/docs/integrations/cyberark-scim/2-select-or-create-your-cyberark-application) - [Entra ID (formerly Azure AD)](https://workos.com/docs/integrations/entra-id-scim/2-select-or-create-your-azure-application) - [JumpCloud](https://workos.com/docs/integrations/jumpcloud-scim/2-select-or-create-your-jumpcloud-application) - [Okta](https://workos.com/docs/integrations/okta-scim/2-select-or-create-your-okta-application) - [OneLogin](https://workos.com/docs/integrations/onelogin-scim/2-select-or-create-your-onelogin-application) - [PingFederate](https://workos.com/docs/integrations/pingfederate-scim/2-install-the-scim-connector-in-pingfederate) - [Rippling](https://workos.com/docs/integrations/rippling-scim/2-create-your-rippling-application) Once the setup is complete and user data starts syncing from your identity provider to Knock, you'll see a "connected" status for directory sync on the **Security** page under the **Admin** section of your account settings in your Knock dashboard. ## Group-to-role mapping You can optionally supply a set of group mappings for your organization. For instance, you might map the "Team Admins" group to the `admin` role, or the "Engineering" group to a [custom permission group](/manage-your-account/roles-and-permissions#custom-permission-groups) with the key `engineering`. You must supply this mapping to the Knock support team to set on your account as there is currently no way to self-service this information. Groups must be mapped to either a built-in role (`owner`, `admin`, `member`, `production_only_member`, `billing`, `support`) or a custom permission group key. The mapping value should always be the role or permission group key itself (`admin`, `engineering`), not a `knock-role-` prefixed value. Knock's default `knock-role-{key}` group names always apply and cannot be overridden by a custom mapping. ### Built-in role groups To assign a built-in role from your identity provider, use the following group names. Knock automatically allocates the matching role to users in that group. | Group name | Role | | --------------------------------- | ---------------------- | | knock-role-owner | owner | | knock-role-admin | admin | | knock-role-member | member | | knock-role-production_only_member | production_only_member | | knock-role-billing | billing | | knock-role-support | support | ### Custom permission groups If your account uses [custom permission groups](/manage-your-account/roles-and-permissions#custom-permission-groups), directory sync can assign those groups as well. Create a group in your identity provider named `knock-role-{key}`, where `{key}` is the permission group's key. For example, a group named `knock-role-engineering` maps to a custom permission group with the key `engineering`. You can also include a custom permission group key in the custom mapping you send to Knock support. For example, mapping `"Engineering"` to `"engineering"` assigns that custom permission group to every member of the Engineering identity provider group. Built-in role names always take precedence over a custom permission group with the same key. Avoid using a permission group key that matches a built-in role (`owner`, `admin`, `member`, `production_only_member`, `billing`, `support`). If you create a custom group whose key is `admin`, `knock-role-admin` still maps to the built-in admin role. Missing or archived custom permission groups are ignored at sync time. If no live custom group remains, Knock falls back to the highest-privilege mapped built-in role, or to the support role if none is mapped. ## How Knock assigns roles Knock resolves each user's assignment from their identity provider group memberships: 1. **Custom permission groups.** A user can belong to more than one identity provider group. If any of those memberships map to live custom permission groups, Knock assigns the **Custom** role and the combined permissions of every matching group. This takes precedence over the owner and admin roles. A member cannot have both a built-in role and custom permission groups. 2. **Highest-privilege built-in role.** If no live custom permission group matches, Knock assigns the highest-privilege mapped built-in role. See [roles and permissions](/manage-your-account/roles-and-permissions) for more details. 3. **No mapped group.** If a user does not belong to any mapped group, Knock assigns the **support** role. ## Preventing account lockout Because Knock uses your identity provider as the source of truth, role changes coming from your identity provider are applied even when they would remove or demote your last account owner. As a result, it's possible to end up with an account that has no owner — for example, if the only user mapped to the `owner` role is removed from your directory (or from the `knock-role-owner` group). To avoid losing owner access, make sure at least one person is always mapped to the owner role — via the{" "} knock-role-owner group or your custom group-to-role mapping — and is not removed from your identity provider. If a live custom permission group mapping also matches that user, the custom assignment wins and re-adding knock-role-owner does not restore owner access. See{" "} custom permission groups and owner access {" "} for how to recover, or contact the{" "} Knock support team. } /> ### Custom permission groups and owner access If a user belongs to both an owner-granting group and a group that maps to a live custom permission group, the custom assignment wins. Re-adding the user to `knock-role-owner` does not restore the owner role while that custom mapping still matches. To restore the owner role, do one of the following: - **Remove the custom group memberships.** Remove the user from the identity provider groups that map to custom permission groups. - **Archive the permission group.** Archive the custom permission group in Knock so Knock ignores it at sync time. Then keep the user in `knock-role-owner` or in a group that your custom mapping assigns to `owner`. ## Frequently asked questions Knock will identify existing users based on their email, and update (i.e. overwrite) their access and roles based on the user data synced from your identity provider. Any users that exist in Knock but not in identity providers will retain access to Knock and retain its role originally assigned to them. If directory sync connection becomes disabled, all users and roles will be left in the state at the time of disconnection and stop syncing from your identity provider. Yes, please contact the [Knock support team](mailto:support@knock.app) to configure a custom mapping. Values can be a built-in role or a custom permission group key. You cannot override Knock's default `knock-role-{key}` mapping. Yes. Use a group named `knock-role-{key}` in your identity provider, where `{key}` matches the custom permission group's key, or include that key in the custom mapping you send to Knock support. If one or more memberships map to live custom permission groups, Knock assigns the Custom role and the combined permissions of every matching group. This takes precedence over the owner and admin roles. Custom permission groups are available on the [Enterprise plan](/manage-your-account/knock-plans). Learn more in [custom permission groups](/manage-your-account/roles-and-permissions#custom-permission-groups). Yes, you can still invite users to Knock from the Knock dashboard, but keep in mind that users created via directory sync will take precedence. This means if you invite a user who is managed via directory sync, the user's role will be updated to reflect the state of your identity provider. Once you enable directory sync, Knock uses your identity provider as the source-of-truth for any users synced via SCIM. ## Managing members Learn how to invite, manage, and remove members on your Knock account. --- title: Managing account members description: Learn how to invite, manage, and remove members on your Knock account. tags: [ "team", "team members", "account members", "invites", "inviting", "auto join", "auto-join", ] section: Manage your account --- ## Overview People with access to your Knock account are called members. Each member has a [role](/manage-your-account/roles-and-permissions) that determines what they can do in your account. Only account owners and admins can invite, manage, and remove members. Account owners and admins will find the **Members** page in the Knock dashboard's settings: Managing account members in the dashboard ## Adding members ### Inviting members You can invite new members to your account by selecting the "New member" button on the **Members** page. Members are always invited via their email address and must be assigned [a role](/manage-your-account/roles-and-permissions). On Enterprise plans, you can assign one or more [custom permission groups](/manage-your-account/roles-and-permissions#custom-permission-groups) instead of a built-in role. You can optionally include a message that will appear in the invitation email. The member will be sent a reminder email if the invite is not accepted within 3 days. Account invites will automatically expire after 2 weeks.
A Knock invite email
An example invitation email inviting a user to a Knock account
### Managing invitations Once you invite a user to your Knock account, their invitation appears as a "pending invite" on the **Members** page until they accept. You can revoke pending invitations, which marks the invite as invalid and expires it immediately. Once you revoke an invitation for a user, you can reinvite them by following the same "New member" process. This will generate a new invitation for the user to join your account. ### Enabling auto-join You can enable auto-join on your account to let users that belong to your domain automatically join your account when signing up for Knock. This is helpful for cases where people from your organization sign up for Knock on their own without realizing your account already exists. You can enable auto-join on the **Security** page under the **Admin** section of your account settings in your Knock dashboard. Auto-join settings #### Selecting approved domains When you enable auto-join, you'll need to select which domains can auto-join your account. For security reasons, we only let you select non-public domains that belong to the owners of your account. This means that you cannot enable auto-join for public domains (such as gmail.com). Additionally, before you can add a new domain to auto-join, someone with an email address from that domain must have an [owner role](/manage-your-account/roles-and-permissions) on your account. #### Setting a default role When you enable auto-join, new users are assigned the `member` role by default. You can change the default auto-join role to `admin`, `member`, or `support`. If you want to assign a different role to someone from your domain, send them an invitation as usual. The role assigned in the invitation takes precedence over the auto-join default. ## Managing members The following restrictions apply when managing members: - Members can't change their own roles. - Members can't remove themselves from a Knock account. - Accounts must have at least one `owner` at all times. ### Updating member roles Account members with appropriate [permissions](/manage-your-account/roles-and-permissions) can update the roles of others by selecting "Change role" from the three-dot menu. You can switch a member between a built-in role and custom permission groups, or update which custom groups they belong to. The change takes effect immediately. If the member is managed by [directory sync](/manage-your-account/directory-sync), Knock overwrites their role and custom permission groups based on their identity provider group memberships. Dashboard role changes for those users do not persist across the next sync. ### Removing members Members with appropriate [permissions](/manage-your-account/roles-and-permissions) can remove other members from an account by clicking the "Remove member" option found in the three-dot menu. Removing a member will revoke their access to Knock and immediately invalidate any session associated with the member. The removed member will receive an email informing them of the change in account access. ## Roles and permissions Learn about roles, permissions, and custom permission groups in Knock. --- title: Roles and permissions description: Learn about roles, permissions, and custom permission groups in Knock. tags: [ "team", "team members", "account members", "invites", "inviting", "permission groups", "custom roles", ] section: Manage your account --- ## Overview Knock uses an account-level roles model, where a given account member's role determines what they'll be able to do in your account. You set an account member's role when you invite them to the Knock dashboard. You can update their role on the **Members** page under the **Admin** section of your account settings. Learn more in our [managing members documentation](/manage-your-account/managing-members). Knock provides a set of built-in roles for common team functions. Here's an overview of the built-in roles available to Knock account members: - **Owner.** For your primary admin who manages billing. This role can invite and manage members, manage billing, and do anything available in the admin role. Your account must always have at least one account owner. - **Admin.** For admins who need to manage account-level settings. This role can invite and manage members (excluding owner and billing roles), manage account branding, manage environments, and manage advanced developer concepts such as signing keys, enhanced security mode, variables, and webhooks. This role has all permissions available to the member role. - **Member.** For users who are editing notification workflows and templates in Knock. This role can manage workflows, layouts, users, objects, and tenants. It can make commits and push changes to subsequent environments, and has full access to message and API logs for debugging. - **Production-only Member.** Available when production write access is enabled in your account settings. For team members who should only work in production (such as lifecycle marketers managing in-app announcements). This role has the same permissions as the member role, but only has access to the production environment. - **Support.** For users who shouldn't have access to workflows and templates, but should be able to dig into message and API logs for debugging purposes. - **Billing.** For account members who shouldn't have access to anything in Knock but billing. For a complete overview of which permissions are available to which built-in roles, see the [lookup table](#roles-and-permissions-lookup-table) below. ## Custom permission groups Custom permission groups are only available on our{" "} Enterprise plan. } /> Custom permission groups enable you to define your own roles with specific capabilities. Create and manage them under **Settings** > **Permissions**. When you create a permission group, you set a unique **key** (lowercase letters, numbers, underscores, and hyphens). The key cannot be changed after creation. [Directory sync](/manage-your-account/directory-sync#custom-permission-groups) uses this key to map identity provider groups to the permission group. ### Capabilities When you create a permission group, you choose which capabilities members in that group receive. Capabilities fall into two scopes: - **Account.** Settings and resources that apply across the whole account, such as members, billing, integrations, and API keys. - **Environment.** Resources that live in an environment, such as workflows, guides, content, broadcasts, audiences, recipient data, observability, and release management. Most capabilities support none, view, and manage. Release management uses a different set of levels: - **None.** No commit or promotion access in that environment. - **Commit.** Commit changes in that environment. - **Manage.** Commit and promote changes between environments. ### Environment access For environment-scoped capabilities, you control both what members can do and which environments they can access: 1. **Same access in every environment.** Apply one set of environment capabilities across development, production, and any additional environments. 2. **Per-environment access.** Use granular mode to set different capability levels in each environment. For example, grant manage access to workflows in development, and view-only access in production. 3. **No access to an environment.** Deny an environment to hide it from members in the group and block all environment-scoped permissions there, including on its branches. Account-level capabilities still apply. At least one environment must remain accessible. ### Customer data obfuscation Custom permission groups can enable [customer data obfuscation](/manage-your-account/data-obfuscation) under environment access. When enabled, all message, user, and object data will be obfuscated in the Knock dashboard for members of that group. You set the rule in the same all or granular mode you use for environment capabilities: 1. **Same access in every environment.** Enable obfuscation once to apply it across every environment. 2. **Per-environment access.** Use granular mode to enable obfuscation in specific environments. A group's rule for a parent environment also applies to that environment's branches. If a member belongs to more than one custom permission group, Knock hides customer data when any assigned group enables obfuscation for that environment. If customer data obfuscation has been enabled at the environment level, that setting applies to every member and cannot be overridden by a permission group. Built-in roles have no per-role obfuscation setting. Only the environment setting applies to them. ### Assigning permission groups You assign custom permission groups when you [invite a member](/manage-your-account/managing-members) or change their role. You can also add members from a permission group's detail page, or assign groups through [directory sync](/manage-your-account/directory-sync#custom-permission-groups). - Choose either a built-in role **or** one or more custom permission groups. You cannot combine a built-in role with custom groups on the same member. - When you assign custom groups, the member's role becomes **Custom**. Their effective permissions are the union of every assigned group. - An environment is accessible to a custom member if any of their assigned groups grants access to it. - When [directory sync](/manage-your-account/directory-sync) is enabled, Knock uses your identity provider as the source of truth for synced users. If a synced user belongs to any group that maps to a live custom permission group, that custom assignment replaces their built-in role, including owner and admin. Archiving a custom permission group removes its grants from any members who had it assigned. If that was their only group, they keep the Custom role and have no permissions until you assign another group or a built-in role. Knock does not fall back to the support role. For directory-synced users, Knock recomputes the assignment when you archive a mapped group. Those users fall back to their highest-privilege mapped built-in role, or to support if none is mapped. ## Roles and permissions lookup table ## Audit logs Learn more about audit logs of actions performed on your Knock account. --- title: Audit logs description: Learn more about audit logs of actions performed on your Knock account. tags: ["audit", "audit log", "access log", "security"] section: Manage your account --- Search your account's audit log to review member actions and events. ## Overview See the{" "} data retention docs for more details on how Knock enforces this policy. } /> The account audit log lets you review actions performed by individual members of the account. In addition, each audit log includes events detailing who performed the action, when it happened, and information about the originating IP address and location of the action. **Note**: accessing an account's audit log is restricted to admins and account owners. ## Review your account audit log You can review your account audit log under your Knock account settings: `dashboard.knock.app//settings/audit-log` where the `slug` is your account identifier (e.g. `foo-corp`). Once there, you can filter the audit log by the **Actor** who performed the action, and/or the type of **Action** performed. Viewing audit logs in the dashboard ## Frequently asked questions When events occur that do not originate from a user action (like a side effect as the result of a merge), we attribute these events to a Knock-created "Migration Bot." Please contact the [Knock support team](mailto:support@knock.app) if you need to export the data inside your audit logs. We'd be happy to assist. ## Data obfuscation Learn more about how to protect your customer's production data in the Knock dashboard. --- title: Customer data obfuscation description: Learn more about how to protect your customer's production data in the Knock dashboard. tags: ["privacy", "data controls", "security", "privacy controls"] section: Manage your account --- Enable customer data obfuscation at the environment level, or through custom permission groups on Enterprise plans, to determine what data your team members can see in the Knock dashboard. ## Overview When you choose Knock to power your notifications, you're also choosing to trust us with your customer data and the data you pass in your notifications. This is why [security](/security) is a foundational priority for us at Knock, and it's also why we built our customer data obfuscation controls. With customer data obfuscation controls, the Knock dashboard automatically hides any data that might contain either user PII or customer proprietary data. This means that the only data your team members will see in the Knock dashboard is anonymous data such as UUIDs. Customer data obfuscation is managed via our backend, so users won't be able to get at this data through their browser consoles. You can enable customer data obfuscation in two ways: - **Environment-level.** Hide customer data for every team member in a given [environment](/concepts/environments). Use this to obfuscate production data while keeping development data visible for testing and debugging. - **Permission group.** On Enterprise plans, hide customer data for members of a [custom permission group](/manage-your-account/roles-and-permissions#custom-permission-groups), either in every environment or in specific environments. A preview of customer data obfuscation enabled ## Environment-level obfuscation Environment-level obfuscation applies to every member in that environment, including members with built-in roles. Built-in roles have no per-role obfuscation setting. To enable customer data obfuscation for an environment, go to the **Environments** page under the **Version control** section of your account settings in the Knock dashboard. Select the "..." for the environment you'd like to configure and click "Edit environment." ## Per-member role obfuscation Permission group data obfuscation is only available on our{" "} Enterprise plan. } /> Custom permission groups can enable customer data obfuscation under environment access. When enabled, all message, user, and object data will be obfuscated in the Knock dashboard for members of that group. Create and manage permission groups under **Settings** > **Permissions**. You set the obfuscation rule in the same editor you use for environment capabilities: 1. **Same access in every environment.** Enable obfuscation once to apply it across development, production, and any additional environments. 2. **Per-environment access.** Use granular mode to enable obfuscation in specific environments. For example, hide customer data in production for a support group, and leave it visible in development. A group's obfuscation rule for a parent environment also applies to that environment's branches. If a member belongs to more than one custom permission group, Knock hides customer data when any assigned group enables obfuscation for that environment. Learn more in our [roles and permissions documentation](/manage-your-account/roles-and-permissions#customer-data-obfuscation). ## Precedence If customer data obfuscation is enabled at the environment level, that setting applies to every member in the environment. A permission group cannot turn environment-level obfuscation off or reveal data the environment already hides. A permission group can only add obfuscation. When the environment setting is off, the group's rule determines whether members of that group see customer data in that environment. ## Account timezone Learn how to set the default timezone for your account. --- title: Account timezone description: Learn how to set the default timezone for your account. tags: ["default timezone", "recipient timezone"] section: Manage your account --- ## Overview You can set a default timezone for your account by navigating to the **General** page under your account settings and selecting a timezone. Account timezone settings in the Knock dashboard The account timezone will be used as a fallback for [send windows](/designing-workflows/send-windows) and [schedules](/concepts/schedules) if the recipient does not have a timezone set. If no account timezone is set, Knock falls back to `Etc/UTC`. ## Data retention How Knock enforces data retention policies on your account. --- title: Data retention description: How Knock enforces data retention policies on your account. tags: ["data", "retention"] section: Manage your account --- Knock applies a retention policy to some of the data within your account. Once data exceeds its retention period, it will no longer be accessible in the Knock Dashboard, the Management API, or the public API. Knock will eventually prune data that has aged out of its retention period. ## Data retention policies If custom retention windows are critical to your usage of Knock, please click the "Contact support" button at the top of this page to reach our support team. } /> Some Knock data is subject to a retention policy based on your account plan. These policies are: - **Enterprise plans**: 90 days - **All other plans**: 30 days Knock applies this plan-based retention policy to the following data: - [Audit logs](/manage-your-account/audit-logs) - [Message log data](/concepts/messages) - [Outbound webhook delivery logs](/developer-tools/outbound-webhooks/overview#reading-webhook-delivery-logs) - [Source event and action logs](/integrations/sources/overview#logging) - [Workflow run logs](/send-notifications/debugging-workflows#accessing-the-workflow-debugger) Additionally, Knock applies a universal, 30-day retention policy to the following data: - [API logs](/developer-tools/api-logs) ## Message log data retention details Message log data subject to your account's retention policy includes both the message log itself and associated metadata, including: - Message contents - Message events - Delivery logs - Activities However, this does not mean that the associated notification has been completely removed from Knock's systems. Your recipients can still interact with these older notifications, and you can still take some specific actions on them. 1. **In-app feed notifications are available indefinitely.** Even after your message log data has expired, you can still fetch the associated `FeedItem` data from the [Show feed endpoint](/api-reference/users/feeds/list_items) to deliver in-app notifications to your recipients. You can also still use the [message status update endpoints](/api-reference/messages/) to update the status of these feed items. These status updates will still generate new message events and trigger your [outbound webhooks](/developer-tools/outbound-webhooks/overview), even though you will not be able to view the message in the Knock Dashboard any longer. 2. **Tracking behavior will persist.** Your recipients can still click [tracked-links](/send-notifications/tracking#link-click-tracking). ## Custom domains Learn more about how to configure custom domains for your Knock account to use for link tracking, email open tracking, and the hosted preference center. --- title: Custom domains description: Learn more about how to configure custom domains for your Knock account to use for link tracking, email open tracking, and the hosted preference center. tags: ["custom domains", "domains", "email", "email tracking", "preference center"] section: Manage your account --- Knock supports setting up custom domains for your account to use for [link tracking](/send-notifications/tracking#how-it-works) (including short links in SMS and WhatsApp), [email open tracking](/send-notifications/tracking#email-open-tracking), and the [hosted preference center](/preferences/hosted-preference-center). Setting a custom domain is important for deliverability and branding. You run the risk of having your messages marked as spam if the sending domain and the domain you're routing to for links are different. By default, Knock uses [a variety of domains](/send-notifications/tracking#link-click-tracking-domains) for link tracking, short links in SMS and WhatsApp, and email open tracking. We also use the `p.knock.app` domain for the hosted preference center. These domains are shared across all accounts and are not configurable. Custom domains enable you to override these defaults and use your own domains. ## Setting up a custom domain We recommend using a subdomain of an existing domain with established email reputation (e.g., link.yourcompany.com) as a custom domain. If using a brand new domain, consider warming it first to build reputation and avoid deliverability issues.

Please note that we do not currently support apex domains (e.g., yourcompany.com) as custom domains. You must use a subdomain. } /> To set up a custom domain, navigate to the **Domains** page under the **Account** section of your Knock dashboard settings. Click "Add domain" under the tracking link or preference center section, depending on what you'd like to configure. You'll be prompted to enter the domain you want to use, such as `link.yourcompany.com` or `prefs.yourcompany.com`. Click "Continue to verification." A subdomain used for the preference center cannot also be used for tracked links, and vice versa. } /> Before you can complete the setup process, you'll need to navigate to your domain registrar and add a CNAME record to the DNS configuration for your domain. The setup modal in your Knock dashboard will display the CNAME record you need to add. The CNAME target depends on how you plan to use the domain: - **Link tracking, short links, and open tracking.** Point the CNAME record to `cname.knock.app`. - **Preference center.** Point the CNAME record to `prefs-cname.knock.app`. Here are some DNS configuration instructions for common providers: Once you've added the CNAME record, click "Check verification" to verify that the domain's DNS records are pointing to the correct value. Please note that it can take up to 48 hours for the DNS changes to propagate, so you may need to come back later and try again if verification does not succeed immediately. Once the domain is verified, you'll assign it to one or more [environments](/concepts/environments). You can assign the same custom domain to multiple environments, or you can use different custom domains across environments for testing purposes. For tracking links only, you will also need to select where the domain is used. Choose the relevant option(s) from the list of use cases: - **Link tracking.** These are links used for tracking link clicks within your notifications. Link tracking is used across many different channels, including email, in-app feed, SMS, and WhatsApp. - **Short links.** These are links used for generating tracked short links in SMS and WhatsApp messages. - **Open tracking.** These are links used for generating a 1x1 transparent tracking pixel, currently used only within email notifications. Once you've selected where the domain is used, click "Apply and activate" to save your changes. Assignment changes take effect immediately for both tracked link and preference center domains. ## Custom domains for the preference center When a custom preference center domain is assigned to an environment, any `{{vars.manage_preferences_url}}` variables in your templates will automatically use your custom domain instead of `p.knock.app`. Each environment can have at most one custom preference center domain. The same custom preference center domain can be assigned to multiple environments. To switch an environment to a different domain, verify the new domain and assign it. The previous domain will be automatically unassigned. ## When assignment changes take effect Assignment changes take effect immediately for both tracked link and preference center domains. New messages will use the currently-assigned custom domains for that environment. Already-sent messages are not affected. Links in already-sent messages continue to work as long as the domain they used (whether a default Knock domain or a previously assigned custom domain) remains verified in Knock and its DNS CNAME record stays intact. ## Rolling out your custom domains We strongly advise that you test out your custom domains in a non-production environment before rolling them out to your production environment. } /> When you're ready to roll out your custom domains to production, assign the custom domain to your production environment. ## Frequently asked questions Please check that the CNAME record is pointing to the correct value. You can also try to verify the domain again by clicking the "Check verification" button. It can take up to 48 hours for DNS changes on your domain to propagate. If after 48 hours the domain is still not verified, please reach out to our support team at [support@knock.app](mailto:support@knock.app?subject=Custom%20domain%20verification%20issue). Yes, you can use a brand new domain. However, we recommend that you warm up the domain before using it for production traffic so that it has a good reputation. No. Links in already-sent messages that used default Knock domains (for both tracked links and the preference center) continue to work after you set a custom domain. No, as long as the domain still exists in Knock and the DNS CNAME record is intact. Links stop working only if you delete the domain in Knock or modify the DNS record. Yes. Verify the new domain and assign it to the environment. The previous domain will be unassigned automatically. Links in already-sent messages will continue to work if that domain still exists in Knock and the DNS CNAME record is intact. No. [Commercial unsubscribe](/preferences/commercial-unsubscribe) works independently and is not affected by preference center custom domain configuration. ## Managing assets Learn how to upload and manage assets for use in your notification templates. --- title: Managing assets description: Learn how to upload and manage assets for use in your notification templates. tags: ["assets", "images", "uploads", "templates"] section: Manage your account --- ## Overview Assets are files that you upload to Knock for use in your notification templates. You can upload images to be hosted by Knock and reference them via URL in your templates. This is useful for including logos, icons, and other visual elements in your notifications. Account owners, admins, and members can manage assets from the **Assets** page in the Knock dashboard's settings. ## Uploading assets To upload a new asset: 1. Navigate to **Content** > **Assets** in your dashboard sidebar. 2. Click the "Upload image" button. 3. Select an image file from your computer. ### File requirements Keep the following requirements in mind when uploading assets: - **File size.** Each image must be 2 MB or less. - **File type.** Currently, only image files are supported (e.g., PNG, JPEG, GIF, SVG). You can upload as many images as you need to your Knock account. ## Using assets in templates Once you've uploaded an asset, you can copy its URL and use it in your notification templates. To copy the URL: 1. Navigate to **Content** > **Assets** in the Knock dashboard. 2. Find the asset you want to use. 3. Click the copy button to copy the asset URL to your clipboard. You can then paste this URL into your notification templates wherever you need to reference the image, such as in email templates or in-app notifications. ## Deleting assets To delete an asset: 1. Navigate to **Content** > **Assets** in the Knock dashboard. 2. Find the asset you want to delete. 3. Click the delete button to remove the asset. If you delete an asset that is referenced in a notification template, the image will no longer be displayed. Make sure to update any templates that reference an asset before deleting it. } /> ## Frequently asked questions Currently, only image files are supported. This includes common formats like PNG, JPEG, GIF, and SVG. In the future, we may support additional asset types. There is no limit to the number of assets you can upload to your Knock account. Each image file must be 2 MB or less. Account owners, admins, and members can upload and manage assets. ## Knock plans Understand differences between Knock plans and how usage is metered. --- title: "Knock plans and usage" description: "Understand differences between Knock plans and how usage is metered." tags: [ "billing", "pricing", "plans", "enterprise", "usage", "sandbox mode", "accounts", ] section: Manage your account --- Knock offers a free Developer plan and two paid plans, Starter and Enterprise. Most features are available across all three plans, with a small set reserved for Enterprise customers only. See our pricing page for current pricing, included volumes, and a feature comparison. ## Enterprise features Teams choose the Enterprise plan for higher message volume, multi-tenancy and security controls, and compliance requirements that call for a HIPAA business associate agreement (BAA). The following features are reserved for the Enterprise plan: - [Per-tenant preferences](/multi-tenancy/per-tenant-preferences) and [branding](/multi-tenancy/per-tenant-branding) - [Per-tenant guides](/in-app-ui/guides/create-guides) - [Translations (i18n)](/template-editor/translations) - [Batch render limits beyond 10 items](/designing-workflows/batch-function#setting-the-batch-render-limit-beyond-10) - [Custom permission groups](/manage-your-account/roles-and-permissions) - [SAML SSO](/manage-your-account/saml-sso) - [Directory sync](/manage-your-account/directory-sync) - [Data warehouse sync](/integrations/extensions/data-sync) - [Datadog](/integrations/extensions/datadog), [New Relic](/integrations/extensions/new-relic), [Segment](/integrations/extensions/segment), and [Heap](/integrations/extensions/heap) extensions ## How Knock meters usage Knock plans vary in terms of usage models: notification-based plans meter usage on messages sent, while recipient-based plans meter usage based on monthly notified recipients. Regardless of plan, usage may be accrued in any Knock [environment](/version-control/environments). ### Message counts A message is counted each time a notification is successfully delivered to a single user on a single channel. A workflow that notifies a user by both email and in-app results in two messages. If a channel step is not executed, there is no associated message. A channel step may be skipped due to user preferences, missing data, or other workflow logic. Due to the nature of [guides](/concepts/guides), guides usage is metered separately and based on active guides users. A user is considered an active guides user if they have received at least one guide in a given month. ### Non-production usage While using Knock, you'll often send messages in non-production environments to test changes. To avoid unexpected usage from testing, we recommend using [sandbox mode](/integrations/overview#sandbox-mode) or setting [channel conditions](/integrations/overview#channel-conditions) in your non-production environments. #### Sandbox mode When a channel is in sandbox mode, Knock does not pass messages downstream for delivery. Sandboxed messages are rendered for preview in the Knock dashboard, but do not count toward usage. Enable sandbox mode in a channel's configuration to apply it across an environment, or use the workflow [test runner's sandbox setting](/send-notifications/testing-workflows#test-run-settings) to apply it for a single run, regardless of how the channel itself is configured. #### Channel conditions Channel conditions enable you to scope a channel's delivery to specific conditions per environment. For example, you might set a condition on your email channel to restrict sending to recipients on your own domain while in a non-production environment. Unlike sandbox mode, channel conditions gate delivery selectively rather than suppress it entirely. Any message that satisfies a channel condition may contribute to usage. ## Managing multiple accounts Most customers need only one Knock account. As you map your resources into Knock, there are two ways to think about separating data within a single account: - **[Environments](/version-control/environments)** isolate your development lifecycle, from development through production. - **[Tenants](/multi-tenancy/overview)** scope notifications, branding, and preferences to your own customers, brands, or business units. Some customers maintain separate Knock accounts under a single Enterprise agreement, often for fully distinct products or business units, or when they need independent account-level data, members, and authentication. Each Knock account is independent: its resources, members, and analytics are scoped to that account, and no configuration is shared between accounts. Accounts can be synchronized programmatically via the [Management API](/developer-tools/management-api), but Knock does not provide any automatic synchronization between separate accounts. ## Frequently asked questions {/* prettier-ignore */} Dashboard users with billing permissions can review current usage under **Settings** > **Billing** in your Knock dashboard. See the pricing page for details about pricing and included usage per plan, or contact support with questions about your plan or invoice. Yes. You can use Knock's APIs to work with [tenant](/multi-tenancy/overview) data and trigger workflow runs for specific tenants on any plan. However, per-tenant preferences, branding, translations, and guides are gated to the Enterprise plan. The in-app channel doesn't support sandbox mode. Knock manages in-app delivery, so there's no downstream provider handoff to suppress. Use [channel conditions](/integrations/overview#channel-conditions) to manage when messages are created for in-app steps in your non-production environments. The guides channel does not support sandbox mode. To manage usage, use audience-limiting [targeting rules](/in-app-ui/guides/create-guides#targeting) while testing in non-production environments. ## Account deletion Learn how to permanently delete your Knock account and what happens to your data. --- title: Account deletion description: Learn how to permanently delete your Knock account and what happens to your data. tags: ["account", "deletion", "delete"] section: Manage your account --- ## Overview Account owners can permanently delete their Knock account from the dashboard. This action is irreversible. Once you delete your account, all data associated with it is permanently removed and cannot be recovered. ## Before you delete Consider the following before deleting your account: - **Production workflows.** Deactivate or migrate any production workflows that are still sending notifications. Deleting your account will immediately stop all notification delivery. - **API keys.** All API keys will be revoked immediately. Any applications or services using these keys will lose access to Knock. - **Account members.** All [account members](/manage-your-account/managing-members) will lose access. They will receive an email informing them that the account has been deleted. - **Integrations.** Any active channel integrations (email, push, chat, SMS) will stop working. - **Billing.** If you are on a paid plan, cancel your subscription before deleting your account. ## Data deletion When you delete your account, Knock permanently removes all data associated with it: - **Account data.** Workflows, templates, layouts, assets, branding, and environment configuration - **Recipient data.** Users, objects, tenants, and all associated data - **Message history and logs.** Message logs, API logs, workflow run logs, and audit logs For context on how Knock handles data during normal operation, see [data retention](/manage-your-account/data-retention). ## Account member deletion All members are removed from the account when it is deleted. Each member receives an email informing them that the account has been deleted. Learn more about [managing account members](/manage-your-account/managing-members). ## How to delete your account 1. Open your account settings in the Knock dashboard. 2. Navigate to the **General** section. 3. Find the **Delete account** option and follow the prompts to confirm. 4. Enter your account slug to confirm the deletion. 5. Confirm the deletion. Your account and all associated data will be permanently removed after 24 hours. --- # Overview Learn more about the integrations that Knock supports to send notifications and receive events. Send notifications to Email, SMS, Push, and Chat apps like Slack with a single API call and trigger them via events from your customer data platforms. --- title: Integrations overview description: Learn more about the integrations that Knock supports to send notifications and receive events. Send notifications to Email, SMS, Push, and Chat apps like Slack with a single API call and trigger them via events from your customer data platforms. tags: [ "sandbox mode", "sandbox", "channel conditions", "channel groups", "multiple providers", "change providers", "new domain", ] layout: integrations Section: Integrations --- You can use Knock to power sophisticated cross-channel notification workflows for your end users, triggered by your application or by events sent to Knock from various customer data platforms (CDPs) like Segment and RudderStack. ## Destination channels We support the following channel types today: - [Email](/integrations/email/overview) (such as Sendgrid, Postmark) - [In-app](/integrations/in-app/overview) (such as feeds and toasts) - [In-app guide](/in-app-ui/guides/overview) (such as banners, modals, and other in-product messaging) - [Push](/integrations/push/overview) (such as APNs, FCM) - [SMS](/integrations/sms/overview) (such as Twilio, Telnyx) - [Chat](/integrations/chat/overview) (such as Slack, Microsoft Teams, and Discord) - [Webhook](/integrations/webhook/overview) (send webhooks to custom channels or enable your own customers to configure webhooks in your product) See a full list of supported channel providers. ## Source integrations Sources connect external services to Knock so that events from those services can trigger workflows, identify users, and manage objects and tenants. Knock supports three categories of source integrations: - **Incoming webhooks.** Receive events from services like [Stripe](/integrations/sources/stripe), [Clerk](/integrations/sources/clerk), [WorkOS](/integrations/sources/workos), [PostHog](/integrations/sources/posthog), and [Supabase](/integrations/sources/supabase), or create a [custom source](/integrations/sources/custom) for any webhook provider. - **CDP integrations.** Forward `track` and `identify` events from platforms like [Segment](/integrations/sources/segment), [RudderStack](/integrations/sources/rudderstack), [Freshpaint](/integrations/sources/freshpaint), and [Jitsu](/integrations/sources/jitsu). - **Reverse ETL integrations.** Sync warehouse data from platforms like [Hightouch](/integrations/sources/hightouch), [Census](/integrations/sources/census), and [Polytomic](/integrations/sources/polytomic). See the [sources overview](/integrations/sources/overview) for configuration details, available actions, and debugging information. ## Platform extensions Knock can also integrate with other tools for debugging, monitoring, and more, including the following: - [Datadog](/integrations/extensions/datadog)/[New Relic](/integrations/extensions/new-relic). Receive Knock platform metrics for workflow & channel successes and errors. - [Segment](/integrations/extensions/segment)/[Heap](/integrations/extensions/heap). Send Knock notification events to downstream analytics tools. - [Vercel](/integrations/extensions/vercel). Synchronize your Knock API keys to one or more Vercel projects. ## Per environment configurations Unlike most parts of the Knock model, your integrations live at the account-level. This means that when you create a new integration in Knock (say, SendGrid), you only have to create it once. Once an integration is created, you can then configure it for each of your environments. There are a couple reasons we enable per-environment configuration for each. - If you use different instances of a service for each of your environments, you can use the respective API key or destination webhook URL of each within your different Knock environments. - You can provide environment-level config details to your channels. A common example is to add the suffix "(dev)" to the "From name" on all emails sent in your development environment. If you want to use the exact same configuration for a given channel across all environments, you can use the "Copy from" button to duplicate your configuration across environments. ## Sandbox mode Sometimes you don't want a message to be sent in a local or testing environment, but you _do_ want the ability to debug your messages and see what might have gone out. Sandbox mode enables you to generate and preview messages for a given channel without delivering them. When sandbox is enabled, Knock will never send the request to your downstream provider, blocking all delivery. To gate delivery selectively rather than suppress it entirely, use channel conditions instead. Sandbox mode is supported across all channel types (except for in-app) and can be enabled from the environment configuration view of a channel. ## Channel conditions You can use channel conditions to place a [condition](/concepts/conditions) on all instances of a channel within a given environment. As an example, if you want to ensure that your email channel only sends to recipients from your domain when it's executing in your staging environment, a channel-level condition would be great way to do that. To add a condition to a channel's environment configuration, navigate to **Channels and sources** in your dashboard account settings, click on the channel you'd like to update, then click "Manage configuration" next to the relevant environment. Select "Conditions" in the modal that is opened.
Managing channel conditions in Knock.
Managing channel conditions in Knock.
Knock will execute your channel conditions for every step using the given channel across all your workflow runs. Knock will join these channel conditions with any [step conditions](/designing-workflows/step-conditions) via an `AND`, meaning both channel and step conditions will need to pass evaluation for the step to execute. You can then use Knock's [conditions debugger](/concepts/conditions#debugging-conditions) to examine the evaluations of your channel conditions. ## Channel groups A channel group enables you to combine multiple channels of the same type into a single workflow step, using one notification template across all providers in the group. These are commonly used to combine push channels: instead of maintaining a separate channel step and template for APNs and FCM in every workflow, create a push channel group holding both connections and add it to your workflows as one push step. You can create and manage channel groups with the{" "} Management API. Dashboard support will soon be available. For help configuring a channel group, reach out to us at{" "} support@knock.app. } /> ### Channel group settings Every channel group has a `key` that identifies it, along with these settings: | Field | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | The human-readable name of the group. | | `channel_type` | The type of channels the group contains, such as `email`, `sms`, `push`, `chat`, or `http`. Every channel in a group shares this type. | | `operator` | Determines how many matching rules will result in message routing: `any` or `all`. When set to `any`, Knock routes to the first matching rule's channel only. When set to `all`, Knock routes to every matching rule's channel. Defaults to `any`. | | `channel_rules` | The ordered list of rules that determine which of the group's channels Knock routes to. | | `visible_in` | Where the group appears as a step destination: `workflow`, `broadcast`, or both. Defaults to both. | ### Channel rules Each rule in `channel_rules` points to one of the group's channels and sets the terms under which Knock routes to it: | Field | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `index` | The rule's position in the group's ordered list. | | `channel_key` | The key of the channel the rule routes to. | | `rule_type` | Determines when the channel should be used: `always`, `if`, or `unless`. When set to `always`, Knock routes to the channel on every workflow run. When set to `if` or `unless`, Knock routes to the channel conditionally based on the values of `variable`, `operator`, and `argument`. | | `variable` | The workflow property to evaluate. Not applicable for `always` rules. | | `operator` | The comparison to apply between the variable and the argument. Accepts any of the condition operators, plus `is_in_random_cohort` for percentage-based cohort routing. Not applicable for `always` rules. | | `argument` | The value to compare the variable against. Not applicable for `always` rules. | ### Random cohorts Random cohorts route recipients by percentage rather than by evaluating a property of the workflow run. Instead of making decisions based on who the recipient is, Knock assigns each recipient to a cohort and routes a percentage of them to a given channel. Splitting traffic this way enables you to shift volume to a new provider gradually rather than all at once. See our [sender domain migration tutorial](/tutorials/sender-domain-migration) for guidance on how to use cohort routing to warm a new email sending domain. A cohort rule takes the following values: | Field | Value | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rule_type` | `if` | | `variable` | `recipient.id` | | `operator` | `is_in_random_cohort` | | `argument` | The cohort threshold, as a number from 0 to 100 with up to one decimal place, such as `12.5`. With a single cohort rule, this is the percentage of recipients routed to this channel. | Knock hashes the recipient ID against the channel group to assign the cohort, so a given recipient consistently routes to the same channel across workflow runs. A single cohort rule only determines routing for the recipients inside that cohort, so it should be paired with at least one additional rule to catch remaining recipients. Set the channel group's `operator` to `any` so that messages are routed to only the first matching rule. To route the remaining recipients, create an `always` rule pointing at the fallback channel. The fallback rule should be ordered after the cohort rule, so that only recipients not belonging to the cohort will reach it. With this configuration, Knock routes the cohort to the first channel and everyone else to the second. #### Using more than one cohort To route traffic across more than two channels, add a cohort rule for each one. Every cohort rule in a group hashes against the same channel group, so each recipient holds a single position that all of the group's cohort rules compare against. Percentages act as cumulative thresholds rather than independent shares: a rule routes the recipients below its percentage that the rules ahead of it didn't already take. Order your cohort rules from the smallest percentage to the largest, and end the group with an `always` rule: | Rule | `argument` | Recipients routed | | ----------------------------------- | ---------- | ----------------- | | Cohort rule pointing at channel A | `12` | The first 12% | | Cohort rule pointing at channel B | `42` | The next 30% | | `always` rule pointing at channel C | — | The remaining 58% | Set each percentage to the running total of the shares ahead of it. Channel B receives 30% of recipients because its rule is set to `42`: the 12% already routed to channel A, plus its own 30%. Ordering a larger percentage ahead of a smaller one would prevent the smaller rule from ever matching, since every recipient below the smaller percentage is also below the larger one. ## Supported providers Find supported integration providers listed by channel type: [Email](/integrations/email/overview#supported-providers), [Chat](/integrations/chat/overview#supported-providers), [In-app](/integrations/in-app/overview#supported-providers), [Push](/integrations/push/overview#supported-providers), and [SMS](/integrations/sms/overview#supported-providers). We're continuously adding new providers, so if you don't see your provider listed, please reach out to us at [support@knock.app](mailto:support@knock.app). # Sources ## Overview Learn how to connect external services to Knock using incoming webhooks, CDPs, or reverse ETL integrations. --- title: Sources in Knock description: Learn how to connect external services to Knock using incoming webhooks, CDPs, or reverse ETL integrations. metaTitle: Source integrations overview metaDescription: Connect external services to Knock using incoming webhooks, CDPs like Segment, or reverse ETL tools like Hightouch and Census. section: Integrations > Sources layout: integrations --- Sources connect external services to Knock so that events from those services can drive actions such as triggering workflows, identifying users, and managing objects and tenants. Knock supports three categories of source integrations: - **Incoming webhooks.** Receive events from services like Stripe, Clerk, and WorkOS. Pre-built templates handle payload verification and provide default action mappings, or create a [custom source](/integrations/sources/custom) for any webhook provider. - **CDP integrations.** Forward `track` and `identify` events from platforms like Segment and RudderStack to trigger workflows and keep recipient data in sync. - **Reverse ETL integrations.** Sync warehouse data from platforms like Hightouch and Census to keep users, objects, and tenants up to date. ## Available sources - [Amplitude](/integrations/sources/amplitude) - [Census](/integrations/sources/census) - [Clay](/integrations/sources/clay) - [Clerk](/integrations/sources/clerk) - [Custom source](/integrations/sources/custom) - [Freshpaint](/integrations/sources/freshpaint) - [Hightouch](/integrations/sources/hightouch) - [Jitsu](/integrations/sources/jitsu) - [Polytomic](/integrations/sources/polytomic) - [PostHog](/integrations/sources/posthog) - [RudderStack](/integrations/sources/rudderstack) - [Segment](/integrations/sources/segment) - [Stripe](/integrations/sources/stripe) - [Supabase](/integrations/sources/supabase) - [WorkOS](/integrations/sources/workos) If you want us to add a new source to this list, please let us know at [support@knock.app](mailto:support@knock.app?subject=Integration%20Source%20Request). ## Configuring sources You can configure sources from the **Platform** > **Sources** page in your Knock dashboard. Initial creation of a source is managed at the account level of your Knock account, though you configure specific events and their actions within your Knock environments. A screenshot of where to find the Platform - Sources page in the Knock dashboard ### Per-environment source configuration Each source has a unique configuration for every Knock environment in your account. This makes it possible to connect your development source environment to your Knock development environment. Use the environment dropdown in the top header to switch between environments and view their configurations. The environment switcher dropdown in the Knock dashboard header, showing available environments for a source ## Triggering actions from source events When a source event is received, you can configure Knock to execute any of the following actions: | Action | Description | | ---------------------- | ---------------------------------------------------------------------- | | Trigger workflow | Start a [workflow](/concepts/workflows) run for one or more recipients | | Cancel workflow | Cancel an in-progress workflow run | | Identify user | Create or update a [user](/concepts/users) in Knock | | Subscribe user | Add a user to an [object's subscribers list](/concepts/subscriptions) | | Unsubscribe user | Remove a user from an object's subscribers list | | Set object | Create or update an [object](/concepts/objects) | | Delete object | Remove an object | | Set tenant | Create or update a [tenant](/concepts/tenants) | | Delete tenant | Remove a tenant | | Add audience member | Add a member to an [audience](/concepts/audiences) | | Remove audience member | Remove a member from an audience | ### Event-to-action mapping After events start flowing into Knock you can configure mappings that tell Knock which action to run and how to populate its parameters. Mappings use dot-notation paths to extract values from the incoming payload and map them to the fields required by each action. The event-to-action mapping configuration in the Knock dashboard, showing how incoming event fields map to action parameters For example, a Stripe `invoice.paid` event includes nested data about the invoice and customer: ```json { "type": "invoice.paid", "data": { "object": { "id": "in_1abc123", "customer": "cus_abc123", "customer_email": "jane@example.com", "amount_due": 4999, "currency": "usd", "subscription": "sub_xyz789" } } } ``` You could map `data.object.customer` to the `recipients` field and `data.object` to the `data` field when triggering a workflow, giving your templates access to the invoice amount, currency, and subscription details. ### Execution order for multiple mappings If a single event type has multiple action mappings, Knock executes them in a fixed priority order based on action type, not the order in which you create mappings in the dashboard. 1. `users_identify` 2. `users_delete` 3. `objects_set` 4. `objects_delete` 5. `tenants_set` 6. `tenants_delete` 7. `objects_subscribe` 8. `objects_unsubscribe` 9. `audiences_add_member` 10. `audiences_remove_member` 11. `workflows_trigger` This ordering is consistent across incoming webhook sources, including custom sources. For full details on configuring custom event types and field mappings, see the [custom source page](/integrations/sources/custom). ## Debugging source events In some cases, you may need to debug source event connections to ensure your integration is sending the correct payloads to Knock. See the{" "} data retention docs for more details on how Knock enforces this policy. } /> #### Event logs Event logs show the contents of each event sent into Knock. #### Action logs Action logs describe what (if any) action Knock took after receiving an event. Action logs are a helpful starting point when troubleshooting workflows or auditing actions Knock has taken for any given event. ## Source event idempotency By default, Knock processes every valid event received from your source. You can enable idempotency checks to deduplicate events that have already been received and processed. This is useful if you know your source may send duplicate events. If you're interested in configuring a different idempotency window for your account, please contact us at{" "} support@knock.app. } /> Idempotency configuration varies by source type. For pre-built webhook integrations, Knock auto-configures the idempotency key. For CDP sources and custom webhooks, you may need to enable idempotency and verify the key field. See your individual source page for setup details. ### Key validation Your idempotency keys must be valid strings no more than 255 characters in length. If an invalid key is found, Knock still ingests your source event but will not attempt to execute your event idempotently. In addition, Knock drops the invalid idempotency key and you will not see it appear in your event logs. ### How Knock handles idempotent events When Knock executes a source event with an idempotency key, it first checks whether a preceding execution should be replayed. Knock finds a preceding execution if it is recorded within the idempotency window with the same: - Idempotency key value - Event type - Integration source configuration - Knock environment in your account If no preceding execution is found, Knock executes your event normally and records that execution for future replay by the same idempotency key. However, if Knock fails to execute your source event, it will not record the execution. Knock only records successful event executions for idempotent replay. If Knock replays an event via an idempotency check, you will still see an event log for that execution. However, the log will not have any actions associated and Knock will label it as idempotent. ## Clerk Receive Clerk webhook events in Knock to trigger workflows and automate actions based on authentication and user management events. --- title: Clerk source description: Receive Clerk webhook events in Knock to trigger workflows and automate actions based on authentication and user management events. metaTitle: Clerk source integration metaDescription: Connect Clerk webhooks to Knock to trigger notification workflows from user sign-ups, session events, and organization changes. section: Integrations > Sources layout: integrations --- The Clerk source enables you to receive Clerk webhook events directly in Knock. Clerk sends webhook callbacks when events occur in your application, such as a new user signing up, a session being created, or an organization being updated. Knock verifies each payload using your Clerk webhook signing secret, identifies the event type, and executes the actions you configure. This integration is useful for building notifications around user lifecycle events: triggering welcome workflows when users sign up, notifying admins about organization changes, or syncing user data into Knock when profile details are updated. Knock supports ingesting any of Clerk's supported events, so you can map them to actions as your needs evolve. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A Clerk application with access to the Webhooks settings in the Clerk dashboard. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Clerk** template as the source type. The Sources page in the Knock dashboard Once you've selected **Clerk** as a source, you can select your desired action mappings. These are helpful defaults to get you started, but Knock can ingest any event Clerk sends and you can adjust your mappings at any time. Click the **Connect Clerk** button to continue. The Clerk source creation modal showing default action mappings for incoming events After creating the source, copy the webhook URL from the setup wizard for the environment you want to configure. The Clerk source setup wizard showing the webhook URL to copy In the Clerk dashboard, navigate to **Configure > Webhooks** and click "Add endpoint." Paste the Knock webhook URL and select the events you want Clerk to send. The Clerk dashboard Webhooks page with the Knock endpoint URL and event subscriptions After creating the endpoint in Clerk, Clerk provides a signing secret. Copy this value and paste it into the **Signing secret** field in your Knock source environment configuration. The Clerk dashboard webhook endpoint detail page showing the signing secret Once configured, Clerk sends webhook events to Knock in real time. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events Clerk sends events for user, session, and organization lifecycle changes. Below are common events you might map to actions in Knock. You can enable or disable individual event types from the source environment configuration. | Event type | Description | | ---------------------- | --------------------------------- | | `user.created` | A new user signed up | | `user.updated` | User profile details were updated | | `user.deleted` | A user was deleted | | `organization.created` | A new organization was created | | `organization.updated` | Organization details were updated | See the Clerk webhook events documentation for the full list of available events. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from Clerk. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If a single event type maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map Clerk events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock automatically configures idempotency for the Clerk source so duplicate events are not processed twice. By default, Knock uses `headers.svix-id` from the Clerk webhook payload as the idempotency key. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## PostHog Receive PostHog webhook events in Knock to trigger workflows and automate actions based on product analytics events. --- title: PostHog source description: Receive PostHog webhook events in Knock to trigger workflows and automate actions based on product analytics events. metaTitle: PostHog source integration metaDescription: Connect PostHog webhooks to Knock to trigger notification workflows from product analytics actions and custom events. section: Integrations > Sources layout: integrations --- The PostHog source enables you to receive PostHog webhook events directly in Knock. PostHog sends webhook callbacks when actions fire in your product analytics, such as a user completing a key funnel step, a feature flag change, or a custom event you define. Knock verifies each payload, identifies the event type, and executes the actions you configure. This integration is useful for building notifications triggered by product behavior: reaching out to users who complete onboarding milestones, alerting your team when a feature flag is changed, or triggering re-engagement workflows based on usage patterns. Knock supports ingesting any of PostHog's supported events, so you can map them to actions as your needs evolve. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A PostHog project with access to the Webhooks settings in the PostHog app. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **PostHog** template as the source type. The Sources page in the Knock dashboard Once you've selected **PostHog** as a source, you can select your desired action mappings. The defaults map PostHog events like `$identify` and `$set` to identify users in Knock. These are helpful defaults to get you started, but Knock can ingest any event PostHog sends and you can adjust your mappings at any time. Click the **Connect PostHog** button to continue. The PostHog source creation modal showing default action mappings for incoming events After creating the source, copy the event ingestion URL from the setup wizard. You will paste this into PostHog in the next steps. The PostHog source setup wizard showing the event ingestion URL to copy In PostHog, navigate to **Data pipelines** and click **New destination**. Search for "Knock" and click **Create** on the Knock destination. The PostHog Data pipelines page showing the Knock destination in the new destination search results On the Knock destination configuration page, paste the Knock event ingestion URL into the **Knock.app webhook destination URL** field. Set the **User ID** field to the property that maps to your Knock user IDs (for example, `{person.id}`). You can optionally configure filters to limit which events are sent, add event matchers to target specific actions, and set up attribute mappings. When ready, click **Create & enable**. The PostHog Knock destination configuration page showing webhook URL, User ID, filters, and event matchers Once configured, PostHog sends events to Knock based on your destination filters and event matchers. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events PostHog webhook events are driven by the actions and events you configure in your PostHog project. Common scenarios include: | Event type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------ | | Action webhooks | Fired when a PostHog action matches (for example, a user clicks a specific button or views a key page) | | Custom events | Fired when your application sends a custom event to PostHog that matches your webhook criteria | | Feature flag changes | Fired when a feature flag is updated | The exact event types depend on how you configure your PostHog actions and webhook destinations. See the PostHog events documentation for more details on available event types and payload formats. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from PostHog. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If a single event type maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map PostHog events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock automatically configures idempotency for the PostHog source so duplicate events are not processed twice. By default, Knock uses `body.messageId` from the PostHog webhook payload as the idempotency key. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Stripe Receive Stripe webhook events in Knock to trigger workflows and automate actions based on payment and subscription lifecycle events. --- title: Stripe source description: Receive Stripe webhook events in Knock to trigger workflows and automate actions based on payment and subscription lifecycle events. metaTitle: Stripe source integration metaDescription: Connect Stripe webhooks to Knock to trigger notification workflows from payment, subscription, and customer lifecycle events. section: Integrations > Sources layout: integrations --- The Stripe source enables you to receive Stripe webhook events directly in Knock. Stripe sends webhook callbacks when events occur in your account, such as a successful payment, a subscription change, or a customer update. Knock verifies each payload using your Stripe webhook signing secret, identifies the event type, and executes the actions you configure. This integration is useful for building notifications around payment lifecycle events: alerting customers about successful charges, notifying your team about failed payments, or triggering onboarding workflows when new customers are created. Knock supports ingesting any of Stripe's supported events, so you can map them to actions as your needs evolve. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A Stripe account with access to the Webhooks settings in the Stripe dashboard. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Stripe** template as the source type. The Sources page in the Knock dashboard Once you've selected **Stripe** as a source, you can select your desired action mappings. The defaults map common Stripe events like `customer.created` to identify users and `invoice.created` and `invoice.paid` to trigger workflows. These are helpful defaults to get you started, but Knock can ingest any event Stripe sends and you can adjust your mappings at any time. Click the **Connect Stripe** button to continue. The Stripe source creation modal showing default action mappings for incoming events After creating the source, copy the event ingestion URL from the setup wizard. You will paste this into the Stripe dashboard in the next steps. The Stripe source setup wizard showing the event ingestion URL to copy In the Stripe dashboard, click the **Developers** button in the bottom left corner of the sidebar to open the Workbench. The Stripe dashboard with the Developers Workbench expanded in the bottom left sidebar In the Workbench, click the **Webhooks** tab. Click **Add destination** to start creating a new webhook endpoint. The Stripe Workbench Webhooks page with the Add destination button In the "Create an event destination" wizard, select **Your account** as the event source. Search for and select the events you want to send to Knock (for example, `customer.created`). Click **Continue** to proceed. The Stripe event destination wizard showing event selection with customer.created checked Select **Webhook endpoint** as the destination type and click **Continue**. The Stripe event destination wizard showing Webhook endpoint selected as the destination type Give the destination a name (for example, "Knock"), paste the Knock event ingestion URL you copied earlier into the **Endpoint URL** field, and click **Create destination**. The Stripe event destination wizard showing the destination name and Knock endpoint URL After creating the destination, Stripe displays the destination detail page with a **Signing secret** (starts with `whsec_`). Copy this value and paste it into the **Signing secret** field in your Knock source environment configuration. The Stripe Workbench webhook destination detail page showing the signing secret Once configured, Stripe sends webhook events to Knock in real time. You can verify that events are arriving by checking the event logs on the source environment page. ## Testing with the Stripe CLI The Stripe Workbench includes a built-in Shell that you can use to send test events to your webhook endpoint. Use the `stripe trigger` command followed by the event name to generate a test event: ```bash stripe trigger customer.created stripe trigger customer.subscription.trial_will_end ``` This creates the necessary fixture data in your Stripe sandbox and sends the corresponding webhook event to all configured destinations, including Knock. The Stripe Workbench built-in Shell showing stripe trigger commands sending test events ## Pre-configured events Stripe sends a wide range of event types. Below are common events you might map to actions in Knock. You can enable or disable individual event types from the source environment configuration. | Event type | Description | | ------------------ | ---------------------------- | | `invoice.paid` | An invoice payment succeeded | | `invoice.created` | An invoice was created | | `customer.created` | A new customer was created | See the Stripe event types documentation for the full list of available events. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from Stripe. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If a single event type maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map Stripe events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock automatically configures idempotency for the Stripe source so duplicate events are not processed twice. By default, Knock uses `body.id` from the Stripe webhook payload as the idempotency key. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Supabase Receive Supabase database webhook events in Knock to trigger workflows and automate actions based on row-level changes. --- title: Supabase source description: Receive Supabase database webhook events in Knock to trigger workflows and automate actions based on row-level changes. metaTitle: Supabase source integration metaDescription: Connect Supabase database webhooks to Knock to trigger notification workflows from row-level insert, update, and delete events. section: Integrations > Sources layout: integrations --- The Supabase source enables you to receive Supabase database webhook events directly in Knock. Supabase sends webhook callbacks when row-level changes occur in your database tables, such as inserts, updates, or deletes. Knock identifies the event type and executes the actions you configure. This integration is useful for building notifications that react to database changes: alerting users when a record relevant to them is created, triggering workflows when data is updated, or automating cleanup actions when rows are deleted. Knock pre-processes each Supabase webhook to compile the schema, table, and operation into a unique event type (for example, `auth.users:INSERT`), so you can create targeted action mappings for any table and operation combination. Supabase database webhooks do not include a signing secret or built-in payload verification mechanism. Any request that reaches your Knock webhook URL will be accepted. Consider restricting access at the network level or validating payloads in your workflow logic if this is a concern. } /> ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A Supabase project with access to the Database Webhooks settings in the Supabase dashboard. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Supabase** template as the source type. The Sources page in the Knock dashboard Once you've selected **Supabase** as a source, you can select your desired action mappings. The default mappings use Supabase table events like `auth.users:INSERT` to identify users in Knock. These are helpful defaults to get you started, but Knock can ingest any event Supabase sends and you can adjust your mappings at any time. Click the **Connect Supabase** button to continue. The Supabase source creation modal showing default action mappings for incoming events After creating the source, copy the event ingestion URL from the setup wizard. You will paste this into the Supabase dashboard in the next steps. The Supabase source setup wizard showing the event ingestion URL to copy In the Supabase dashboard, select your project and navigate to **Integrations** > **Database Webhooks**. Click the **Webhooks** tab, then click **Create a new hook**. The Supabase dashboard Database Webhooks page with the Create a new hook button In the webhook creation form, give the webhook a name and select the table you want to monitor (for example, `auth.users`). Under **Events**, check the operations you want to trigger notifications for: **Insert**, **Update**, and **Delete**. Under **Type of webhook**, select **HTTP Request**. The Supabase create webhook dialog showing table selection and event checkboxes In the **HTTP Request** section, set the method to **POST** and paste the Knock event ingestion URL you copied earlier into the **URL** field. You can optionally add custom HTTP headers. Click **Create webhook** to finish. The Supabase create webhook dialog showing the HTTP Request URL field with the Knock webhook URL Once configured, Supabase sends webhook events to Knock when the specified database changes occur. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events Supabase database webhooks fire based on the table operations you select when creating the hook. Knock pre-processes each incoming payload to compile the schema, table name, and operation type into a single event type using the format `{schema}.{table}:{operation}`. This enables you to create specific action mappings for each table and operation combination. | Event type | Description | | ------------------- | ---------------------------------------- | | `auth.users:INSERT` | A new row was inserted into `auth.users` | | `auth.users:UPDATE` | A row in `auth.users` was updated | | `auth.users:DELETE` | A row was deleted from `auth.users` | The exact event types depend on which tables and operations you configure in your Supabase database webhooks. Each event payload includes the row data (both old and new values for updates). See the Supabase database webhooks documentation for payload structure details. For more on how Knock pre-processes incoming payloads, see the [pre-processing](/integrations/sources/custom#preprocessing) section of the custom source page. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from Supabase. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If a single event type maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map Supabase events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Supabase database webhooks do not include a built-in idempotency key. You can enable idempotency checks and configure a key field from the **Settings** tab in your source environment configuration if your payload contains a unique identifier you want to use for deduplication. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Amplitude Send Amplitude events, user updates, and cohort membership changes to Knock to trigger workflows and keep users and audiences in sync. --- title: Amplitude source description: Send Amplitude events, user updates, and cohort membership changes to Knock to trigger workflows and keep users and audiences in sync. metaTitle: Amplitude source integration metaDescription: Connect Amplitude Webhooks Streaming and Cohort Webhooks to Knock to trigger workflows, identify users, and sync cohort membership to audiences. section: Integrations > Sources layout: integrations --- The Amplitude source enables you to send analytics events, user updates, and cohort membership changes from [Amplitude](https://amplitude.com) to Knock. Knock receives Amplitude's default webhook payloads, verifies a shared secret, identifies the incoming event type, and runs the actions you configure. You can connect either or both of Amplitude's outbound webhook integrations to the same Knock source URL: - [Webhooks Streaming](https://amplitude.com/docs/data/destination-catalog/webhooks) sends product events and user property updates as Amplitude ingests them. - [Cohort Webhooks](https://amplitude.com/docs/data/destination-catalog/cohort-webhooks) sends batches when users enter or exit an Amplitude cohort. Use these integrations to trigger notification workflows from product behavior, keep Knock user properties aligned with Amplitude, or use an Amplitude cohort as a [Knock audience](/concepts/audiences). ## How verification works Amplitude supports custom headers but does not sign Webhooks Streaming or Cohort Webhook requests. The Knock source verifies a shared secret sent as a Bearer token in the `Authorization` header: ```text Authorization: Bearer ``` The signing secret in Knock must match the token configured in every Amplitude destination that sends data to the source. Treat the secret like an API key, store it somewhere safe, and rotate it if it leaks. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - An Amplitude project with access to **Data** > **Catalog** > **Destinations**. - A paid Amplitude plan if you want to use Cohort Webhooks. ## Set up the source in Knock Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment, then select the **Amplitude** template. The template includes mappings that identify users and add or remove cohort members from Knock audiences. You can select which mappings to create and change them later. Click **Connect Amplitude** to continue. Copy the event ingestion URL from the setup wizard for the environment you want to configure. Both Amplitude webhook integrations can send to this URL. Generate a strong random string, such as with `openssl rand -hex 32`, and paste it into the **Signing secret** field in the Knock source environment. You will use the same value in the `Authorization` header in Amplitude. ## Send events and users with Webhooks Streaming Use Amplitude's **Webhook: Events · User Properties** destination to stream product events and user profile updates to Knock in real time. This destination sends data as Amplitude ingests it; it does not backfill events that Amplitude received before you enabled the destination. In Amplitude, navigate to **Data** > **Destinations**, then click **Add Destination**. Search for "Webhook," select **Webhook: Events · User Properties**, enter a sync name, and click **Create Sync**. Paste the Knock event ingestion URL into the webhook URL field. Add a custom header named `Authorization` with the value `Bearer `, using the secret you set in Knock. Under **Send Events**, enable **Events are sent to Webhook**. Keep the default Amplitude event payload, then select and filter the events you want Knock to receive. Each event retains its Amplitude `event_type`, so you can add a Knock action mapping for any event you send. Under **Send Users**, enable **User updates are sent to Webhook** if you want to identify users in Knock. Keep the default Amplitude user payload. Amplitude sends a user payload when it receives an event and when it receives an [Identify API](https://amplitude.com/docs/apis/analytics/identify) call. Finally, set the destination status to **Enabled** and click **Save**. Send a matching event or Identify API update, then check the **Logs** tab on the Knock source environment to confirm delivery. An event payload includes fields such as `event_type`, `user_id`, `event_properties`, `groups`, and a unique `uuid`: ```json { "user_id": "user_123", "uuid": "bf0b9b2a-304d-11e6-934f-22000b56058f", "event_type": "Order Completed", "event_time": "2026-07-23T16:20:30.123Z", "event_properties": { "order_id": "order_789", "total": 149.99 }, "groups": { "workspace": "workspace_123" } } ``` User payloads do not include `event_type`, so Knock normalizes them to `$identify`. ## Sync cohorts with Cohort Webhooks Cohort Webhooks send batched membership changes to Knock. Knock maps entry batches to `cohort.entered` and exit batches to `cohort.exited`. In Amplitude, open **Data** > **Destinations**, then click **Add Destination**. Search for "Webhook," select **Webhook: Cohorts**. Enter a display name, and click **Create Sync**.In the **Cohorts** section, select **Webhook** and create a destination. Enter a display name. Then, paste the same Knock event ingestion URL into the webhook URL field. Add a custom `Authorization` header with the value `Bearer `, and keep the default Amplitude cohort payload. Then click **Save**. Open the cohort you want to export, click **Sync**, choose **Webhook**, and select the destination you created. You can include up to 50 user properties in the payload. Choose a one-time, recurring (hourly or daily), or real-time sync, then click **Sync**. Amplitude supports [three cohort sync behaviors](https://amplitude.com/docs/data/destinations/syncs): | Cadence | Behavior | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | One-time | Sends the cohort membership once. Use this for a one-off campaign or test. | | Hourly or daily | Recalculates the cohort on a schedule and sends membership additions and removals. | | Real-time | Sends the initial cohort population, then checks for entry and exit changes every minute. The initial population can take longer for a large cohort. | Amplitude sends cohort updates in batches with a unique `message_id`: ```json { "cohort_name": "Highly engaged users", "cohort_id": "cohort_123", "in_cohort": true, "computed_time": "1784823900", "message_id": "message_123", "users": [{ "user_id": "user_123" }, { "user_id": "user_456" }] } ``` ## Default action mappings The Amplitude template includes these mappings: | Event type | Knock action | Default behavior | | ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `$identify` | Identify user | Uses `user_id`, falling back to `device_id`, as the Knock user ID. Maps `user_properties.name`, `user_properties.email`, and the full `user_properties` object. | | `cohort.entered` | Add audience members | Adds every user in the batch to the audience keyed `amplitude-`. Creates the audience if it does not exist. | | `cohort.exited` | Remove audience members | Removes every user in the batch from the audience keyed `amplitude-`. | The cohort name can change in Amplitude, so Knock uses the stable `cohort_id` when it constructs the audience key. Cohort membership payloads must include Amplitude `user_id` values to map members to Knock users. ### Align user identities Set Amplitude `user_id` to the same stable identifier you use as the Knock user ID. This keeps product events, user updates, cohort membership, and workflow recipients attached to the same Knock user. See Amplitude's [user identity documentation](https://amplitude.com/docs/get-started/identify-users) for its identity model and user ID recommendations. Amplitude can send an Identify payload with only a `device_id`. In that case, the default mapping creates or updates a Knock user keyed by that device ID. Knock does not merge that recipient with a later recipient keyed by Amplitude `user_id`, so use Amplitude `user_id` for signed-in users whenever possible. ## Map product events to workflows Knock preserves the `event_type` for each streamed product event. After an event arrives, open the source's **Mappings** tab, select the event type, and create an action mapping. For the `Order Completed` example above, you could configure a **Trigger workflow** action with these fields: | Action field | Payload path | | ----------------- | ------------------------------------ | | Workflow | Select your order-completed workflow | | Recipients | `body.user_id` | | Data | `body.event_properties` | | Tenant (optional) | `body.groups.workspace` | This triggers the selected workflow for the Amplitude user and makes properties such as `order_id` and `total` available in the workflow's templates. If your events can be device-only, choose a field that always resolves to a Knock user ID or normalize the recipient in the source preprocessing script. ## Map Amplitude groups to objects or tenants Amplitude group types and Knock [object collections](/concepts/objects) are customer-defined. The Amplitude template does not assume that a group such as `workspace`, `account`, or `project` should become a particular object collection or [tenant](/concepts/tenants). For an event with `groups.workspace` and `group_properties.workspace`, you can [customize the Amplitude webhook payload](https://amplitude.com/docs/data/destination-catalog/webhooks#freemarker-templating-language) to include the Knock collection name, then add a **Set object** mapping: ```json { "object_collection": "workspaces", "groups": { "workspace": "workspace_123" }, "group_properties": { "workspace": { "name": "Acme Corp", "plan": "enterprise" } } } ``` | Set object field | Payload path | | ----------------- | -------------------------------------- | | Collection | `body.object_collection` | | Object ID | `body.groups.workspace` | | Name | `body.group_properties.workspace.name` | | Custom properties | `body.group_properties.workspace` | If your Amplitude group represents a Knock tenant instead, create a **Set tenant** mapping with `body.groups.workspace` as the tenant ID and `body.group_properties.workspace` as its properties. Adjust every path and collection name to match your Amplitude taxonomy and Knock data model. ## Retries and idempotency Amplitude [retries failed event and user deliveries](https://amplitude.com/docs/data/destination-catalog/webhooks#amplitudes-retry-mechanism) nine times over four hours after the first attempt. It also retries `5xx` and `429` responses within each attempt. Cohort Webhooks can retry timed-out batches, which can produce duplicate deliveries. Knock configures idempotency for the Amplitude source using: - `body.uuid` for streamed product events. - `body.message_id` for cohort batches. - A combination of the user or device ID and `body.event_time` for user updates. Knock ignores a duplicate while its idempotency key remains in the source's idempotency window. You can change this behavior from the **Settings** tab. See [source event idempotency](/integrations/sources/overview#source-event-idempotency) for the window and key rules. ## Debugging Open **Platform** > **Sources**, select the Amplitude source, and use its **Logs** tab to inspect received events and the actions they produced. Common issues include: - **Verification failed.** Confirm that Amplitude sends `Authorization: Bearer ` and that the token matches the signing secret in the current Knock environment. - **No events arrive.** Confirm that the Amplitude destination is enabled and that the event matches its filters. Webhooks Streaming does not send events that Amplitude ingested before you enabled the destination. - **An action was skipped.** Open the action log and check for a missing required field. Compare the mapping's dot-notation path with the received payload. - **Cohort members do not match users.** Confirm that Amplitude `user_id` values match Knock user IDs and that the cohort payload includes those IDs. You can modify the default action mappings or add mappings for any Amplitude event type. For details on field paths, preprocessing, and available actions, see the [custom source](/integrations/sources/custom) and [sources overview](/integrations/sources/overview#triggering-actions-from-source-events). ## WorkOS Receive WorkOS webhook events in Knock to trigger workflows and automate actions based on directory sync and SSO events. --- title: WorkOS source description: Receive WorkOS webhook events in Knock to trigger workflows and automate actions based on directory sync and SSO events. metaTitle: WorkOS source integration metaDescription: Connect WorkOS webhooks to Knock to trigger notification workflows from directory sync and SSO lifecycle events. section: Integrations > Sources layout: integrations --- The WorkOS source enables you to receive WorkOS webhook events directly in Knock. WorkOS sends webhook callbacks when events occur in your enterprise integrations, such as a user being provisioned via directory sync or an SSO connection being activated. Knock verifies each payload using your WorkOS webhook signing secret, identifies the event type, and executes the actions you configure. This integration is useful for automating provisioning and access workflows: identifying users in Knock when they are provisioned through a directory, notifying admins about SSO configuration changes, or triggering onboarding workflows when new directory users appear. Knock supports ingesting any of WorkOS's supported events, so you can map them to actions as your needs evolve. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A WorkOS account with access to the Webhooks settings in the WorkOS dashboard. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **WorkOS** template as the source type. The Sources page in the Knock dashboard Once you've selected **WorkOS** as a source, you can select your desired action mappings. These are helpful defaults to get you started, but Knock can ingest any event WorkOS sends and you can adjust your mappings at any time. Click the **Connect WorkOS** button to continue. The WorkOS source creation modal showing default action mappings for incoming events After creating the source, copy the webhook URL from the setup wizard for the environment you want to configure. The WorkOS source setup wizard showing the webhook URL to copy In the WorkOS dashboard, navigate to **Webhooks** and click "Create webhook." Paste the Knock webhook URL and select the events you want WorkOS to send. The WorkOS dashboard Webhooks page with the Knock endpoint URL and event subscriptions After creating the endpoint in WorkOS, WorkOS provides a signing secret. Copy this value and paste it into the **Signing secret** field in your Knock source environment configuration. The WorkOS dashboard webhook endpoint detail page showing the signing secret Once configured, WorkOS sends webhook events to Knock in real time. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events WorkOS sends events for directory sync and SSO lifecycle changes. Below are common events you might map to actions in Knock. You can enable or disable individual event types from the source environment configuration. | Event type | Description | | -------------------------- | ----------------------------------------- | | `dsync.user.created` | A user was provisioned via directory sync | | `dsync.user.updated` | A directory sync user was updated | | `dsync.user.deleted` | A directory sync user was deprovisioned | | `dsync.group.created` | A group was created via directory sync | | `dsync.group.updated` | A directory sync group was updated | | `dsync.group.deleted` | A directory sync group was deleted | | `dsync.group.user_added` | A user was added to a directory group | | `dsync.group.user_removed` | A user was removed from a directory group | | `connection.activated` | An SSO connection was activated | | `connection.deactivated` | An SSO connection was deactivated | See the WorkOS events documentation for the full list of available events. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from WorkOS. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If a single event type maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map WorkOS events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock automatically configures idempotency for the WorkOS source so duplicate events are not processed twice. By default, Knock uses `body.id` from the WorkOS webhook payload as the idempotency key. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Segment Receive Segment webhook events in Knock to trigger workflows and keep user data in sync with track and identify events. --- title: Segment source description: Receive Segment webhook events in Knock to trigger workflows and keep user data in sync with track and identify events. metaTitle: Segment source integration metaDescription: Connect Segment webhooks to Knock to trigger notification workflows from track and identify events. section: Integrations > Sources layout: integrations --- This is the updated Segment source integration. If you are looking for the previous version that uses manual webhook destinations, see the{" "} legacy Segment source. } /> The Segment source enables you to receive Segment webhook events directly in Knock. Segment sends webhook callbacks when track and identify events flow through your workspace. Knock verifies each payload using your Segment webhook signing secret, identifies the event type, and executes the actions you configure. This integration is useful for triggering notification workflows from track events, such as alerting users when key actions happen in your product, or syncing user data into Knock from identify events so recipient profiles stay up to date. Knock supports ingesting any of Segment's supported events, so you can map them to actions as your needs evolve. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A Segment workspace with access to webhook or destination settings. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Segment** template as the source type. The Sources page in the Knock dashboard Once you've selected **Segment** as a source, you can select your desired action mappings. These are helpful defaults to get you started, but Knock can ingest any event Segment sends and you can adjust your mappings at any time. Click the **Connect Segment** button to continue. The Segment source creation modal showing default action mappings for incoming events After creating the source, Knock displays a setup wizard. Copy the event ingestion URL from step 1 of the wizard. You will paste this into your Segment destination configuration in a later step. The Segment source setup wizard showing the event ingestion URL to copy In your Segment workspace, navigate to **Destinations** and click "Add Destination." Search for **Webhooks (Actions)** and select it. Choose the data source you want to receive events from, give the destination a name, and click "Create Destination." In the Segment destination, navigate to the **Mappings** tab and click "New Mapping." Under Actions, click "Send." Configure the event conditions to determine which events are forwarded to Knock. For example, you can add conditions for **Event type is Track** and **Event type is Identify**, and set the top operator to **any** to send both event types. After selecting your events, proceed to the **Map fields** step. Paste the Knock event ingestion URL you copied earlier into the **URL** field and set the **Method** to `POST`. The Segment Map fields step showing the Knock webhook URL in the URL field Unlike other webhook providers, Segment does not auto-generate a signing secret. You need to generate your own secret and enter it in both Segment and Knock. In your Segment destination, navigate to the **Settings** tab. Enter your generated secret in the **Shared Secret** field and click "Save Changes." The Segment Webhooks (Actions) destination Settings page showing the Shared Secret field Then, back in Knock, paste the same secret into the **Signing secret** field in step 2 of the setup wizard, or in the source environment configuration under the **Settings** tab. Once configured, Segment sends webhook events to Knock in real time. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events Segment sends events based on the Segment spec. Below are common event types you might map to actions in Knock. | Event type | Description | | ---------- | ----------------------------------- | | `track` | A user performed an action | | `identify` | User traits were created or updated | See the Segment spec documentation for the full list of available event types and their schemas. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from Segment. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If you need to map Segment events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock uses the `messageId` field from the Segment event spec as the idempotency key. Segment includes a `messageId` in every track and identify event by default, so no additional field configuration is needed. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Shopify Receive Shopify webhook events in Knock to trigger workflows and automate actions based on customer and order lifecycle events. --- title: Shopify source description: Receive Shopify webhook events in Knock to trigger workflows and automate actions based on customer and order lifecycle events. metaTitle: Shopify source integration metaDescription: Connect Shopify webhooks to Knock to trigger notification workflows from customer and order lifecycle events in your store. section: Integrations > Sources layout: integrations --- The Shopify source enables you to receive Shopify webhook events directly in Knock. Shopify sends webhook callbacks when events occur in your store, such as a new customer signing up, an order being placed, or a fulfillment being shipped. Knock verifies each payload using your Shopify webhook signing secret, identifies the event topic from the `X-Shopify-Topic` header, and executes the actions you configure. This integration is useful for building ecommerce notifications: triggering order confirmations when customers check out, syncing Shopify customer data into Knock when profiles are created or updated, and powering downstream notifications like shipping updates or cancellation alerts as you add the topics you need. Knock supports ingesting any of Shopify's supported webhook topics, so you can map them to actions as your needs evolve. ## Two ways to configure Shopify webhooks Shopify supports two distinct ways to create webhooks, and Knock works with both. The payload format, headers, and signature verification are identical — only the setup path differs. Pick the option that matches how you operate your Shopify store. ### From the Shopify admin panel A store owner adds webhook destinations manually in the Shopify admin panel under **Settings > Notifications > Webhooks**. The signing secret is displayed in that same section and is shared across every webhook on the store. This path is the best fit for merchants who run their own store and want to forward events to Knock without writing a Shopify app. [Learn more.](#send-webhooks-from-the-shopify-admin-panel) ### From a Shopify app A Shopify app developer declares webhook subscriptions in the app's `shopify.app.toml` configuration, or creates them at runtime using the `webhookSubscriptionCreate` GraphQL mutation on the Admin API. The signing secret is the app's API client secret, so a single secret verifies webhooks from every store that installs the app. This path is the best fit for teams building a Shopify app or managing webhook subscriptions across many stores. [Learn more.](#send-webhooks-from-a-shopify-app) Whichever path you choose, the steps for setting up the source in Knock are the same. Once the source exists in Knock, follow the section below that matches your setup. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - Either access to the Shopify admin panel for the store you want to ingest events from (for the admin path), or a Shopify app with permission to create webhook subscriptions on the stores you want to ingest from (for the app path). ## Set up the source in Knock These steps are the same regardless of which Shopify path you use. Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Shopify** template as the source type. Once you've selected **Shopify** as a source, you can select your desired action mappings. The defaults identify Knock users from `customers/create` and `customers/update` events, identify the customer attached to an `orders/create` event, and trigger an `order-confirmation` workflow for that customer. These are helpful defaults to get you started, but Knock can ingest any topic Shopify sends and you can adjust your mappings at any time. Click the **Connect Shopify** button to continue. After creating the source, copy the webhook URL from the setup wizard for the environment you want to configure. You will use this URL as the destination for the webhooks you create in Shopify. ## Send webhooks from the Shopify admin panel Use this path if you're a store owner and want to forward events directly from a single Shopify store without building an app. {/* prettier-ignore */} In the Shopify admin panel, navigate to **Settings > Notifications** and scroll to the **Webhooks** section. Click "Create webhook," paste the Knock webhook URL into the **URL** field, select the event topic you want to send (for example, `orders/create`), set the format to **JSON**, and save. Repeat this step for each topic you want to send to Knock. After creating your first webhook, Shopify displays a **Signing secret** at the bottom of the Webhooks section. Copy this value and paste it into the **Signing secret** field in your Knock source environment configuration. Shopify uses the same signing secret to sign every webhook from a given store, so you only need to copy it once. ## Send webhooks from a Shopify app Use this path if you're building a Shopify app and want webhook subscriptions to be installed automatically on every store the app runs on. You can declare subscriptions in your app's configuration file with the Shopify CLI, or create them at runtime with the Admin GraphQL API. Declare your subscriptions in `shopify.app.toml` using the Shopify CLI (3.63.0 or later). Set each subscription's `uri` to the Knock webhook URL you copied earlier and include the topics you want to send. ```toml # shopify.app.toml [webhooks] api_version = "2025-01" [[webhooks.subscriptions]] # Topics that match the source's pre-configured action mappings. topics = ["customers/create", "customers/update", "orders/create"] uri = "https://api.knock.app/v1/sources/shopify/..." # Add additional subscriptions for any other topics you want to map in # Knock (e.g. orders/paid, orders/fulfilled, orders/cancelled, # customers/delete). ``` Run `shopify app deploy` to register the subscriptions with Shopify. If you'd rather create subscriptions at runtime instead of declaring them in your app config, call the `webhookSubscriptionCreate` mutation on the Admin GraphQL API and pass the Knock webhook URL as the `callbackUrl`. See Shopify's subscribe to webhooks docs for the full subscription syntax and filtering options. Open your app in the Shopify Partner dashboard and copy the **Client secret** from the app's API credentials page. Paste it into the **Signing secret** field in your Knock source environment configuration. Shopify uses this client secret to sign every webhook your app delivers, regardless of which store installed the app, so a single Knock source can verify events from every install. Once configured, Shopify sends webhook events to Knock in real time. Knock verifies each payload by computing an HMAC-SHA256 of the raw request body using your signing secret and comparing it to the base64-encoded value in the `X-Shopify-Hmac-Sha256` header. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events Shopify sends events for customer and order lifecycle changes. Below are the topics Knock pre-configures with default action mappings. You can enable or disable individual topics, or add new ones, from the source environment configuration. | Event topic | Action | Description | | ------------------ | ------------------------------------- | ------------------------------------------------------------------------------ | | `customers/create` | Identify user | A new customer was created in your store | | `customers/update` | Identify user | A customer's profile details were updated | | `orders/create` | Identify user | Identifies the order's customer in Knock before the workflow is triggered | | `orders/create` | Trigger `order-confirmation` workflow | Triggers a workflow with the customer as the recipient and the order as `data` | Customer events use the top-level `body.id` as the Knock user ID. The two `orders/create` mappings work together: the identify action upserts the customer from `body.customer.*` (with `body.customer.id` as the user ID) so the workflow trigger that runs right after can address that same customer as the recipient. The full Shopify order payload is sent as the workflow's trigger `data`, so your templates have access to line items, totals, shipping address, and order metadata. To use the order mapping out of the box, create a workflow in Knock with the key `order-confirmation`. You can rename the workflow key at any time by editing the action mapping. ### Other common topics to add Most Shopify customers extend the defaults with a few additional notification flows. Common topics worth adding as custom mappings: | Event topic | Typical action | Description | | ------------------ | ------------------------------------- | ------------------------------------------------------- | | `orders/paid` | Trigger a `payment-received` workflow | An order's payment was captured | | `orders/fulfilled` | Trigger an `order-shipped` workflow | An order was fulfilled, often with tracking information | | `orders/cancelled` | Trigger an `order-cancelled` workflow | An order was canceled, often with associated refunds | | `customers/delete` | Delete the user in Knock | A customer was deleted from your store | For order topics, use `body.customer.id` as the workflow recipient and send `body` as the workflow data, matching the pattern of the pre-configured `orders/create` mapping. See the Shopify webhook topics documentation for the full list of available events. ## Customization You can modify the default action mappings or add new ones for any topic Knock receives from Shopify. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. Shopify puts the event topic in the `X-Shopify-Topic` HTTP header instead of the JSON body, so the Shopify source extracts the event type from `headers.x-shopify-topic`. You can reference other Shopify headers in your mappings using the same dot-notation — for example, `headers.x-shopify-shop-domain` to differentiate between stores when a single Knock environment ingests events from multiple Shopify stores or from many installs of the same Shopify app. If a single topic maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map Shopify events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock automatically configures idempotency for the Shopify source so duplicate events are not processed twice. By default, Knock uses `headers.x-shopify-webhook-id` from the Shopify webhook payload as the idempotency key. Shopify generates a unique ID per webhook delivery and includes it in the `X-Shopify-Webhook-Id` header, which makes it a reliable way to deduplicate retries. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## RudderStack Receive RudderStack webhook events in Knock to trigger workflows and keep user data in sync with track and identify events. --- title: RudderStack source description: Receive RudderStack webhook events in Knock to trigger workflows and keep user data in sync with track and identify events. metaTitle: RudderStack source integration metaDescription: Connect RudderStack webhooks to Knock to trigger notification workflows from track and identify events. section: Integrations > Sources layout: integrations --- This is the updated RudderStack source integration. If you are looking for the previous version that uses manual webhook destinations, see the{" "} legacy RudderStack source . } /> The RudderStack source enables you to receive [RudderStack webhook events](https://www.rudderstack.com/docs/destinations/webhooks/webhook/) directly in Knock. RudderStack sends webhook callbacks when track and identify events flow through your workspace. Knock identifies the event type and executes the actions you configure. This integration is useful for triggering notification workflows from track events, such as alerting users when key actions happen in your product, or syncing user data into Knock from identify events so recipient profiles stay up to date. RudderStack webhook destinations do not generate a signing secret or include a built-in payload verification mechanism. If you need to verify incoming payloads, you can include a shared secret as a custom header in your RudderStack destination configuration and use a{" "} source script in Knock to validate it. } /> ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A RudderStack workspace with access to destination settings. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **RudderStack** template as the source type. The Sources page in the Knock dashboard Once you've selected **RudderStack** as a source, you can select your desired action mappings. The defaults map common RudderStack events like `identify` to identify users and `track/order_completed` to trigger workflows. You can always add additional action mappings later. Click the **Connect RudderStack** button to continue. The RudderStack source creation modal showing default action mappings for incoming events After creating the source, Knock displays a setup wizard. Copy the event ingestion URL from step 1 of the wizard. You will paste this into your RudderStack destination configuration in a later step. The RudderStack source setup wizard showing the event ingestion URL to copy In your RudderStack workspace, navigate to **Directory** > **Destinations** and search for "Webhook." Select the **Webhook** destination type from the results. The RudderStack Directory showing search results for Webhook destinations Give the destination a name to identify it in RudderStack, such as "Knock," and click **Continue**. The RudderStack Create Webhook destination wizard with the Name field set to Knock Select the RudderStack source you want to route events from. This determines which track and identify events are forwarded to Knock. Click **Continue** to proceed. The RudderStack Connect step showing a source selected to send events to the Webhook destination In the **Connection Settings** step, paste the Knock event ingestion URL you copied earlier into the **Webhook URL** field and set the **URL Method** to `POST`. You can also add a `content-type: application/json` header. Click **Continue** to finish creating the destination. The RudderStack Configure step showing the Knock webhook URL and POST method Once configured, RudderStack sends webhook events to Knock in real time. You can verify that events are arriving by checking the event logs on the source environment page. ## Pre-configured events RudderStack sends events based on the [RudderStack event spec](https://www.rudderstack.com/docs/event-spec/standard-events/). Below are common event types you might map to actions in Knock. | Event type | Description | | ---------- | ----------------------------------- | | `track` | A user performed an action | | `identify` | User traits were created or updated | See the [RudderStack event spec documentation](https://www.rudderstack.com/docs/event-spec/standard-events/) for the full list of available event types and their schemas. ## Customization You can modify the default action mappings or add new ones for any event type Knock receives from RudderStack. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. If you need to map RudderStack events to actions beyond triggering workflows, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Knock uses the `messageId` field from the RudderStack event spec as the idempotency key. RudderStack includes a `messageId` in every track and identify event by default, so no additional field configuration is needed. You can change the idempotency key field or disable idempotency checks from the **Settings** tab in your source environment configuration. Events without an idempotency key attribute are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Hightouch Learn how to power your Knock product notifications using synced data from your data warehouse using Hightouch. --- title: How to integrate Hightouch with Knock description: Learn how to power your Knock product notifications using synced data from your data warehouse using Hightouch. metaTitle: Hightouch source integration metaDescription: Connect Hightouch to Knock to sync user, object, and tenant data from your data warehouse into Knock on a scheduled basis. section: Integrations > Sources layout: integrations --- ## Getting started Knock integrates with Hightouch as a downstream destination to sync customer and event data from your data warehouse. ## Use cases You can use our Hightouch integration to: 1. Sync customer data into Knock from your data warehouse (identify) 2. Trigger workflows from records added, changed, or removed in your data warehouse 3. Populate [Audiences](/concepts/audiences) with data from your data warehouse. Audiences can be used to trigger workflows or power conditional logic during workflow execution. ## Syncing customer data into Knock from Hightouch You can use the HTTP request destination within Hightouch to sync your customer data from your data warehouse into Knock. For this example, we're going to make a call to the [Knock API users identify endpoint](/api-reference/users/update). you can also extend this technique to make other calls to the Knock API from your Hightouch models to sync object data or trigger workflows. Please{" "} get in touch{" "} if you'd like any additional help. } /> 1. Create a new HTTP request destination and select your model to query from 2. Set an `Authorization` header, with the value set to `Bearer `. You can find your secret API key in [your dashboard](https://dashboard.knock.app) under **Platform** > **API keys** 3. Name your destination "Knock API" 4. Create a new sync with your Knock API destination 5. Select the types of events that should trigger, the most common case here is "Rows added" 6. Set the HTTP request method to `PUT` and the URL to `https://api.knock.app/v1/users/{{ row.user_id }}` where `{{ row.user_id }}` corresponds to the user identifier in the table 7. Select a "JSON" payload and "Use the JSON editor" to craft the request 8. Add at least a `name`, `email`, or `phone_number` field from your users table 9. For the rate limit, you can specify 60 requests per second 10. You'll likely want to "backfill" all of the available rows meaning that any existing data will also be synced to Knock 11. Click "Continue" 12. Select your sync frequency 13. Click "Finish" An example configuration for your sync may look something like this: ```json title="Example sync configuration" { "add": { "enabled": true, "method": "PUT", "url": "https://api.knock.app/v1/users/{{ row.user_id }}", "timeout": 30, "contentType": "application/json", "hasRateLimit": true, "bodyType": "template", "body": "{\n \"name\": \"{{ row.first_name }} {{ row.last_name }}\",\n \"email\": \"{{ row.email_address }}\",\n \"phone_number\": \"{{ row.phone_number }}\"\n}", "rateLimit": 60, "rateLimitTime": "second", "onError": "retryRequest", "retries": 3 }, "change": {}, "remove": {}, "skipFirstRun": false } ``` ## Syncing audiences into Knock from Hightouch Hightouch models can be synced to Knock [audiences](/concepts/audiences) by configuring Knock as an Embedded Destination in Hightouch. This integration does not require any additional configuration in the Knock dashboard once the following steps are completed in Hightouch. ### Prerequisites 1. Determine which Knock environment you want to sync your audience to. You most likely want to sync in the Production environment, unless you’re configuring a test sync. 2. Have your Knock environment’s secret API key ready. Each environment has its own unique set of API keys; you can find your secret API key in your dashboard under **Platform** > **API keys**. Be sure that the correct environment is selected in the switcher at the top of the page. ### Configure Knock as a Hightouch Destination Before you can sync audiences into Knock from Hightouch, you need to configure Knock as an Embedded Destination in Hightouch. Navigate to the **Integrations** > **Destinations** page in Hightouch’s sidebar, then click "Add destination." Add destination Under "Developer tools," select "Embedded Destination" and then click "Continue." Select Embedded Destination Copy and paste the following into the URI field: ```txt title="Knock Embedded Destination URI" https://api.knock.app/v1/integrations/hightouch/embedded-destination ```
Add the following under **Headers**. Be sure to toggle the "Secret" setting for the value: | Header | Value | | ------ | ----- | | `Authorization` | `Bearer ` |
Your completed URI configuration should look something like this: Configure the destination URI
Click "Continue." You'll be given the opportunity to configure alerts for this destination; if you don't need alerts, you can click "Continue" again.
Type in a name for your destination and click "Finish." We recommend something like “Knock Production” so that its clear which environment the destination writes to.
### Add a sync to your Knock destination Once you have configured Knock as an Embedded Destination in Hightouch, you can add a sync to your destination. Navigate to **Activation** > **Syncs** in the Hightouch sidebar, then click "Add sync." Add a sync You'll be prompted to select a model for your source data. Select the model for the audience that you want to sync to Knock. Next, you'll select the Knock Embedded Destination that you created above. Select a destination Select “Audiences” in the “Which object would you like to sync data to?” dropdown. Configure your sync
Then, select a mode for the sync: - **Insert only:** Only new rows added to your model will be synced to Knock. Removing rows from your model will have no effect on your Knock audience. - **Mirror:** New rows in your model will be added to your audience and removed rows in your model will be deleted from your audience. Knock’s Embedded Destination only applies add and remove operations. It does not support update operations. } />
Map your model’s fields to the audience fields. You must map a model field to the audience "User ID" field. This field should correspond to the ID of the user in Knock. If you’d also like to map a `tenant` field, click "Add mapping" and select the appropriate field from your model. Configure your data mapping Next, enter the key of the Knock audience you want to sync to. If an audience with this key doesn't exist in Knock yet, it will be created automatically when the sync runs for the first time. Click "Continue." Finally, you'll set your sync to run manually, on a schedule, or with an automation. You can now click "Finish" to add the sync and run it whenever you’d like. Refer to Hightouch’s documentation for more information on syncs. If you want to trigger any Knock workflows based on audience entry, ensure that the workflows are configured, committed, and promoted to the relevant environment before starting the Hightouch sync. } /> You can verify that the sync is behaving as expected by: - Comparing total user counts in your model and the audience in your Knock dashboard. - Viewing the audience user list. - Viewing workflow run logs for the audience (if you have workflow audience triggers configured).
## Census Learn how to power your Knock product notifications using synced data from your data warehouse using Census. --- title: How to integrate Census with Knock description: Learn how to power your Knock product notifications using synced data from your data warehouse using Census. metaTitle: Census source integration metaDescription: Connect Census to Knock to sync user, object, and tenant data from your data warehouse into Knock on a scheduled basis. section: Integrations > Sources layout: integrations --- ## Getting started Knock can be integrated as a downstream destination in Census to sync customer and event data from your data warehouse. ### Use cases You can use our Census integration to: 1. Sync customer data into Knock from your data warehouse ([identify recipients](/managing-recipients/identifying-recipients)). 2. [Trigger workflows](/send-notifications/triggering-workflows/overview) from records added, changed, or removed in your data warehouse. 3. Populate [audiences](/concepts/audiences) with data from your data warehouse. Audiences can be used to trigger workflows or power conditional logic during workflow execution. ## Configure Knock as a destination in Census Before you can sync data into Knock from Census, you need to configure Knock as a destination. Our Census integration only requires configuration within Census. Once you've completed the setup steps below, you will not need to take any additional steps in the Knock dashboard. } /> ### Prerequisites 1. Determine which Knock environment you want to sync your Census data to. You most likely want to sync in the Production environment, unless you’re configuring a test sync. 2. Have your Knock environment’s secret API key ready. Each environment has its own unique set of API keys; you can find your secret API key in your dashboard under **Platform** > **API keys**. Be sure that the correct environment is selected in the switcher at the top of the page. 3. Choose your destination type. Your options are: - **Custom destination** - Our recommended destination type for syncing audiences into Knock from Census. Setup is simpler and includes automatic support for removing a user from an audience during your syncs without additional configuration. - **HTTP Request destination** - This destination type is required if you need to [sync users and their properties to Knock](#syncing-customer-data-into-knock-from-census) or if you need to include [tenant](/concepts/tenants) data in your Knock audience. ### Configuration instructions Navigate to the **Destinations** tab in Census and click "Add a Destination." Add a new destination in Census Select "Custom Destination API." Select Custom Destination API as the destination type in the UI **a.** Input a destination name. We recommend something like **Knock Production** that correlates to the Knock environment that you're syncing to. **b.** Copy and paste the following into the API URL field: ```txt title="Knock Custom Destination API URL" https://api.knock.app/v1/integrations/census/custom-destination ```
**c.** Click the "+ Add Header" button to add an `Authorization` header. Be sure to toggle the "Secret" setting for the value: | Header | Value | | ------ | ----- | | `Authorization` | `Bearer ` |
**d.** Leave “1” selected as the API Version, then click "Connect" to complete the configuration. Census will now test connectivity with Knock. This process may take a few moments to complete. Configure the destination
Navigate to the **Destinations** tab in Census and click "Add a Destination." Add a new destination in Census Select "HTTP Request." Select HTTP Request as the destination type in the UI **a.** Input a destination name. We recommend something like **Knock Production HTTP API** that correlates to the Knock environment that you're syncing to. **b.** Leave the Authorization Type set to "Manual." **c.** Copy and paste the following into the Base URL field: ```txt title="Knock API Base URL" https://api.knock.app/v1 ```
**d.** Click the "+ Add Header" button to add an `Authorization` header. Be sure to toggle the "Secret" setting for the value: | Header | Value | | ------ | ----- | | `Authorization` | `Bearer ` |
**e.** Click "Connect" to complete the configuration. Configure the destination
## Syncing audiences into Knock from Census Census Sources and Datasets can be synced to Knock audiences by configuring Knock as a custom destination or using an HTTP request destination connected to Knock’s audiences API. This integration will only sync a user's audience membership to Knock. To sync users and their properties into Knock, see the section below on{" "} syncing customer data into Knock from Census . } /> ### Prerequisites 1. Complete the steps to [configure Knock as a destination in Census](#configure-knock-as-a-destination-in-census). 2. Create, save, and commit a [static audience](/concepts/audiences#audience-types) in Knock that will serve as the target for your sync. Be sure that you create your audience in the same environment that you used to configure your Knock destination. 3. Ensure that any Knock workflows that should be [triggered by a user's audience entry](/send-notifications/triggering-workflows/audiences) are configured, committed, and promoted to your Knock environment before finalizing your Census sync. ### Add an audience sync to your Knock destination Follow the steps below to add an audience sync to your configured Knock destination in Census. Navigate to the **Syncs** tab in Census and click "Create a sync." Create a new sync in Census You'll be prompted to select a dataset for your source data. Select the dataset that you want to sync to Knock. Select your source data in Census Next, you'll select the Knock custom destination that you created above. The "Audiences" object should be selected by default. Select a destination in Census Select the sync behavior that you want to use. - **Create only:** New rows added to your model will be synced to Knock. Removing rows from your model will have no effect on your Knock audience. - **Mirror:** New rows added to your model will be added to your audience and removed rows in your model will be deleted from your audience. Select a sync behavior in Census Select a sync key from your source to map to the Knock audience User ID field. This field's value must be unique; if Census detects multiple rows with the same sync key value, it marks them as duplicates and does not sync them. Select a sync key for your user ID mapping Under "Set Up Custom Destination API Field Mappings," you'll add the key of the Knock audience that you want to sync to. **a.** Click the "Source value..." dropdown. Select the source value input for your audience key
**b.** Click "Constant Value" on the left-hand side, then enter the key of your Knock audience in the input field. Add the audience key to your sync
**c.** Click "Save."
We recommend clicking "Run test" to test your sync. If successful, this will sync one row of data to your Knock audience, which you can confirm in the Knock dashboard. Any errors with your source data or field mappings will be displayed in the test results. Test your sync Click "Next." You'll be prompted to provide an optional label and select the trigger type for your sync. Finalize your sync configuration
Click "Create" to complete the sync creation process.
Navigate to the **Syncs** tab in Census and click "Create a sync." Create a new sync in Census You'll be prompted to select a dataset for your source data. Select the dataset that you want to sync to Knock. Select your source data in Census Next, you'll select the Knock HTTP request destination that you created above. Enter the following into the request endpoint input, replacing `` with the key of your destination audience. For example, if your audience key is `new-signups`, you’d enter `/audiences/new-signups/members`. ```txt title="Knock API audience members endpoint" /audiences//members ```
Select a destination in Census
Select "Records added" as the trigger type. Set the request trigger type to 'Records added' Select "Multiple records per request" to sync audience members in batches. Set the number of rows per batch to 500. Select the request body in Census Select a sync key from your source to map to the Knock audience User ID field. This field's value must be unique; if Census detects multiple rows with the same sync key value, it marks them as duplicates and does not sync them. Select a sync key for your user ID mapping Select `POST` as the request method. Select the POST request method
Then, select "JSON" as the payload type and "Template editor" as the customization option. Select the JSON payload type with template editor option
Modify the following template, replacing `source_user_id` and `source_tenant_id` with the names of columns from your data source that map to the Knock user ID and the Knock tenant ID. Then paste it into the JSON payload form. Note that column names are case-sensitive. ```json title="Request body template" { "members": [ {% for record in records %} { "user": { "id": "{{ record['source_user_id']}}" }, "tenant": "{{ record['source_tenant_id'] }}" } {% endfor %} ] } ```
Edit the request body template
The rate limit for “Add member” API requests to Knock is 60 requests per second. We recommend choosing a lower number (such as 10 requests per second) to allow for multiple syncs to run at once. Configure a rate limit for the sync Select "Backfill All Records" to sync all existing records in your source data to your Knock audience, or "Skip Current Records" to only sync new records going forward. Choose whether to backfill existing records We recommend clicking "Run test" to test your sync. If successful, this will sync one row of data to your Knock audience, which you can confirm in the Knock dashboard. Any errors with your source data or field mappings will be displayed in the test results. Test your sync Click "Next." You'll be prompted to provide an optional label and select the trigger type for your sync. Finalize your sync configuration
Click "Create" to complete the sync creation process. If you want your audience sync to run in “insert only” mode, you’re all set! If you’d like to also remove audience members when rows are deleted from your data source, proceed to the next step.
Repeat steps 1. and 2. above, using the same source data and your Knock HTTP request destination. When you get to step 3., paste the following into the request endpoint, with replacements: - `audience_key` should be the same audience key used in the previous section. - `source_user_id` should be the name of the column that stores Knock user IDs. - `source_tenant_id` should be the name of the column that stores Knock tenant IDs. ```txt title="Knock API audience members endpoint" /audiences//members?members=[{"user": {"id": "{{ record['source_user_id'] }}"}, "tenant": "{{ record['source_tenant_id'] }}"}] ``` **a.** Select "Records removed" as the trigger type. Select the request trigger type
**b.** Select "One record per request" to remove audience members one at a time. Select the request trigger type
**c.** Select the same sync key as the one that you used in your "Add member" sync. Select the sync key
**d.** Select `DELETE` as the request method. The payload type will be "empty" for this request method. Select the DELETE request method
**e.** Configure a rate limit for the sync. The same 60 requests per second rate limit as your "add member" sync applies to this endpoint, and we recommend a similar setting to allow for multiple syncs to run at once. Configure a rate limit for the sync
As above, we recommend clicking "Run test" to test your sync. If successful, this will remove one row of data from your Knock audience, which you can confirm in the Knock dashboard. Any errors with your source data or field mappings will be displayed in the test results. Test your sync
Click "Next." You'll be prompted to provide an optional label and select the trigger type for your sync. Finalize your sync configuration
Click "Create" to complete the sync creation process. Your setup is now complete!
## Syncing customer data into Knock from Census Census user records can be synced to Knock by configuring an HTTP request destination connected to Knock’s users API. ### Prerequisites 1. Complete the steps to [configure Knock as a destination in Census](#configure-knock-as-a-destination-in-census). You'll need to configure an HTTP Request destination. ### Add a user sync to your Knock destination Follow the steps below to add a user data sync to your configured Knock HTTP request destination in Census. These instructions leverage our bulk identify endpoint to optimize migrating large numbers of user records to Knock, but you can adapt this approach to use our [identify endpoint](/api-reference/users/identify) for ongoing syncs of individual user records. Navigate to the **Syncs** tab in Census and click "Create a sync." Create a new sync for identifying users You'll be prompted to select a dataset for your source data. Select the dataset that you want to sync to Knock. Select your source data in Census Next, you'll select the Knock HTTP request destination that you created above. Enter the following into the request endpoint input: ```txt title="Knock API bulk identify endpoint" /users/bulk/identify ```
Select a destination in Census
Select either "Records added" or "Records added or changed" as the trigger type. If you only plan to run a one-time sync to migrate your user data to Knock, your selection doesn’t matter. Ongoing syncs should use "Records added or changed" to ensure that updated user records are synced to Knock. Set the request trigger type Select "Multiple records per request" to sync audience members in batches. Set the number of rows per batch to 1000. Select the request body in Census Select a sync key from your source to map to the Knock audience User ID field. This field's value must be unique; if Census detects multiple rows with the same sync key value, it marks them as duplicates and does not sync them. Select a sync key for your user ID mapping Select `POST` as the request method. Select the POST request method
Then, select "JSON" as the payload type and "Template editor" as the customization option. Select the JSON payload type with template editor option
Define a template for your request body. The only required Knock field is `id`. To reference source columns, use the `{{ record['source_column_name'] }}` syntax. Note that column names are case-sensitive. Here is an example template that identifies `id`, `name`, `email`, and `favorite_color` attributes in Knock using the columns from your source data: ```json title="Request body template" { "users": [ {% for record in records %} { "id": "{{ record['user_id'] }}", "name": "{{ record['name'] }}", "email": "{{ record['email'] }}", "favorite_color": "{{ record['fav_color'] }}" }, {% endfor %} ] } ```
Edit the request body template
The rate limit for bulk identify API requests to Knock is 1 request per second. Configure a rate limit for the sync Select "Backfill All Records" to sync all existing user records in your source data to Knock, or "Skip Current Records" to only sync new user records going forward. Choose whether to backfill existing records We recommend clicking "Run test" to test your sync. If successful, this will sync one user to your Knock environment, which you can confirm in the Knock dashboard. Any errors with your source data or field mappings will be displayed in the test results. Test your sync Click "Next." You'll be prompted to provide an optional label and select the trigger type for your sync. Finalize your sync configuration
Click "Create" to complete the sync creation process.
## Clay Sync enriched lead and contact data from Clay tables into Knock to identify users and trigger workflows from CRM enrichment events. --- title: Clay source description: Sync enriched lead and contact data from Clay tables into Knock to identify users and trigger workflows from CRM enrichment events. metaTitle: Clay source integration metaDescription: Add a Clay HTTP API enrichment column that POSTs enriched lead and contact data to Knock to identify users and trigger notification workflows. section: Integrations > Sources layout: integrations --- The Clay source enables you to send enriched lead and contact data from your Clay tables directly into Knock. You configure this by adding an HTTP API enrichment column to a Clay table that POSTs each row's enriched data to your Knock source URL. Clay runs the column as part of your enrichment workflow, so Knock receives updated contact data as soon as a row finishes processing. This integration is useful for sales and marketing notifications: identifying enriched leads in Knock as soon as Clay finishes researching them, triggering Slack or email alerts to your sales team when a high-fit account lands, and keeping user profile data in sync with the enrichment work happening in your Clay tables. Because the HTTP API column lets you define an arbitrary JSON body and custom headers, you can map any Clay column to any field in your Knock action mappings as your needs evolve. ## How verification works Clay does not sign outbound HTTP API requests with a built-in signature scheme. Instead, Knock verifies a shared secret that you send as a Bearer token in the `Authorization` header from your HTTP API column. Knock checks that the value of the `Authorization` header matches `Bearer ` before processing the event. This means the signing secret you configure in Knock must match the token you send in the `Authorization` header from Clay. Treat this value like an API key: store it somewhere safe and rotate it if it leaks. ## Prerequisites - A Knock account with at least one [environment](/concepts/environments) configured. - A Clay workspace with a table you want to send enriched data from. ## Set up the source in Knock Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Clay** template as the source type. Once you've selected **Clay** as a source, you can review the default action mappings. The default identifies a Knock user from a `lead.enriched` event using `body.email` as the user ID and maps common contact fields (`full_name`, `email`, `phone_number`, `avatar_url`) into the identify payload, plus the full row as `properties`. These are helpful defaults to get you started, but Knock can ingest any event you POST from Clay and you can adjust your mappings at any time. Click the **Connect Clay** button to continue. After creating the source, copy the event ingestion URL from the setup wizard for the environment you want to configure. You will paste this URL into the HTTP API enrichment column in Clay as the POST destination. Generate a strong random string to use as your shared secret (for example, with `openssl rand -hex 32`) and paste it into the **Signing secret** field in your Knock source environment configuration. You will send this same value as a Bearer token in the `Authorization` header from Clay in the next steps. ## Add an HTTP API enrichment column in Clay Add an HTTP API enrichment column to your Clay table so each enriched row POSTs to your Knock source URL with a Bearer token in the `Authorization` header. See Clay's HTTP API integration overview for column configuration details. In your Clay table, click **Add column** and choose **HTTP API** from the enrichment menu. In the **Setup inputs** section, set the request method to **POST** and paste the Knock source URL you copied earlier into the **Endpoint** field. Adding an HTTP API column in Clay In the **Accounts** section, add a HTTP API (Headers) account with key `Authorization` and value `Bearer `, replacing `` with the value you set in Knock. Knock uses this Bearer token to verify each POST from your Clay table before processing the row data. Adding the Authorization header in Clay Configure the JSON body to include the fields you want Knock to receive. At minimum, set an `event` field that names the event (for example, `lead.enriched`) so Knock can match it to an action mapping, plus the contact attributes you want to map to a Knock user. ```json { "event": "lead.enriched", "full_name": "{{ Full Name }}", "email": "{{ Work Email }}", "phone_number": "{{ Phone Number }}", "avatar_url": "{{ Profile Picture URL }}", "job_title": "{{ Job Title }}", "company_name": "{{ Company Name }}", "company_domain": "{{ Company Domain }}" } ``` Use Clay's templating to drop in values from other columns in your table. The event name (`lead.enriched` in this example) is what Knock looks for in `body.event` to decide which action mappings to run. Run the HTTP API enrichment column on a row (or on the full table) to POST the enriched data to Knock. You can verify that events are arriving by checking the event logs on the source environment page in Knock. Adding the Authorization header in Clay Once configured, every time the HTTP API enrichment column runs on a row, Clay POSTs that row's data to your Knock source URL. Knock verifies the `Authorization` header against your signing secret, extracts the event name from `body.event`, and executes the actions you've mapped for that event. ## Pre-configured events Your HTTP API enrichment column can send any event name in `body.event`, so the event types you POST to Knock are up to you. The default action mapping below identifies enriched leads as Knock users; add additional mappings for any other event names you decide to send. | Event type | Action | Description | | --------------- | ------------- | ---------------------------------------------------------------------------- | | `lead.enriched` | Identify user | Identifies a user in Knock from an enriched Clay row, keyed off `body.email` | The default identify mapping uses `body.email` as the Knock user ID and maps `body.full_name`, `body.email`, `body.phone_number`, and `body.avatar_url` to the corresponding user fields. The full row is sent as `properties`, so any additional columns you include in the request body (job title, company domain, ICP score, and so on) are stored on the Knock user and available in your templates. ### Other common events to add Clay tables run a wide range of enrichment and research workflows. Common events worth adding as custom mappings: | Event type | Typical action | Description | | ------------------ | ----------------------------------------- | ------------------------------------------------------------------------------ | | `account.enriched` | Identify [object](/concepts/objects) | Sync an enriched company or account as a Knock object | | `lead.high_intent` | Trigger a `new-high-intent-lead` workflow | Notify your sales team when an enriched row crosses an ICP or intent threshold | | `contact.updated` | Identify user | Re-identify a user when Clay re-runs enrichment on an existing contact | For each new event you send from Clay, add a corresponding mapping in the Knock source environment so the event is routed to the right action. ## Customization You can modify the default action mapping or add new ones for any event you POST from Clay. For details on how field mapping works with dot-notation paths, see the [custom source](/integrations/sources/custom) page. Because the HTTP API enrichment column lets you set arbitrary headers on each POST, you can also reference custom headers in your mappings using dot-notation — for example, `headers.x-clay-table-id` if you want to differentiate between events from different Clay tables when one Knock environment ingests from many tables. If a single event maps to multiple actions, Knock executes those actions in a fixed order. See [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings). If you need to map Clay events to actions beyond identifying users, see the full list of [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the sources overview. ## Event idempotency Clay does not include a built-in delivery ID in HTTP API enrichment column requests, so Knock does not configure idempotency for the Clay source by default. If you want to deduplicate events — for example, when the enrichment column is re-run on the same row — include a stable identifier in your request body or headers (such as the Clay row ID or a hash of the row's content) and configure that field as the idempotency key from the **Settings** tab in your source environment configuration. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Polytomic Learn how to power your Knock product notifications using synced data from your data warehouse using Polytomic. --- title: How to integrate Polytomic to Knock description: Learn how to power your Knock product notifications using synced data from your data warehouse using Polytomic. metaTitle: Polytomic source integration metaDescription: Connect Polytomic to Knock to sync user, object, and tenant data from your data warehouse into Knock on a scheduled basis. section: Integrations > Sources layout: integrations --- ## Getting started Knock integrates with Polytomic as a downstream destination to sync customer and event data from your data warehouse. Our Polytomic integration is currently in beta. If you'd like early access, or this is blocking your adoption of Knock, please{" "} get in touch . } /> ## Use cases You can use our Polytomic integration to: 1. Sync customer data into Knock from your data warehouse (identify) 2. Trigger workflows from records added, changed, or removed in your data warehouse ## Jitsu Learn how to connect your Jitsu events to Knock to power your product notifications. --- title: How to integrate Jitsu to Knock description: Learn how to connect your Jitsu events to Knock to power your product notifications. metaTitle: Jitsu source integration metaDescription: Connect Jitsu to Knock to send events from your open-source data pipeline that trigger notification workflows. section: Integrations > Sources layout: integrations --- ## Getting started Knock integrates with Jitsu as a downstream destination to sync customer and event data. Our Jitsu integration is currently in beta. If you'd like early access, or this is blocking your adoption of Knock, please{" "} get in touch . } /> ## Use cases You can use our Jitsu integration to: 1. Sync customer data into Knock 2. Trigger workflows from track events ## Freshpaint Learn how to connect your Freshpaint events to Knock to power your product notifications. --- title: How to integrate Freshpaint to Knock description: Learn how to connect your Freshpaint events to Knock to power your product notifications. metaTitle: Freshpaint source integration metaDescription: Connect Freshpaint to Knock to send healthcare-focused CDP events that trigger notification workflows and keep user data in sync. section: Integrations > Sources layout: integrations --- ## Getting started Knock integrates with Freshpaint as a downstream destination to sync customer and event data. Our Freshpaint integration is currently in beta. If you'd like early access, or this is blocking your adoption of Knock, please{" "} get in touch . } /> ## Use cases You can use our Freshpaint integration to: 1. Sync customer data into Knock 2. Trigger workflows from track events ## Custom source Send custom webhook events from any service into Knock and map them to actions like triggering workflows and identifying users, with optional scripting for verification and preprocessing. --- title: Custom source description: Send custom webhook events from any service into Knock and map them to actions like triggering workflows and identifying users, with optional scripting for verification and preprocessing. metaTitle: Custom source integration metaDescription: Send webhook events from any service into Knock using a custom source with configurable event types, field mapping, and scripting for verification and preprocessing. section: Integrations > Sources layout: integrations --- The custom source enables you to receive webhook events from any service that can make HTTP callbacks and map those events to actions inside Knock. Use it when you need to connect a service that Knock does not yet offer a [pre-built integration](/integrations/sources/overview) for, or when you want to send events from your own internal tools and applications. The custom source supports optional [scripting](#scripting) so you can verify incoming payloads and pre-process them before field mapping. ## When to use custom webhooks Custom webhooks are the right choice when: - The service you want to connect does not have a pre-built Knock source integration. - You are sending events from an internal tool, custom application, or microservice. - You need full control over event type identification and field mapping. For services with pre-built integrations (Stripe, Clerk, WorkOS, Supabase, PostHog), use those dedicated source pages instead. They provide automatic signature verification and pre-configured event types. ## Getting started Navigate to **Platform** > **Sources** in the Knock dashboard. Make sure you're in the correct environment. Select the **Custom HTTP** template as the source type. You can select default action mappings to get started, then click the **Connect Custom HTTP** button to continue. The Custom HTTP source creation modal showing default action mappings After creating the source, Knock displays a setup wizard. Copy the event ingestion URL from step 1 of the wizard. You will paste this into your service's webhook or callback settings. The Custom HTTP source setup wizard showing the event ingestion URL and additional setup steps In step 2 of the setup wizard, configure the event type path so Knock knows how to identify the event type from incoming payloads. The default path is `body.type`, but you should update this to match the structure of your payload. For example, if your service sends payloads with an `event_type` field at the top level, set the path to `body.event_type`. You can also update this later from the **Settings** tab on the source environment page under **Key path configuration**. Paste the webhook URL into your service's webhook or callback settings. Knock accepts `POST` requests with a JSON body. If your source includes a [script](#scripting), Knock runs it on every incoming request to handle verification and any preprocessing you need. ## Scripting Each custom source can have a script that runs every time Knock receives a webhook request. Scripts enable you to verify that incoming payloads are authentic and preprocess payloads before field mapping. You can configure your script from the **Settings** tab on the source environment page. A script must export a `main` function. Knock calls `main` with a context object that contains the following properties: | Property | Description | | --------- | ---------------------------------------------- | | `vars` | Account-level [variables](/concepts/variables) | | `body` | The parsed request body object | | `rawBody` | The raw request body string | | `headers` | HTTP headers from the incoming request | ### Verification The `main` function must return an object with a `verified` boolean. When the **Enforce verification** toggle is enabled, Knock rejects any request where `verified` is `false` or missing with a `401` response. The enforce verification toggle in the Custom HTTP source settings If the sending service signs its payloads, store the signing secret as an account-level [variable](/concepts/variables) so your script can access it through `vars`. The default script included with new custom sources compares an `x-webhook-secret` header against a variable you configure, but you can customize the verification logic to match whatever algorithm and header convention the sending service expects. ```javascript async function main(ctx) { const { headers, rawBody, vars } = ctx; const signingSecret = vars.my_signing_secret; const isValid = await verifySignature(headers, rawBody, signingSecret); return { verified: isValid }; } ``` Full per-source verification examples are available on the dedicated source integration pages (for example, [Stripe](/integrations/sources/stripe)). ### Preprocessing Any additional keys you return from `main` alongside `verified` become available in field mappings under the `preprocess` namespace. This is useful when you need to normalize or reshape the incoming payload before mapping it to action parameters. The preprocess event script editor in the Custom HTTP source settings ```javascript function main(ctx) { const { body } = ctx; return { verified: true, normalized_event: `${body.schema}.${body.table}:${body.type}`, item_idempotency_key: `${body.record.id}_${body.record.updated_at}`, }; } ``` In this example, `normalized_event` becomes `preprocess.normalized_event` and `item_idempotency_key` becomes `preprocess.item_idempotency_key` in field mapping paths. ## Configuring event types As described in [step 3 of getting started](#getting-started), Knock needs to know how to identify the event type from incoming payloads. The default event type path is `body.type`, but you should update this to match the structure of your payload. For example, if your service sends payloads like: ```json { "event_type": "task.completed", "timestamp": "2024-09-15T14:30:00Z", "data": { "task_id": "task_123", "assigned_to": "user_456" } } ``` You would set the event type path to `body.event_type` so Knock can identify this as a `task.completed` event. Note that all body paths are prefixed with `body.` since Knock exposes the parsed request body under that namespace. ## Event-action mappings After events start flowing into Knock, you can configure what action Knock should take when it receives each event type. From the source environment configuration page: 1. Select an event type from the list of received events. 2. Click "Create action mapping" to add a new mapping. 3. Choose the action to execute (trigger workflow, identify user, set object, and so on). See the [available actions](/integrations/sources/overview#triggering-actions-from-source-events) in the overview for the full list. 4. Map fields from the incoming payload to the parameters the action requires. You can create multiple action mappings for a single event type. For example, a `customer.created` event could both identify a user and trigger a welcome workflow. ### Execution order for multiple mappings If a single event type has multiple action mappings, Knock executes them in a fixed priority order based on action type, not the order in which you create mappings in the dashboard. 1. `users_identify` 2. `users_delete` 3. `objects_set` 4. `objects_delete` 5. `tenants_set` 6. `tenants_delete` 7. `objects_subscribe` 8. `objects_unsubscribe` 9. `audiences_add_member` 10. `audiences_remove_member` 11. `workflows_trigger` For incoming webhook context and related behavior, see [execution order for multiple mappings](/integrations/sources/overview#execution-order-for-multiple-mappings) in the sources overview. The Custom HTTP source Mappings page showing an action mapping with field mappings and a sample payload ## Field mapping Field mappings use dot-notation paths to extract values from the incoming JSON payload and map them to the parameters each action expects. Given a payload like: ```json { "event_type": "task.completed", "data": { "task_id": "task_123", "assigned_to": "user_456", "project": { "id": "proj_789", "name": "Website redesign" } } } ``` You could create the following mappings when triggering a workflow: | Payload path | Maps to | | ------------------- | ------------------- | | `data.assigned_to` | Recipients | | `data.task_id` | `data.task_id` | | `data.project.name` | `data.project_name` | A few things to keep in mind: - Paths are case-sensitive and follow the structure of your JSON payload. - If your source has a [preprocessing script](#preprocessing), the values it returns are available under the `preprocess.*` namespace alongside the raw payload paths. For example, a script that returns `normalized_event` can be mapped using the path `preprocess.normalized_event`. - You can map a single event to multiple action parameters. - Knock validates that required fields are populated before executing the action. If a required field is missing, the action is skipped and an error appears in the action log. ## Example: task management app Suppose you have a task management app that sends a webhook when a task is assigned. The payload looks like this: ```json { "event_type": "task.assigned", "data": { "task_id": "task_123", "title": "Review pull request", "assigned_to": "user_456", "assigned_by": "user_789" } } ``` To trigger a notification workflow when a task is assigned: 1. Set the event type path to `body.event_type`. 2. Wait for a `task.assigned` event to arrive (or send a test event). 3. Create an action mapping that triggers your `task-assigned` workflow. 4. Map `data.assigned_to` to **Recipients** and `data.assigned_by` to **Actor**. 5. Map `data.task_id` and `data.title` to workflow data fields so you can reference them in your notification templates. ## Debugging You can see a log of all events received per source under **Platform** > **Sources** in the Knock dashboard on the "Logs" tab for your configured source. Common issues to look for: - **Missing fields.** A required field path does not exist in the incoming payload. Check that the dot-notation path matches your payload structure. - **Invalid paths.** The key path for event type identification does not resolve to a value. Verify the path against a sample payload. - **Type mismatches.** A field value does not match the expected type (for example, passing a string where a user ID array is expected). ## Event idempotency You can enable idempotency checks to deduplicate custom webhook events that have already been received and processed. This is useful if the service sending webhooks may deliver the same event more than once. Because custom webhook payloads vary by service, you need to specify which field in the incoming payload contains the idempotency key. Enable the **Enforce idempotency** toggle in the **Settings** tab for your source environment configuration and set the **Idempotency key path** to the field in your payload that contains a unique event identifier. The Custom HTTP source settings showing the enforce idempotency toggle and idempotency key path configuration For example, if your service sends payloads with a unique event identifier: ```json title="A custom webhook event with an idempotency key." { "event_type": "task.completed", "event_id": "evt_abc123", "data": { "task_id": "task_456", "assigned_to": "user_789" } } ``` You would set the idempotency key path to `event_id` so Knock can use it for deduplication. Events without a value at the configured key path are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Legacy ## Segment The legacy Segment source uses webhook destinations to forward track and identify events from Segment to Knock. For new integrations, use the updated Segment source instead. --- title: Segment source (legacy) description: The legacy Segment source uses webhook destinations to forward track and identify events from Segment to Knock. For new integrations, use the updated Segment source instead. metaTitle: Segment source integration (legacy) metaDescription: Connect Segment to Knock using the legacy webhook destination approach. For new integrations, use the updated Segment source. section: Integrations > Sources layout: integrations --- This is the legacy Segment source integration that uses manual webhook destinations. It is still supported for existing integrations. For new integrations, use the updated{" "} Segment source instead. } /> Knock's Segment integration enables you to use Segment as a [Knock source](/integrations/sources/overview) to power your notifications with track and identify events. Knock also provides a separate [Segment extension](/integrations/extensions/segment) for sending Knock notification data into Segment for use in your downstream tools. ## Getting started When using Segment as a Knock source, you also need to configure Knock as a [destination](https://segment.com/docs/connections/destinations/) within Segment. This means you can use events coming through Segment to power actions in Knock, such as triggering a workflow. To start routing your Segment events to Knock, navigate to **Integrations** > **Sources** under the account settings section of your dashboard, click the "Create Source" button and select Segment. Once created, select it from the list to access the environment configuration page, where you can copy the unique webhook destination URLs for each environment you have configured in Knock. ## Configure a Knock destination in Segment Knock does not have a first-party integration with Segment, so you will not find us in Segment's destination catalog. Instead, you'll configure the connection to Knock via a webhook destination as described below. } /> In your Segment workspace, navigate to the **Destinations** tab and click "Add Destination." From here, search for **Webhooks** or navigate to **Raw Data** in the sidebar and click the "Webhooks (Actions)" option. This is the destination type we'll use for Knock. Configure webhooks Click the "Configure Webhooks (Actions)" button in the top right to enter the setup flow to configure your webhook: - Select the data source you'd like to receive events from and click "Next." - Give a name to the destination, like "Knock," and keep the "Fill in settings manually" option selected, then click "Create destination." Now you'll see a **Settings** page. Check the "Enable Destination" button and click "Save changes." At the top of the page, navigate to the **Mappings** tab. Click "New Mapping," and under Actions, click "Send". You will then be directed to a form to configure the webhook: - Select the events to map and send to Knock. If you'd like to send all events to Knock, you can add the condition **Event type is Track,** a condition for **Event type is Identify,** and change the top operator to **any** instead of all: Select event conditions - You can load a test or sample event; either way, ensure it matches your trigger conditions (Segment will warn you if it doesn't). - Click "Test Mapping" to send the loaded or sample event from the previous step to Knock. - If the test is successful, click "Save" to exit the form, and then click to enable the mapping you just created. ## Viewing Segment track events in Knock Once your Segment destination is set up, all events you trigger from the Segment source will be forwarded to Knock. Unique events will appear in your list of events under the Source so that you can set up triggers for your workflows. From the source environment configuration page, click the "View in environment" button on one of the source environments. You'll be taken to the Segment source in the environment you selected, and you should see events sent. If you don't, try clicking the refresh button at the top of the list to refetch any incoming events. ### How Knock translates Segment events Although Segment has its own event format, Knock translates incoming events into a common format that includes the following fields: - `user_id`. The ID of the user performing a given action (may not be set if a user has not been identified yet). - `data`. The primary contents of the event. For a Segment `track` call with associated `properties`, Knock uses those `properties` to set the `data` field. - `event`. The original event, as originally received by Knock. ## Triggering workflows from received events You can add a **track event** as a trigger to your workflow directly from the workflow builder. Click on the workflow's trigger step and change the type from "API" to "Source Event." Then you'll be able to select the event and map its properties into the data the workflow needs. You can have any number of workflows triggered by each event. If no workflow is configured for an event, the event is logged but no action is taken. When connecting an event to a workflow, you can use any data available within the event payload in the workflow's parameters. For example, if your payload looks like this:
          {eventPayload}
        
and you wanted the commenter from the event to appear as the{" "} actor in the workflow, then in the{" "} actor field, you would write{" "} properties.commenter.id to supply their ID as the actor.

If you wanted to supply the event's userId as the workflow's recipient, you'd write userId in the Recipients field.

Note: when you edit message templates, all properties are available to you as data variables. You can access them directly in your templates without the properties. prefix. } /> If you exceed this limit, Knock will not process your workflow trigger and instead generate an error log.

If you need to manage a large list of recipients you might want to consider using our{" "} subscriptions feature to have Knock manage the set of recipients who need to be notified instead. } /> ## Disabling a trigger Triggers are automatically enabled when you create them. If you want to stop an event from triggering a workflow, you can go to the trigger page and toggle its status to "Inactive." Keep in mind that this will disable that trigger for the current environment only. When you're ready to trigger the workflow again, you can set it back to "Active." ## Enabling Identify events When Segment sends identify events, Knock will create and update user information accordingly. Knock will correctly map the `userId` as the user's `id`, as well as `name`, `email`, `phone` (mapped as `phone_number` in Knock), `avatar`, `locale`, `timezone`, and any additional custom properties provided in the `traits` object. All custom properties are stored on the Knock user object and can be used in templates and other parts of Knock that rely on user properties. To enable the handling of identify events, open the settings for the source in the relevant Knock environment. You can then enable or disable handling identify events accordingly. A screenshot of how to toggle Knock handling Segment identify calls if you send Knock an event that includes a recipient not yet identified in Knock, our system will not generate a workflow run for that user.

For use cases such as new signup events, where events often reach Knock before identify calls, consider{" "} inline identification {" "} of users in your Segment events. } /> ## Inline identify users in a Segment event Inline identification is not supported by our{" "} workflow test runner , which can only trigger test runs for existing users. To test inline identification with a source event, you should send a test event from Segment. } /> In cases where you send a Segment event to Knock with recipients who may not yet have been identified in our system, it's good practice to [inline identify](/managing-recipients/identifying-recipients#inline-identification) your users. By inline identifying your users within your Segment events, you ensure that those users are identified in Knock when your event triggers a workflow. As an example, take the user-signed-up event below. We're currently mapping the `properties.recipients` field to the `recipients` field of our workflow in Knock. If we send this event to Knock before the user with id `sam10` has been identified, the user will not be notified. ```json title="A Segment event without inline identify" { "event": "user-signed-up", "email": "sam@example.com", "userId": "sam10", "type": "track", "messageId": "segment-test-message-123", "properties": { "recipients": ["sam10"], "account_id": "123" }, "timestamp": "2023-05-23T21:49:54Z" } ``` To ensure the user is notified, we'd change the id reference in `recipients` to a complete user object, as in the example below. This way, Knock has all the information it needs to identify the user during workflow runtime. ```json title="A Segment event with inline identify" { "event": "user-signed-up", "email": "sam@example.com", "userId": "sam10", "type": "track", "messageId": "segment-test-message-123", "properties": { "recipients": [ { "id": "sam10", "name": "Sam Seely", "email": "sam@example.com" } ], "account_id": "123" }, "timestamp": "2023-05-23T21:49:54Z" } ``` You can learn more about inline identification in [our documentation on identifying recipients](/managing-recipients/identifying-recipients). ## Event idempotency You can enable idempotency checks to deduplicate Segment events that have already been received and processed. This is useful if you know Segment may send duplicate events to Knock. Knock automatically uses the `messageId` field from the Segment track spec as the idempotency key. No additional field configuration is needed — Segment includes a `messageId` in every track and identify event by default. To enable idempotency, open the **Settings** tab for your source environment configuration and toggle idempotency checks on. A screenshot of where to configure idempotency for your Source. ```json title="An example Segment event with a valid idempotency key." { "type": "track", "event": "user.created", "messageId": "some-id-from-segment", "properties": { "id": "user-1", "email": "user-1@example.com" } } ``` Events without a `messageId` are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Video walkthrough

## Frequently asked questions Yes. Under the **Platform** > **Sources** section in your Knock dashboard, select an event from Segment to view a list of triggers configured for that event. Then, in the upper right-hand corner, click the "Create workflow trigger" button to select the additional workflow you want this event to trigger, then click "Create." You can then make any changes to the schema mapping before saving and committing the workflow with its new trigger. Yes. To do so, go to the **Platform** > **Sources** section in your Knock dashboard, select an event, and then click the "Create workflow trigger" button in the upper right-hand corner. You will choose the same workflow from the **Create workflow trigger** modal. ## RudderStack The legacy RudderStack source uses webhook destinations to forward track and identify events from RudderStack to Knock. For new integrations, use the updated RudderStack source instead. --- title: RudderStack source (legacy) description: The legacy RudderStack source uses webhook destinations to forward track and identify events from RudderStack to Knock. For new integrations, use the updated RudderStack source instead. metaTitle: RudderStack source integration (legacy) metaDescription: Connect RudderStack to Knock using the legacy webhook destination approach. For new integrations, use the updated RudderStack source. section: Integrations > Sources layout: integrations --- This is the legacy RudderStack source integration that uses manual webhook destinations. It is still supported for existing integrations. For new integrations, use the updated{" "} RudderStack source{" "} instead. } /> ## Video walkthrough
## Getting started Knock is a **RudderStack Destination**, which means you can use events coming through RudderStack to power actions in Knock, such as triggering a workflow. You can start routing your RudderStack events to Knock by creating a source of type "RudderStack" in the dashboard. From here, you'll be taken to the environment configuration page for the source which will give you unique URLs for each environment you have configured in Knock. You'll copy this URL and use it to let RudderStack know where to send events. ## Configuring RudderStack Knock does not have a first-party integration with RudderStack, so you will not find us in the list of destinations. Instead, you'll configure the connection to Knock via a webhook destination as described below. } /> You will need to create a RudderStack destination for each Knock environment that you want to receive events from RudderStack. 1. In your RudderStack workspace, navigate to the "Destinations" page and click "New destination." Search for and select "Webhook" 2. Give the destination a name (e.g. "Knock <environment name>") and click "Continue" 3. Optionally, choose the sources you want to route into this destination. Then, click "Continue" 4. Paste the URL from the Knock dashboard for the Knock environment you want to receive events from RudderStack. Make sure the "URL Method" selected is POST. No other headers or settings are required. Click "Continue" 5. Transformations may be configured as needed, but are typically not required. Click "Continue" to finalize the RudderStack destination ## Viewing RudderStack track events in Knock Once your RudderStack destination is set up all events you trigger from the RudderStack source will be forwarded to Knock. Unique events will appear in your list of events under the Source so that you can set up triggers for your workflows. From the source environment configuration page click the "View in environment" button on one of the source environments. You'll be taken to the RudderStack source in the environment you selected and you should see events sent. If you don't, try clicking the refresh button on the top of the list to refetch any incoming events. ### How Knock translates RudderStack events Although RudderStack has its own event format, Knock translates incoming events into a common format that includes the following fields: - `user_id`. The ID of the user performing a given action (may not be set if a user has not been identified yet). - `data`. The primary contents of the event. For a RudderStack `track` call with associated `properties`, Knock uses those `properties` to set the `data` field. - `event`. The original event, as originally received by Knock. ## Triggering workflows from received events You can add a **track event** as a trigger to your workflow directly from the workflow builder. Click on the workflow's trigger step and change the type from "API" to "Source Event." Then you'll be able to select the event and map its properties into the data the workflow needs. You can have any number of workflows triggered by each event. If no workflow is configured for an event, the event is logged but no action is taken. To use any of the properties fields, you can access them with dot-syntax by prefixing them with data.. For example, if your payload looks like this:
          {eventPayload}
        
and you wanted the commenter from the event to appear as the{" "} Actor in the workflow, then in the{" "} Actor field you would write{" "} data.commenter.id to supply their ID as the actor. } /> If you exceed this limit, Knock will not process your workflow trigger and instead generate an error log.

If you need to manage a large list of recipients you might want to consider using our{" "} subscriptions feature to have Knock manage the set of recipients who need to be notified instead. } /> ## Disabling a trigger Triggers are automatically enabled when you create them. If you want to stop an event from triggering a workflow, you can go to the trigger page and toggle its status to "Inactive." Keep in mind that this will disable that trigger for the current environment only. When you're ready to trigger the workflow again, you can set it back to "Active." ## Enabling Identify Events When RudderStack sends identify events, Knock will create and update user information accordingly. Knock will correctly map the `userId` as the user's `id`, as well as `name`, `email`, `phone` (mapped as `phone_number` in Knock), `avatar`, `locale`, `timezone`, and any additional custom properties provided in the `traits` object. All custom properties are stored on the Knock user object and can be used in templates and other parts of Knock that rely on user properties. RudderStack's default Identify{" "} event schema {" "} has the traits object nested under a top-level{" "} context key.{" "} Some RudderStack SDKs {" "} will also include a top-level traits object.

Knock expects user properties to be contained in a top-level traits object when processing an Identify event, so you may need to apply a data transformation prior to sending these events to Knock depending on which SDK you're using. } /> To enable handling of identify events, open the settings for the source in the relevant Knock environment. You can then enable or disable handling identify events accordingly. A screenshot of how to toggle Knock handling RudderStack identify calls if you send Knock an event that includes a recipient not yet identified in Knock, our system will not generate a workflow run for that user.

For use cases such as new signup events, where events often reach Knock before identify calls, consider{" "} inline identification {" "} of users in your RudderStack events. } /> ## Inline identify users in a RudderStack event Inline identification is not supported by our{" "} workflow test runner , which can only trigger test runs for existing users. To test inline identification with a source event, you should send a test event from RudderStack. } /> In cases where you send a RudderStack event to Knock with recipients that may not yet have been identified into our system, it's good practice to [inline identify](/managing-recipients/identifying-recipients#inline-identification) your users. By inline identifying your users within your RudderStack events, you ensure that those users are identified in Knock when your event triggers a workflow. As an example, take the user-signed-up event below. We're currently mapping the `properties.recipients` field to the `recipients` field of our workflow in Knock. If we send this event to Knock before the user with id `sam10` has been identified, the user will not be notified. ```json title="A RudderStack event without inline identify" { "event": "user-signed-up", "email": "sam@example.com", "userId": "sam10", "type": "track", "messageId": "rudderstack-test-message-123", "properties": { "recipients": ["sam10"], "account_id": "123" }, "timestamp": "2023-05-23T21:49:54Z" } ``` To ensure that the user is notified, we'd change the id reference in `recipients` to a complete user object, as in the example below. This way Knock has all the information it needs to identify the user during workflow runtime. ```json title="A RudderStack event with inline identify" { "event": "user-signed-up", "email": "sam@example.com", "userId": "sam10", "type": "track", "messageId": "rudderstack-test-message-123", "properties": { "recipients": [ { "id": "sam10", "name": "Sam Seely", "email": "sam@example.com" } ], "account_id": "123" }, "timestamp": "2023-05-23T21:49:54Z" } ``` You can learn more about inline identification in [our documentation on identifying recipients](/managing-recipients/identifying-recipients). ## Event idempotency You can enable idempotency checks to deduplicate RudderStack events that have already been received and processed. This is useful if you know RudderStack may send duplicate events to Knock. Knock automatically uses the `messageId` field from the RudderStack track spec as the idempotency key. No additional field configuration is needed — RudderStack includes a `messageId` in every track and identify event by default. To enable idempotency, open the **Settings** tab for your source environment configuration and toggle idempotency checks on. A screenshot of where to configure idempotency for your Source. ```json title="An example RudderStack event with a valid idempotency key." { "type": "track", "event": "user.created", "messageId": "some-id-from-rudderstack", "properties": { "id": "user-1", "email": "user-1@example.com" } } ``` Events without a `messageId` are processed normally. For details on how Knock handles idempotent events, key validation rules, and the default 24-hour idempotency window, see the [source event idempotency](/integrations/sources/overview#source-event-idempotency) section of the sources overview. ## Video walkthrough This video is Segment-specific, but you can apply any of the portions within the Knock dashboard to RudderStack as well.
## HTTP The legacy HTTP source accepts events using the Segment track spec. For new integrations, use the custom source instead. --- title: HTTP source (legacy) description: The legacy HTTP source accepts events using the Segment track spec. For new integrations, use the custom source instead. metaTitle: HTTP source integration (legacy) metaDescription: Use the legacy HTTP source to send Segment track spec events into Knock. For new integrations, use the custom source instead. section: Integrations > Sources layout: integrations --- The legacy HTTP source uses the Segment track spec event schema and is still supported for existing integrations. For new integrations, use the{" "} custom source instead. } /> The legacy HTTP source creates a generic event ingestion endpoint that accepts events structured according to the Segment track spec. The events you stream into the HTTP source can be used to trigger Knock notification workflows. Knock can receive any structured event data via the legacy HTTP source, as long as you format the payload as JSON and make an HTTP request from the service that produces or consumes events. ## Getting started To get started you can create a new HTTP source by going to **Integrations** > **Sources** under your account settings and clicking "Create source." You'll have the opportunity to name the HTTP source and give it a description. Once your source is created, you'll have a unique ingestion endpoint per environment to send events to. You can get each environment's endpoint by clicking the "Copy webhook destination URL" button. ## Sending event data To start sending events to Knock, structure a JSON payload that matches the event schema below and submit it via a `POST` request to the ingestion endpoint. A well-formed event payload receives a `204` response code. You also need to send the following headers with your request: ```txt title="Request headers" Content-Type: application/json ``` You need to send a single event at a time. There is no batch event ingestion endpoint. ## Event schema Your events must be structured as JSON with the following schema: | Name | Type | Description | | ------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------- | | `type` | string (required) | The type of event to send. Must be set to `track`. | | `event` | string (required) | The name of the event you're sending to Knock. | | `properties` | map | A set of properties associated with the event. | | `userId` | string | An optional userId to be used as the recipient or actor for the triggered workflow. | | `messageId` | string | An optional unique identifier for the event message, to be used as a deduplication key. | | `timestamp` | string | An optional ISO-8601 timestamp indicating when the event occurred. If omitted, defaults to the time of ingestion. | ```json title="An example event payload" { "type": "track", "event": "My event", "properties": { "foo": "bar" } } ``` ## Triggering workflows from received events Received events can be configured as workflow triggers directly in the workflow editor. Click on the workflow's trigger step and change the type from "API" to "Source Event." Then you can select the event and map its properties into the data the workflow needs. ## Disabling a trigger Triggers are automatically enabled when you create them. If you want to stop an event from triggering a workflow, go to the trigger page and toggle its status to "Inactive." This disables that trigger for the current environment only. When you're ready to trigger the workflow again, set it back to "Active." ## Mapping workflow trigger properties When creating workflow triggers from your events, you can optionally configure the schema mapping Knock uses to map your event properties into the corresponding workflow trigger properties. To target any items under the `properties` key, prefix the schema mapping with `data.propertyKey`. As an example, if you have a property `properties.recipientId` you would map this as `data.recipientId`. ## Debugging events You can see a log of all events received per source under **Integrations** > **Sources** in the Knock dashboard under the "Logs" page for your configured source. You can also see any workflow triggers that were configured as part of the event ingestion, and any workflow runs that were triggered. ## Frequently asked questions No, the legacy HTTP source only accepts track events and not user identifies. Please use the [user identify API](/concepts/users) instead. Yes. You can use [inline identification](/managing-recipients/identifying-recipients#inline-identification) with the source events that you send to Knock. You need to ensure that the schema mapping for your event maps the **Recipients** of your workflow to the same field where you provide the recipient object. There's no rate limit for the event ingestion endpoint, but we ask that if you're going to be sending more than 1,000 events per second you reach out to us first so that we can provision additional capacity. No, the HTTP endpoint only accepts single events at a time. # Email ## Overview Learn how to send transactional email notifications with the Knock API. --- title: Email notifications with Knock description: Learn how to send transactional email notifications with the Knock API. section: Integrations > Email layout: integrations --- Effortlessly design and deliver email notifications to downstream providers, without the need to keep HTML templates in your backend codebase. ## Features - **Easy to get started, and extend**: Knock email notifications look great out of the box. We include sensible, well tested, default responsive styles so you can get up and running quickly across all major email clients. Check out the [Settings](/integrations/email/settings) page to learn about available configuration options. - **Block based visual editor**: Don't want to write HTML? No problem! You can use our drag-and-drop visual editor to get started. - **Fully flexible templates and styling**: If you need to write custom HTML or CSS you can drop down to a raw code editor to create your email messages. You're never constrained to the templates Knock provides. - **CSS inlining**: By default, Knock will inline all CSS included with your emails to ensure maximum compatibility across email clients. This behavior can be disabled per-environment as a setting on your email channel's configuration. - **Text generation**: We'll automatically generate a text version of your emails, so you never need to write both HTML and text templates. - **Multiple layouts**: We support any number of emails layouts that can "wrap" your email templates and provide styles and shared elements like headers and footers. - **MJML support**: Use [MJML](/integrations/email/mjml) to build responsive email layouts and templates with built-in mobile compatibility. - **Attachments support**: It's easy to send attachments alongside your emails, just pass through some Base64 encoded data along with your `workflow.trigger` call and you're done! - **Knock link and open tracking**: Capture link-click and email-open events right within your Knock account. For more details, see the [Knock link and open tracking documentation](/send-notifications/tracking). - **Per environment configuration**: Configure different settings for each environment in your Knock account. - **Sandbox mode**: Use sandbox mode to test your email steps before sending an actual email. ## Supported providers Knock currently supports sending email notifications to the following email providers. Beyond the universally supported features mentioned above, each provider supports different features for tracking and managing email delivery. Some providers support multiple methods for delivery tracking. For example, Amazon SES requires webhook configuration to enable delivery tracking and bounce support. See each provider's integration page for setup instructions. } /> If your preferred provider is not listed here, please let us know by emailing us at support@knock.app. } /> ## Layouts Learn more about how to use layouts with your email templates in Knock. --- title: Email layouts description: Learn more about how to use layouts with your email templates in Knock. section: Integrations layout: integrations tags: [ "precontent", "css", "layout", "styles", "design system", "button", "preview text", "branding", "email preview text", ] --- Knock emails are built from two pieces: layouts and templates. The **layout** is the shared "frame" of your email. The header, footer, and any HTML or CSS you want applied across templates. You define it once so every email renders consistently. The **template** is the body of a specific email, authored in an email workflow step. At send time, Knock injects the template into the `{{content}}` variable of its layout to produce the final email. Here's an example of a transactional email we send at Knock, with the template content merged into the layout. Green is the template, blue is the layout. An example of a layout and template within an email notification Knock provides two system-level variables for use in your layouts: | Variable | Description | | -------------- | ---------------------------------------------------------------------------------------------------------------- | | `content` | The rendered email template is injected here. **Required — must appear somewhere in every layout.** | | `footer_links` | The footer links configured in the layout editor are injected here. Optional and not required in custom layouts. | ## The Knock default layout Every Knock account starts with a default layout at **Content** > **Email layouts**. New email templates use this layout by default, so update it to change the shared look and feel of all your emails. Click the "Default" layout to open the visual editor, where you can configure footer links for HTML emails. Logo, icon, and brand color come from the **Branding** page under your account settings. To edit the HTML and CSS directly, click "Edit in code editor" to open the [custom layout](/integrations/email/layouts#custom-layouts-and-styling) editor. Each layout also includes a plaintext version under the "Text" tab. It works the same way, the plaintext body of your template is injected into `{{content}}`. ## Select a layout for your email template New email templates use your `default` layout. To use a different layout on a specific step, open the template editor and pick one from the "Email layout" dropdown. Email layouts follow the Knock environment commit model — commit them to your current environment before they'll appear in your email notifications. } /> ## Custom layouts and styling To create a custom layout, open the layout editor and click "Edit in code editor" to edit HTML and CSS directly. Your layout must include `{{content}}` somewhere inside its `` as that's where Knock injects the template at send time. Layouts can also use [MJML](/integrations/email/mjml). An MJML layout must have a root `` tag instead of an HTML document, and any plain HTML inside must be wrapped in `` tags. If you're providing a custom HTML layout for your emails, the layout must be a valid HTML document. For MJML layouts, the layout must be a valid MJML document with the <mjml> root tag. } /> ### Creating new layouts To create a new layout, go to **Developers** > **Layouts** and click "Create layout." New layouts open in the visual editor by default; click "Edit in code editor" to switch to HTML and CSS. ### Using custom fonts Reference custom web fonts inside your layout's `` with a `` tag. The `@import` rule is not supported: ```html ``` Many email clients don't support web fonts, so pair any custom font with a web-safe fallback. ### Using variables and brand attributes in a custom layout Inject account- and environment-level variables into your layout with the `vars.*` namespace. This is useful for global values that are the same across all emails, like a base URL for embedded links. Branding properties set in account settings are available under `vars.branding.*`: | Variable | Description | | ------------------------------------------- | -------------------------------------------------------------------------------------------- | | `vars.branding.logo_url` | URL of the logo image for your brand. | | `vars.branding.icon_url` | URL of the icon image for your brand. | | `vars.branding.dark_logo_url` | URL of the logo image for dark mode. Defaults to `logo_url` if not set. | | `vars.branding.dark_icon_url` | URL of the icon image for dark mode. Defaults to `icon_url` if not set. | | `vars.branding.primary_color` | Primary brand color (hex). Defaults to `#000000`. | | `vars.branding.primary_color_contrast` | Contrast color for text on primary color backgrounds (hex). Defaults to `#FFFFFF`. | | `vars.branding.dark_primary_color` | Primary brand color for dark mode (hex). Defaults to `#FFFFFF`. | | `vars.branding.dark_primary_color_contrast` | Contrast color for text on dark mode primary color backgrounds (hex). Defaults to `#000000`. | With [per-tenant branding](/multi-tenancy/per-tenant-branding), Knock resolves these properties against the `tenant_id` on the workflow run, falling back to account-level branding if the tenant has none set. ### Dark mode support The Knock default email layout supports dark mode out of the box via the `prefers-color-scheme` CSS media query. If your default layout was created before April 21, 2026, it does not support dark mode. Either create a new layout (which starts from Knock's latest default) or add dark mode support to your existing layout using the snippet below. } /> To opt a custom layout in to dark mode, add color scheme meta tags, set `color-scheme` on `:root`, and override colors inside a `@media (prefers-color-scheme: dark)` block: ```html title="Adding dark mode support to a custom layout" ``` #### Swapping logos and icons in dark mode If your logo or icon doesn't render well on a dark background, upload dark-mode versions in account branding and expose them via `vars.branding.dark_logo_url` and `vars.branding.dark_icon_url`. Render both images in your layout and toggle them with CSS: ```html title="Rendering light and dark logos" {% assign dark_logo_url = vars.branding.dark_logo_url | default: vars.branding.logo_url %} {{ vars.app_name }} {{ vars.app_name }} ``` Hide the dark image by default and swap them inside your dark mode media query: ```css title="CSS to toggle light and dark images" .dark-img { display: none; visibility: hidden; } @media (prefers-color-scheme: dark) { .dark-img { display: inline !important; visibility: visible !important; } .light-img { display: none !important; visibility: hidden !important; } } ``` The ` {{ content }} ``` ### Defining a blank email layout To send a template without any layout wrapper, select "No layout" in the "Email layout" dropdown in your email channel step's template settings. ## Layouts and the visual template editor You can use the [visual template editor](/template-editor/email-templates#visual-editing-with-drag-and-drop-components) to compose your template's `content` regardless of which layout the email step uses. Layouts already include <head> and{" "} <body> tags, so your template shouldn't. If you're porting email content from elsewhere, strip those tags before saving — or select "No layout" to skip the layout wrapper entirely. } /> When you insert components like buttons and dividers from the visual editor, Knock auto-generates CSS from a set of base component styles (see below), which you can override to match your design system. The visual template editor only renders component styles in preview mode, so any changes to base styles won't show up until you preview. } /> ### Updating base component styles Visual editor components use the base styles below, which Knock auto-injects into the top of your layout's `` at runtime. To match your design system, override them in your layout's ``. Because these styles are injected at runtime, overrides must use `!important` to win — for example, `font-size: 20px !important;` to change the `block-button-sm` font size. ```css title="Base component styles" /* Button components */ .block-row.block-row--button_set-v1 .block-button { display: inline-block; box-sizing: border-box; text-decoration: none; -webkit-text-size-adjust: none; } .block-row.block-row--button_set-v1 .block-button.block-button--outline { border-style: solid; } .block-row.block-row--button_set-v1 .block-button.block-button--sm { font-size: 14px; line-height: 20px; min-width: 32px; padding-top: 4px; padding-bottom: 4px; padding-left: 8px; padding-right: 8px; } /* Divider component */ .block-row.block-row--divider-v1 .block-divider { border-bottom: 1px solid #dddee1; } /* Markdown components */ .block-row.block-row--markdown-v1 .block-markdown > :first-child { margin-top: 0; } .block-row.block-row--markdown-v1 .block-markdown > :last-child { margin-bottom: 0; } ``` ## Automate layout management with the Knock CLI You can manage layouts programmatically with the [Knock CLI](/developer-tools/knock-cli) or [Management API](/developer-tools/management-api), keeping Knock layouts in sync with files in your application code, and committing and promoting changes as [part of your CI/CD workflow](/tutorials/integrating-into-cicd). See [Layout file structure](/cli/email-layout/file-structure) for how layout files are organized and the [Knock CLI reference](/cli/overview) for the full command set. Contact us if you have questions. ## MJML Learn how to use MJML to build responsive email layouts and templates in Knock. --- title: MJML support description: Learn how to use MJML to build responsive email layouts and templates in Knock. section: Integrations layout: integrations tags: [ "mjml", "responsive", "email", "layouts", "templates", "mobile", "liquid", "mj-raw", "css inlining", ] --- Knock supports MJML, a responsive email framework that compiles to HTML that is optimized for email clients. MJML abstracts away the complexity of table-based layouts and media queries, so you can build responsive emails that look great across devices with less code. ## MJML layouts You can set an [email layout](/integrations/email/layouts) to use MJML. When a layout is configured for MJML, it must contain a root `` tag. The layout structure works the same as HTML layouts: the `{{content}}` variable receives the template content, and `{{footer_links}}` receives footer links when configured. To include plain HTML within an MJML layout, wrap it in `` tags. MJML will pass through the contents of `` without compiling them, so you can use standard HTML where needed: ```mjml title="MJML layout with for HTML" Welcome {{ content }} ``` ## MJML templates Email templates can use MJML in two ways: 1. **Full template.** Write your entire template in MJML in the code editor. MJML templates can be used with "No layout" (standalone) or within an MJML layout. 2. **Visual block editor.** Use the visual editor to compose your template. When the template or its layout is MJML, the blocks render as MJML components. When you use an MJML template with a layout, the layout must also be MJML. When you use "No layout," the template stands alone as a complete MJML document. ## Mixing HTML and MJML Knock automatically wraps plain HTML inside MJML templates and layouts in `` tags when the HTML appears at the ``, ``, or `` level. This means you can include HTML snippets in your MJML templates and they will render correctly. ### Ending tags Knock skips auto-wrapping of HTML when the parent element is an MJML ending tag. Ending tags are MJML components that expect plain HTML or text content as their direct children by spec. The following MJML tags do not have their HTML children wrapped: - `` - `` - `` - `` - `` - `` - `` - `` - `` - `` HTML partials cannot include MJML. When an HTML partial is used in an MJML template and is not rendered as a direct child of an MJML ending tag, Knock wraps the partial's content in <mj-raw> tags so that it renders correctly within the MJML document. } /> ## Using Liquid in MJML You can use [Liquid](/template-editor/reference-liquid-helpers) in MJML layouts and templates to render dynamic content, but where you can place it is determined by the order in which Knock compiles your MJML. Knock renders an MJML email in two steps: 1. **MJML compile.** Knock compiles your MJML document into HTML. Liquid is still unrendered text at this point. 2. **Liquid render.** At send time, Knock renders Liquid tags and variables against the compiled HTML. Because the MJML compiler runs first, it has to parse your document with the Liquid still in it. Liquid that sits where the compiler expects an MJML component will break compilation, and your final message may not render as expected. ### Where you can use Liquid | Location | Supported | What to do | | ------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------- | | Inside `` and the components below it | Yes | Write Liquid directly. No wrapping needed. | | Inside an `` or `` block | Yes | Open and close each Liquid tag inside the same block. | | Wrapping or outside the `` root tag | No | Branch inside ``, or create a second layout. | | Bare at the `` level | No | Move the Liquid into an `` or `` block. | | Wrapping ``, ``, or `` | No | Declare these without a conditional and reference them dynamically in CSS instead. |
Here's an example of an email layout using Liquid in the places it's supported: ```mjml title="Liquid in an MJML layout" {% if vars.use_dark_mode %} {% endif %} {% if vars.use_dark_mode %} .email-body { background-color: #262626; } {% endif %} Hello {{ recipient.name }} {{ content }} {% if recipient.support_plan_enabled %} Need help? Contact support. {% endif %} ``` ### Troubleshooting If a preview or email message displays raw CSS styles and `` tags as body text, this indicates an issue with MJML compilation. Check for Liquid outside the `` root, a conditional straddling an `` block, or a Liquid tag wrapping a compile-time component. #### Common mistakes The MJML compiler has no position for content outside the root in the document it produces, so compilation fails when you add Liquid outside the `` root tag. This also applies between `` and its `` and `` children. ```mjml title="Incorrect: a conditional wrapping the whole document" {% if vars.use_marketing_layout %} {% else %} {% endif %} ``` Instead, keep one `` root and move the conditional inside ``: ```mjml title="Correct: branching inside " {% if vars.use_marketing_layout %} {% endif %} {{ content }} ``` If the two shapes differ enough that a single document can't express both, create a second layout and select it on the email step. These MJML tags are used within `` to configure the compiler's output, which means that the limitation mentioned in the table above applies. However, you can't resolve this by simply moving these tags inside an `` block and wrapping them with a condition there, because `` preserves its contents without compiling them. A tag will be emitted as literal `` markup instead of being processed by the compiler. For example, wrapping `` in a conditional to switch fonts per brand theme won't work: ```mjml title="Incorrect: a conditional around " {% if vars.brand_theme == "editorial" %} {% else %} {% endif %} ``` When the component has a plain-HTML equivalent, write that equivalent instead and keep the conditional inside an `` block. Here, `` becomes the `` tag it would have generated: ```mjml title="Correct: use and conditionally wrap HTML font imports" {% if vars.brand_theme == "editorial" %} {% else %} {% endif %} ``` Alternatively, declare both fonts unconditionally so the compiler loads each one, then pick between them at send time in an `` block: ```mjml title="Correct: declare both fonts, select one conditionally in CSS" .email-body, .email-body td { font-family: {% if vars.brand_theme == "editorial" %} "Roboto Slab", Georgia, serif {% else %} "Inter", Helvetica, sans-serif {% endif %}; } ``` The tradeoff to this approach is that every declared font is referenced in the final email, so recipients' email clients may load a font that the message doesn't end up using. `` and `` have no plain-HTML equivalent. For those, declare them without a conditional and branch in CSS instead, similar to the second example above. Both halves of a Liquid tag pair must sit inside the same block. When a conditional wraps only one of an MJML block's tags, compilation will fail. ```mjml title="Incorrect: the conditional wraps only one of the block's tags" {% if vars.use_dark_mode %} {% endif %} .email-body { background-color: #262626; } ``` ```mjml title="Correct: the conditional is contained within a single block" {% if vars.use_dark_mode %} .email-body { background-color: #262626; } {% endif %} ``` ## Dark mode support in MJML layouts MJML layouts support dark mode using the `prefers-color-scheme` media query. The Knock default MJML layout includes dark mode support out of the box, automatically swapping colors and images based on the user's system preference. To add dark mode support to a custom MJML layout, include the color scheme meta tags in `` and define your dark mode styles in an `` block: ```mjml title="MJML layout with dark mode support" :root { color-scheme: light dark; supported-color-schemes: light dark; } @media (prefers-color-scheme: dark) { .email-wrapper, .email-body { background-color: #262626 !important; } p, h1, h2, h3 { color: #ffffff !important; } } ``` For swapping logos and icons in dark mode, see [Swapping logos and icons in dark mode](/integrations/email/layouts#swapping-logos-and-icons-in-dark-mode) in the email layouts documentation. ## Styling buttons in MJML layouts The Knock default MJML layout styles buttons using your branding colors, with automatic dark mode support. This applies to both legacy `mj-button` components and visual editor buttons. #### How branding colors are applied - **Solid buttons** use `primary_color` as the background and `primary_color_contrast` as the text color. - **Outline buttons** use a transparent background with `primary_color` as the text and border color. - **Dark mode** uses `dark_primary_color` (defaults to `#FFFFFF`) and `dark_primary_color_contrast` (defaults to `#000000`). ```mjml title="MJML branding-aware button styling" /* Light mode button styles */ .block-button--solid { background-color: {{ vars.branding.primary_color | default: "#000000" }}; color: {{ vars.branding.primary_color_contrast | default: "#FFFFFF" }}; } .block-button--outline { background-color: transparent; color: {{ vars.branding.primary_color | default: "#000000" }}; border-color: {{ vars.branding.primary_color | default: "#000000" }}; } /* Dark mode overrides */ @media (prefers-color-scheme: dark) { .block-button--solid { background-color: {{ vars.branding.dark_primary_color | default: "#FFFFFF" }} !important; color: {{ vars.branding.dark_primary_color_contrast | default: "#000000" }} !important; } .block-button--outline { background-color: transparent !important; color: {{ vars.branding.dark_primary_color | default: "#FFFFFF" }} !important; border-color: {{ vars.branding.dark_primary_color | default: "#FFFFFF" }} !important; } } ``` #### Why dark mode uses `!important` overrides The visual editor renders button colors as static inline styles that don't change between light and dark mode. To ensure buttons remain readable on dark backgrounds, the layout uses `!important` in dark mode to override these inline styles. This means per-button colors from the visual editor are respected in light mode, while dark mode enforces branding-aware colors for readability. For more details, see [Styling buttons](/integrations/email/layouts#styling-buttons) in the email layouts documentation. ## Limitations - **MJML layouts require the `` root tag.** Layouts set to MJML mode must be valid MJML documents, with a single `` root and no content outside it. - **Liquid can't wrap the `` root or compile-time components.** Knock compiles MJML before it renders Liquid. See [Using Liquid in MJML](#using-liquid-in-mjml) for the placement rules. - **Partials cannot include MJML.** [HTML partials](/template-editor/partials/html-partials) must contain HTML only. When used in an MJML template, their content is automatically wrapped in `` tags where appropriate. ## Settings and overrides Learn more about how to configure your email channels in Knock. --- title: Email settings and overrides description: Learn more about how to configure your email channels in Knock. section: Integrations layout: integrations tags: ["bcc", "cc", "JSON overrides", "email settings", "email overrides"] --- Knock email channel configurations support a number of settings. These include email-specific fields (such as `cc`, `bcc`, and `reply-to`) as well as JSON overrides to be passed in API calls to the configured provider's API endpoints. When you configure a setting in a channel's configuration, it will be used on all instances of that channel across all workflows. ## Configuring email settings You can override email settings on a per-channel basis, or on a per-template basis. - **At the channel level**. The `to`, `cc`, `bcc`, and `reply-to` fields can be found in the "Overrides" section of the channel's "Settings" tab. - **At the template level**. The email settings fields can be found at the top of the email template editor. Email-specific configuration fields support Liquid usage. You can use any variables available in your workflow trigger payloads, as well as system [variables](/template-editor/variables) such as the current workflow, activities, and any other variables you have access to when building templates. As an example, if you wanted to conditionally change the "From name" on an email depending on whether one is configured on the actor that triggered the notification, you'd use the following liquid in the "From name" field of your email configuration. ```js title="A From name email configuration using Liquid" {{ actor.from_name | default: "no-reply@knock.app" }} ``` Provider settings like API keys and other send-time credentials do not support dynamic values; check your specific provider's **Channel configuration** documentation for more information about supported values. ## Overriding the default `to` address By default, Knock will send your emails to the `email` property stored on the `recipient` for the workflow run. If you need to override this, you can do so by setting the `to` field in your email configuration either at the channel or the template level. As an example, if you wanted to send all emails to a single address, you could set the `to` field at the channel level to either a static value (like `hello@example.com`) or a dynamic value (like `{{ data.email_to_override }}`). ### Using multiple `to` addresses It's possible to set multiple `to` addresses via an override. However, because Knock is designed to process a unique workflow run for each `recipient` in your workflow trigger, this approach comes with some caveats and limitations: - You must always provide at least one `recipient` on your workflow trigger. - Although you may override the `to` email addresses to send an email to more than one address in a given workflow run, that run will reference only the original `recipient`'s properties and notification preferences. - Delivery and engagement metrics will also be associated with the original `recipient`, because all of the delivered emails will be related to a single Message record in Knock. This means that you won't be able to track per-recipient metrics for the list of email addresses in your override; they'll all be tracked by the workflow `recipient`. - Knock does not currently support a comma-separated list of `to` addresses in the same way as we do for `cc` and `bcc` addresses (see section below). This means that you will need to provide a [JSON payload override](#provider-json-overrides) in your workflow step's configuration in order to format the `to` list according to your provider's API requirements. Please [reach out to our support team](mailto:support@knock.app) if you need help with this. ## Setting `cc` and `bcc` addresses The `cc` and `bcc` fields can be used to support a single or multiple addresses. In order to use several addresses on any of these fields, make sure to separate them with a comma. You can pass multiple cc and bcc addresses using workflow trigger variables, and configure these fields to use them. } /> ## Provider JSON overrides Sometimes you may want to customize the API call Knock sends to your email provider. A good example of this is passing custom arguments as part of the API payload. Take an example where we're using SendGrid as our email provider. SendGrid allows sending custom arguments under the `custom_args` key of the JSON payload of their API. By default, Knock sends some arguments using that key, such the Knock message id. If you want to add more arguments, you can check the following image on how to add them as JSON overrides:
Channel JSON overrides
Configuring email channel JSON overrides.
In this example, we want to add two arguments to the `custom_args` attribute of the API call we send to SendGrid. In this case, the first argument will be hardcoded, and the second argument's value will be the value of `dynamic_value`, which we expect to be passed in the payload of the workflow trigger call. ## Sending attachments Learn more about how to send emails with attachments in Knock. --- title: Email attachments description: Learn more about how to send emails with attachments in Knock. section: Integrations layout: integrations --- ## How attachments work in Knock 1. Set an attachment key in your email template to tell Knock how to resolve attachments you send in the data payload of your trigger call. To set an attachment key, click the gear icon (⚙️) at the top of the email template editor, then set your desired key under the "Attachment key" field. This key will default to `attachments` if not specified. 2. Include one or more attachment objects in the `data` payload of your trigger call, under the configured attachment key. Each attachment object should have the content of the file to be attached as a base64 encoded value. 3. Knock automatically adds any attachments included in the attachment key of your trigger call to the emails sent by your email provider. ## The attachment object Every attachment you send to Knock in your `data` payload should include the following properties (those marked with an `*` are required): | Property | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name`\* | The name of the file | | `content_type`\* | A mime type for the file | | `content`\* | The base64-encoded file content. Attachments count toward the 10MB trigger payload limit, which applies to the full request. Note that some email providers may enforce stricter limits. | | `content_id` | An optional unique `Content-ID` for the attachment. Currently only supported for SendGrid, Postmark, and Resend. See Referencing attachment files inline for more details. | | `disposition` | An optional disposition for the attachment (`inline` or `attachment`). Currently only supported for SendGrid and Postmark. |
```js title="An example attachment object" { name: "my-file.txt", content: myFileContent, content_type: "text/plain" } ``` ## Sending attachments in your trigger call Once you've specified your attachment key in your email template, include the base64-encoded attachment data in the `data` payload of your trigger call. To send multiple attachments, pass an array of attachment objects under the attachment key. ## Sending a different attachment per recipient If you need to send a different attachment per recipient in a workflow then you'll need to make one trigger call per recipient, such that the data payload is unique to that recipient. ```js title="Unique attachments per recipient" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const filesAndRecipients = [ { id: "user-1", file: user1FileContents }, { id: "user-2", file: user2FileContents }, ]; filesAndRecipients.forEach(({ id, file }) => { knock.workflows.trigger("my-workflow", { recipients: [id], data: { attachment: { name: "Invoice.pdf", content_type: "application/pdf", content: file, }, }, }); }); ``` ## Referencing attachment files inline If you're using [SendGrid](/integrations/email/sendgrid) or [Postmark](/integrations/email/postmark) as your email provider, you can set attachment files to be inline in your email messages by providing a `disposition` property of `inline` on the attachment object you send in your trigger call. [Resend](/integrations/email/resend) supports inline images (see below), but does not allow you to explicitly set the disposition of an attachment. ### Inline image attachments When using Resend, you can reference images inline without setting a{" "} disposition property on the attachment object. } /> To attach image files to your email and reference them inline, add a `content_id` and `disposition` to the attachment object. You can then reference the attachment in your template using the `cid:` prefix and the `content_id` value. ```js title="An example inline image attachment" { name: "my-image.png", content_type: "image/png", content: imageContent, content_id: "my-attachment", disposition: "inline" } ``` ```html title="Referencing an attachment file inline" ``` ### Inline calendar invitations To set calendar files to be inline in your email message, add a `disposition` property of `inline` on the attachment object you send in your trigger call. ```js title="An example attachment object for an inline calendar invitation" { name: "invite.ics", content_type: "text/calendar; charset=utf-8; method=REQUEST", content: calendarContent, disposition: "inline" } ``` We also recommend that your `.ics` file include the following parameters for the best email client support: - `METHOD:REQUEST` - `RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION` (on the `ATTENDEE`, to enable RSVPs) ## Sending attachments with batched workflows If you trigger a batched workflow with attachment files, email steps after the batch step will only include the attachment file(s) from the first activity in the batch. } /> When a workflow includes a [batch step](/designing-workflows/batch-function), only the attachment(s) from the activity that **opened the batch window** will be included in email messages that are sent after the batch window closes. Attachments from subsequent `activities` that are added to the batch will not be included. To send a single email that includes an attachment file for each batched activity, use a [fetch function](/designing-workflows/fetch-function) step placed after your batch step to collect them once the window closes: 1. **Trigger the workflow per item with identifiers, not file content.** Include a reference to each attachment (such as an ID or URL) in your `data` payload rather than the base64-encoded file content. 2. **Batch by recipient.** Group the per-item triggers using your batch step as you normally would. 3. **Add a fetch step after the batch step.** Using the `activities` array to enumerate every batched item, call back to your service to retrieve a single array of attachment objects under one key. The data returned by the fetch step is merged into the workflow run's `data` and made available to the email channel step that follows. 4. **Point your template's attachment key at the fetched data.** Set the email template's attachment key to [the response path key](/designing-workflows/fetch-function#specifying-the-response-path) that you configured on the fetch step so that all of the collected files are sent with the email. If you don't specify a response path, the fetch step's response data will be merged into the workflow run's `data` at the top level. The fetch step's request body is a Liquid-compatible input, so you can loop over the `activities` array to build the list of identifiers to send to your service: ```liquid title="Fetch step request body: collecting attachment IDs from the batch" { "attachment_ids": [ {% for activity in activities %} "{{ activity.attachment_id }}"{% unless forloop.last %},{% endunless %} {% endfor %} ] } ``` Your service then returns the assembled attachments, which Knock merges into the workflow run's `data`: ```json title="Response data returned by your service" { "attachments": [ { "name": "invoice-1.pdf", "content_type": "application/pdf", "content": "" }, { "name": "invoice-2.pdf", "content_type": "application/pdf", "content": "" } ] } ``` In the example above, if no response path key is specified on the fetch step these returned `attachments` will be available under `data.attachments`. This matches the default attachment key for newly-created email templates. While the example above shows how you can send a single email that includes an attachment for every batched activity, you should be aware that email providers and your users' email clients have size limits on email messages, often between 10MB and 25MB. If you're sending a large number of attachments, you may need to send multiple emails or use a different approach, such as sending a single email with links to hosted files. } /> ## Previewing messages with attachments The Knock dashboard does not currently support previewing the attachment files that are sent with your email messages. This means that you'll need to send a test message (via the [workflow test runner](/send-notifications/testing-workflows) or the API) to your own email inbox to see what the attachment(s) will look like. This limitation applies to both regular attachment files and inline attachments. Any images that are sent as inline attachments will not be viewable in the dashboard. ## Client previews Preview your email notifications across different email clients directly in the Knock editor. --- title: Email client previews description: Preview your email notifications across different email clients directly in the Knock editor. section: Integrations > Email layout: integrations --- Email client previews are available on all paid plans (Starter and up). } /> ## Overview Email client previews allow you to test how your email notifications will render across different email clients. This feature is embedded directly in the Knock template editor, using Litmus technology to generate accurate previews without requiring you to leave the editor or send test emails. ## Features - Preview emails in popular clients including Gmail, Outlook, and Apple Mail. - Access previews directly within the Knock template editor. - View rendering across multiple desktop and mobile email clients. - Preview emails in dark mode for supported clients (Outlook for Windows, iOS Outlook, Android Outlook, iOS Gmail, Android Gmail, and Apple Mail). ## Using email client previews To access and use email client previews: 1. Open the workflow template editor for an email channel. 2. Make sure the preview pane is opened. By default, you'll see the "in browser" preview. 3. Use the toggle option at the top of the preview pane to switch to "client previews." 4. Click on any email client thumbnail to see an expanded view. This feature uses the Litmus Instant API to generate client-specific previews. ## Knock test emails Start testing with Knock's built-in email channel. --- title: Send email with Knock's built-in test channel description: Start testing with Knock's built-in email channel. section: Integrations > Email layout: integrations --- Knock comes with a built-in email channel to test your email notifications. This is a great way to explore Knock and start testing email notifications before integrating your [email provider of choice](/integrations/email/overview#supported-providers). This provider is for testing purposes only. Sending is limited to email addresses associated with the{" "} members {" "} of your Knock account. } /> ## Features - 100 emails per month - Attachments support - Delivery tracking - Bounce support - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You'll find the Knock test channel under the **Channels and sources** page in your account settings. By default, all new Knock accounts will have the channel enabled and ready to use across all environments with no additional configuration required. ### Allowed recipients The Knock test channel is limited to sending emails to email addresses associated with the **[members](/manage-your-account/managing-members) of your Knock account only**. Attempting to send emails to email addresses that are not associated with a member will result in a delivery error (`knock_recipient_not_allowed`). ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your channel. When enabled, no emails will be sent to your recipients. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings** - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Recipient data requirements To send an email notification you'll need a valid `email` property set on your recipient. ## Delivery tracking Delivery tracking can result in the following status updates to your message: - The message delivery is confirmed and Knock updates the message to `delivered` - The message was not delivered due to a bounce and Knock updates the message to `bounced` ## Frequently asked questions You can send up to 100 emails per month for free. You can only send emails to email addresses that are associated with the [**members**](/manage-your-account/managing-members) of your Knock account. Attempting to send emails to email addresses that are not associated with a member will result in a delivery error. If you exceed the monthly limit, your emails will be marked as `undelivered` and will not be sent to the recipient. You will be able to send emails again at the start of the next month. You can use the Knock test channel to send email notifications to your users. To do this, you'll need to create a new workflow or broadcast and add a step that uses the Knock test channel. No, this provider is for testing purposes only and is not intended for production use. For production use, please configure a different provider. Once you've setup a production email channel, you can change the channel on each of your existing email steps in your workflows to use the new provider. You'll need to commit and promote those changes in order for them to take effect. ## Amazon SES How to send transactional email notifications to Amazon SES with Knock. --- title: How to send email with Amazon SES description: How to send transactional email notifications to Amazon SES with Knock. tags: ["simple email service", "amazon", "aws", "ses"] section: Integrations > Email layout: integrations --- Knock integrates with Amazon Simple Email Service (Amazon SES) to send email notifications to your users. This page walks through how to get started with SES, including provider configurations and additional data you can pass through to SES. ## Features - Attachments support - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Amazon SES channel in the dashboard under **Channels and sources** in your account settings. From there, you'll need to take some steps in AWS before you can configure your SES channel within Knock. You'll need to verify the **"From" email address** you plan on using to send emails with AWS if you haven't already. To do so, follow the steps outlined in AWS's documentation on creating and verifying an email address identity. Knock supports two authentication schemes with Amazon SES: To send notifications via Amazon SES using an IAM User, Knock requires the **access key ID** and a **secret access key** of an AWS user with SES send permissions. (Specifically, the `ses:SendEmail` and `ses:SendRawEmail` permissions.) If you don't already have a user with send permissions, you can create an IAM user in AWS to use with the Knock API. You can learn more about creating IAM users in AWS here. Once you've created your new IAM user, you'll need to provision them with the policy below. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ses:SendEmail", "ses:SendRawEmail"], "Resource": "*" } ] } ``` Now that you have an AWS user created and provisioned with SES send access, grab the **access key ID** and a **secret access key** of the user—we'll use these later when configuring the SES channel within Knock. To send notifications via Amazon SES by delegating an IAM Role in your AWS account to Knock, secured with an External ID: 1. Create a new AWS Role: - For "Trusted Entity Type" choose "AWS Account." - Select "Another AWS account" and put "496685847699" in the Account ID. - Check "Require external ID" and enter the ID of the SES channel you created in your Knock dashboard. Configuring a new AWS role with an external ID
2. Attach the following permission policy to that role. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ses:SendEmail", "ses:SendRawEmail"], "Resource": "*" } ] } ``` 3. Use that role's ARN when configuring your Amazon SES channel in Knock. 4. Enable Security Token Service (STS) for the `us-east-2` region in your AWS account. This enables Knock to generate temporary security credentials for sending email via SES. For more information, review the AWS documentation on enabling STS for another region.
Now that you have a verified "From" address and either an AWS User's credentials or an AWS IAM Role to delegate to Knock, you're ready to [configure your SES channel](#channel-configuration) in the Knock dashboard under the **Channels and sources** page in your account settings.
Here are a few other things to keep in mind once you have your SES channel configured in Knock: - **SES sandbox mode.** By default, AWS places all new accounts in the SES sandbox. While your account is in the sandbox, you can only send emails to verified email address—keep this in mind if you're testing in development before you've moved your account out of the SES sandbox. For more information on the SES sandbox and how to move your account out of it, see the SES sandbox documentation. - **Deliverability tracking.** By default, SES channels do not track delivery beyond "Sent". However, you can enable [delivery status webhooks](#delivery-status-webhooks) to receive real-time updates about email delivery and bounces. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Amazon SES [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your SES channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Amazon SES** - **AWS region** (`enum*`) - The region of your verified domain. - **Authentication scheme** (`enum*`) - The authentication scheme (Access Key or External ID) to use for your SES channel. - **Access key ID** (`string*`) - The access key ID from your AWS account. Required when using Access Key authentication. - **Secret access key** (`string*`) - The secret access key from your AWS account. Required when using Access Key authentication. - **AWS IAM Role ARN to assume** (`string*`) - The ARN of the role in your AWS Account that this channel will use. Required when using External ID authentication. - **External ID** (`string*`) - The external ID for your AWS IAM Role. Required when using External ID authentication. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails (all as `Tags`): - `Sender`: always set to `knock.app` - `knock_message_id`: the ID of the message this email is associated with - `knock_workflow`: the key of the workflow this message was generated from - `knock_recipient_id`: the Knock ID of the recipient this email is being sent to You can learn about the role of these SES attributes in the Amazon SES API documentation. Amazon SES tags are limited to 256 characters and can only contain ASCII letters (a-z, A-Z), numbers (0-9), underscores (_), or dashes (-). If your{" "} knock_recipient_id does not meet these requirements, Knock will truncate knock_recipient_id to 256 characters and remove any prohibited characters. See the{" "} AWS Docs {" "} for more information. } /> ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Delivery status webhooks When enabled, SES will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your email messages. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A verified domain or email address in Amazon SES 2. An SES channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to AWS SNS (Simple Notification Service) configuration ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your Amazon SES channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 6. Copy the generated webhook URL - you'll need this in the next step In the AWS Console, configure an SNS topic to receive SES notifications: 1. Go to the **SNS Console** in AWS 2. Create a new SNS topic (or use an existing one) for SES notifications 3. Configure the topic to send notifications for: - **Delivery** notifications - **Bounce** notifications 4. Add an HTTPS subscription to the topic with the webhook URL from Knock 5. Knock will automatically confirm the subscription In the AWS SES Console, configure your verified domain or email to publish events to SNS: 1. Go to **Verified identities** in the SES Console 2. Select the domain or email address you're using with Knock 3. Go to the **Notifications** tab 4. Under **Feedback notifications**, configure: - **Bounces**: Select your SNS topic - **Deliveries**: Select your SNS topic 5. Save your configuration ### Supported delivery statuses When delivery status webhooks are enabled for SES, Knock will update message statuses based on these SES events: | SES Event Type | Knock Status | Description | | -------------- | ------------ | ------------------------------------------------------------------- | | Delivery | `delivered` | The email was successfully delivered to the recipient's mail server | | Bounce | `bounced` | The bounced due to invalid recipient or domain | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check SNS subscription status.** Verify the subscription is "Confirmed" in the AWS SNS console. 2. **Verify SNS topic configuration.** Ensure the SNS topic is correctly configured for Bounce, Complaint, and Delivery notifications. 3. **Check SES notification settings.** Confirm your verified identity is publishing to the correct SNS topic. 4. **Test with verified addresses.** If in SES sandbox mode, ensure you're sending to verified addresses. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## Cloudflare Email How to send transactional email notifications with Cloudflare Email Service and Knock. --- title: How to send email with Cloudflare Email description: How to send transactional email notifications with Cloudflare Email Service and Knock. tags: ["cloudflare", "email", "email service", "transactional"] section: Integrations > Email layout: integrations --- Knock supports using Cloudflare Email Service to send email notifications to your users. Cloudflare Email Service requires your domain's DNS to be configured correctly on Cloudflare. See{" "} Email Service domain configuration {" "} for DNS record details. } /> Cloudflare Email Service does not currently support delivery status tracking. Knock will not receive bounce or delivery confirmation events from Cloudflare for messages sent through this channel. } /> ## Features - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started 1. In the Cloudflare dashboard, create an **API token** (or use a compatible **API key**) with permission to send email for your account, and copy your Cloudflare **account ID**. See Cloudflare's API token documentation and how to find your account ID. 2. In Knock, open **Channels and sources** in your account settings and create a **Cloudflare Email** channel. 3. For each [environment](/concepts/environments), open **Manage configuration** on the channel and enter your **Account ID** and **API key** (your Cloudflare API token or key), plus your default **From** address and optional **From** name. Use addresses on domains you have verified for sending in Cloudflare. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Cloudflare Email [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Cloudflare Email channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Cloudflare Email** - **Account ID** (`string*`) - Your Cloudflare account ID. Used with Cloudflare's email sending API. - **API key** (`string*`) - Your Cloudflare API token or API key with permission to send email for this account. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). Must use a domain you have configured for sending in Cloudflare. - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. See [Settings and overrides](/integrations/email/settings) for more on email channel overrides. - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Recipient data requirements To send an email notification you'll need a valid `email` property set on your recipient. ## MailerSend How to send transactional email notifications to MailerSend with Knock. --- title: How to send email with MailerSend description: How to send transactional email notifications to MailerSend with Knock. section: Integrations > Email layout: integrations --- Knock integrates with MailerSend to send email notifications to your users. This page describes how to get started with MailerSend in Knock, including necessary provider configurations and additional data you can pass through to MailerSend. ## Features - Attachments support - Delivery tracking - Bounce Support - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new MailerSend channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your MailerSend [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your MailerSend channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for MailerSend** - **API key** (`string*`) - The API key for your MailerSend account. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Delivery tracking Delivery tracking for MailerSend can result in the following status updates to your message: - The message delivery is confirmed and Knock updates the message to `delivered` - The message was not delivered due to bad recipient(s) and Knock updates the message to `bounced` ## Mailgun How to send transactional email notifications to Mailgun with Knock. --- title: How to send email with Mailgun description: How to send transactional email notifications to Mailgun with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Mailgun to send email notifications to your users. This page describes how to get started with Mailgun in Knock, including necessary provider configurations and additional data you can pass through to Mailgun. ## Features - Attachments support - Delivery tracking - Bounce Support - Knock link and open tracking - Mailgun link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Mailgun channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Mailgun [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Mailgun channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Mailgun** - **API key** (`string*`) - The private API key for your Mailgun account. - **Domain** (`string*`) - The domain verified with Mailgun for sending emails. - **Mailgun region** (`enum*`) - The sending region (US or EU) for your Mailgun account. - **Open tracking** (`boolean`) - Whether to enable Mailgun email-open tracking. - **Link tracking** (`boolean`) - Whether to enable Mailgun link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes to Mailgun along with your emails: - `v:sender`: always set to `knock.app` - `v:knock_message_id`: the ID of the message this email is associated with - `v:knock_recipient_id`: the Knock ID of the recipient this email is being sent to - `o:tag`: the workflow key for the workflow being invoked You can learn about the role of these Mailgun attributes in the Mailgun API documentation. ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Delivery status webhooks When enabled, Mailgun will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your email messages in real-time. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A verified domain in Mailgun 2. A Mailgun channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to your Mailgun domain webhook settings ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your Mailgun channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 5. Copy the generated webhook URL - you'll need this in the next step In the Mailgun dashboard, configure webhooks to send delivery events to Knock: 1. Go to **Sending > Webhooks** in your Mailgun dashboard 2. Select the domain you're using with Knock 3. Add the webhook URL from Knock to these webhook types: - **Delivered messages** - Click "Add webhook URL", paste the Knock webhook URL, and save - **Permanent failure** - Click "Add webhook URL", paste the Knock webhook URL, and save 4. Mailgun will automatically begin sending events to Knock ### Supported delivery statuses When delivery status webhooks are enabled for Mailgun, Knock will update message statuses based on these Mailgun webhook events: | Mailgun Event Type | Knock Status | Description | | ------------------ | ------------ | ------------------------------------------------------------------- | | delivered | `delivered` | The email was successfully delivered to the recipient's mail server | | failed | `bounced` | The email failed permanently due to invalid recipient or domain | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check webhook configuration.** Verify both "Delivered messages" and "Permanent failure" webhooks are configured with the correct Knock webhook URL for your domain. 2. **Verify domain.** Ensure you're sending from a verified domain in Mailgun that matches the domain configured in your webhooks. 3. **Check webhook region.** Ensure your Mailgun region (US or EU) matches the region configured in your Knock channel settings. 4. **Test webhooks.** Send a test email and check the Webhooks page in Mailgun to verify events are being sent. 5. **Review webhook logs.** Check the webhook logs in Mailgun to see if requests are being sent successfully. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## Passing additional tags to Mailgun It's possible to pass additional tags to Mailgun by setting the "JSON overrides" attribute in the channel configuration or at the message template level. To pass one or more tags, you can set the `o:tag` attribute to an array of tag names: ```json { "o:tag": ["tag1", "{{ workflow.key }}"] } ``` ## Mailjet How to send transactional email notifications to Mailjet with Knock. --- title: How to send email with Mailjet description: How to send transactional email notifications to Mailjet with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Mailjet to send email notifications to your users. This page describes how to get started with Mailjet in Knock, including necessary provider configurations and additional data you can pass through to Mailjet. ## Features - Attachments support - Delivery tracking - Knock link and open tracking - Mailjet link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Mailjet channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Mailjet [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Mailjet channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Mailjet** - **API key** (`string*`) - The public API key for your Mailjet account. - **API secret key** (`string*`) - The secret API key for your Mailjet account. - **Open tracking** (`boolean`) - Whether to enable Mailjet email-open tracking. - **Link tracking** (`boolean`) - Whether to enable Mailjet link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `CustomID`: the Knock `message_id` associated with this email. You can learn about the role of this Mailjet attribute in the Mailjet API documentation. ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## JSON overrides behavior For this provider, we will merge the JSON overrides with the first object under the `"Messages"` attribute. ## Mailtrap How to send transactional email notifications to Mailtrap with Knock. --- title: How to send email with Mailtrap description: How to send transactional email notifications to Mailtrap with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Mailtrap to send email notifications to your users. This page describes how to get started with Mailtrap in Knock, including necessary provider configurations and additional data you can pass through to Mailtrap. ## Features - Attachments support - Delivery tracking - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Mailtrap channel in the dashboard under the under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Mailtrap [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Mailtrap channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Mailtrap** - **API key** (`string*`) - The API key for your Mailtrap account. - **API** (`enum*`) - Which Mailtrap API (Sending or Testing) to use. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `custom_variables.knock_message_id`: the ID of the message this email is associated with - `custom_variables.knock_recipient_id`: the Knock ID of the recipient this email is being sent to ## Recipient data requirements To send an email notification you'll need a valid `email` property set on your recipient. ## Mandrill How to send transactional email notifications to Mandrill with Knock. --- title: How to send email with Mandrill description: How to send transactional email notifications to Mandrill with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Mandrill to send email notifications to your users. This page describes how to get started with Mandrill in Knock, including necessary provider configurations and additional data you can pass through to Mandrill. ## Features - Attachments support - Delivery tracking - Knock link and open tracking - Mandrill link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Mandrill channel in the dashboard under the under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Mandrill [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Mandrill channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Mandrill** - **API key** (`string*`) - The API key for your Mandrill account. - **Open tracking** (`boolean`) - Whether to enable Mandrill email-open tracking. - **Link tracking** (`boolean`) - Whether to enable Mandrill link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `metadata.knock_message_id`: the ID of the message this email is associated with - `metadata.knock_recipient_id`: the Knock ID of the recipient this email is being sent to ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Postmark How to send transactional email notifications to Postmark with Knock. --- title: How to send email with Postmark description: How to send transactional email notifications to Postmark with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Postmark to send email notifications to your users. This page describes how to get started with Postmark in Knock, including necessary provider configurations and additional data you can pass through to Postmark. ## Features - Attachments support - Delivery tracking - Bounce Support - Knock link and open tracking - Postmark link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Postmark channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Postmark [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Postmark channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Postmark** - **API key** (`string*`) - The API key for your Postmark server. - **Open tracking** (`boolean`) - Whether to enable Postmark email-open tracking. - **Link tracking** (`boolean`) - Whether to enable Postmark link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `Metadata.sender`: always set to `knock.app` - `Metadata.knock_message_id`: the ID of the message this email is associated with - `Metadata.knock_recipient_id`: the Knock ID of the recipient this email is being sent to - `Tag`: the key of the workflow this message was generated from You can learn about the role of these Postmark attributes in the Postmark API documentation. ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Delivery status webhooks When enabled, Postmark will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your email messages in real-time. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A verified sender signature or domain in Postmark 2. A Postmark channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to your Postmark server webhook settings ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your Postmark channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 5. Copy the generated webhook URL - you'll need this in the next step In the Postmark dashboard, configure webhooks to send delivery events to Knock: 1. Go to your Postmark server and select the server you're using with Knock 2. Navigate to the **Webhooks** tab 3. Add the webhook URL from Knock to both webhook configurations: - **Delivery webhook** - Add the Knock webhook URL and click "Save delivery webhook" - **Bounce webhook** - Add the Knock webhook URL and click "Save bounce webhook" 4. Postmark will automatically begin sending events to Knock ### Supported delivery statuses When delivery status webhooks are enabled for Postmark, Knock will update message statuses based on these Postmark webhook events: | Postmark Event Type | Knock Status | Description | | ------------------- | ------------ | ------------------------------------------------------------------- | | Delivery | `delivered` | The email was successfully delivered to the recipient's mail server | | Bounce | `bounced` | The email bounced due to invalid recipient or domain | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check webhook configuration.** Verify both the Delivery and Bounce webhooks are configured with the correct Knock webhook URL in your Postmark server settings. 2. **Verify sender signature.** Ensure you're sending from a verified sender signature or domain in Postmark. 3. **Check Postmark activity.** Review the Activity page in Postmark to ensure emails are being sent successfully. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## Using overrides to customize notifications We provide full support for [overriding the payload](/email/settings#provider-json-overrides) of your email notifications. This enables you to customize the API request that Knock sends to Postmark on your behalf. ### Targeting a specific `MessageStream` Knock does not target a specific `MessageStream` when sending your transactional email notifications to Postmark. This means that Postmark defaults your notifications to the `"outbound"` transactional stream. For marketing use cases such as newsletters or product updates sent to large recipient lists, and particularly when leveraging Knock's [broadcasts](/concepts/broadcasts) feature, we recommend following Postmark's best practices for sending promotional messaging via a separate `MessageStream`. Configure a `Broadcast`-type message stream in your Postmark account. Read more about the `MessageStream` API here. Set a payload override in Knock with the ID of the `MessageStream` that you'd like to target. This can be done at either the channel configuration level or directly in the settings of your workflow's email channel step: ```json title="Payload override to target a broadcast MessageStream" { "MessageStream": "broadcasts" } ``` ## Resend How to send transactional email notifications to Resend with Knock. --- title: How to send email with Resend description: How to send transactional email notifications to Resend with Knock. section: Integrations > Email layout: integrations --- Knock integrates with Resend to send email notifications to your users. This page describes how to get started with Resend in Knock, including necessary provider configurations and additional data you can pass through to Resend. ## Features - Attachments support - Delivery tracking - Bounce support - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Resend channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Resend [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Resend channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for Resend** - **API key** (`string*`) - The API key for your Resend account, available from your Resend dashboard. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Recipient data requirements To send an email notification you'll need a valid `email` property set on your recipient. ## Debugging common errors ### `Identity not found` If you see an `Identity not found` error in your delivery logs this is because you need to verify your sending domain with Resend (the `From email address`). ## Delivery status webhooks When enabled, Resend will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your email messages in real-time. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A verified domain in Resend 2. A Resend channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to your Resend dashboard webhook settings ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your Resend channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 5. Copy the generated webhook URL - you'll need this in the next step In the Resend dashboard, configure webhooks to send delivery events to Knock: 1. Go to the Webhooks section in your Resend dashboard 2. Click "Add Endpoint" 3. Paste the webhook URL from Knock 4. Select the events you want to track: - **email.delivered** - Tracks successful delivery - **email.bounced** - Tracks bounce events 5. Click "Add Endpoint" to save 6. Resend will automatically verify the endpoint ### Supported delivery statuses When delivery status webhooks are enabled for Resend, Knock will update message statuses based on these Resend webhook events: | Resend Event Type | Knock Status | Description | | ----------------- | ------------ | ------------------------------------------------------------------- | | email.delivered | `delivered` | The email was successfully delivered to the recipient's mail server | | email.bounced | `bounced` | The email bounced due to invalid recipient or domain | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check webhook endpoint status.** Verify the endpoint shows as "Active" in the Resend webhooks dashboard. 2. **Verify event selection.** Ensure you've enabled both `email.delivered` and `email.bounced` events. 3. **Test with verified domains.** Ensure you're sending from a verified domain in Resend. 4. **Check webhook payload.** Review webhook delivery logs in Resend to ensure events are being sent. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## SendGrid How to send transactional email notifications to SendGrid with Knock. --- title: How to send email with SendGrid description: How to send transactional email notifications to SendGrid with Knock. section: Integrations > Email layout: integrations --- Knock integrates with SendGrid to send email notifications to your users. This page describes how to get started with SendGrid in Knock, including necessary provider configurations and additional data you can pass through to SendGrid. ## Features - Attachments support - Delivery tracking - Bounce support - Knock link and open tracking - SendGrid link and open tracking - Per environment configuration - Sandbox mode ## Getting started ### Connect SendGrid to Knock You can create a new SendGrid channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. Here are a few things to note as you configure your SendGrid provider: - **API key.** At a minimum, Knock needs an API key with full access to the **Mail Send** permission. For webhook-based delivery tracking (recommended), no additional permissions are needed. If you're using polling-based delivery tracking, you'll need to provide an API key with read access to the **Email Activity** permission (requires a SendGrid paid add-on). - **Delivery tracking.** We recommend using webhook-based delivery tracking, which provides real-time updates without requiring the Email Activity add-on. See the [delivery status webhooks](#delivery-status-webhooks) section below for setup instructions. - **Enable email open tracking.** If enabled, you can go to the email activity page in SendGrid to check the open status of a given email. - **Enable email link tracking.** If enabled, you can go to the email activity page in SendGrid to check the link open status of a given email. If you choose to enable open and link tracking, please keep user privacy top of mind and follow the privacy guidelines outlined in SendGrid's documentation. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your SendGrid [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your SendGrid channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for SendGrid** - **API key** (`string*`) - The API key for your SendGrid account. - **Check delivery status** (`boolean`) - When set will attempt to check for the delivery status of a message. Only available with SendGrid's Email Activity add-on. - **Open tracking** (`boolean`) - Whether to enable SendGrid email-open tracking. - **Link tracking** (`boolean`) - Whether to enable SendGrid link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `custom_args.sender`: always set to `knock.app` - `custom_args.knock_message_id`: the ID of the message this email is associated with - `custom_args.knock_recipient_id`: the Knock ID of the recipient this email is being sent to - `tags[0]`: the key of the workflow this message was generated from You can learn about the role of these SendGrid attributes in the SendGrid API documentation. ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Delivery status webhooks Delivery tracking for SendGrid can result in the following status updates to your message: - The message delivery is confirmed and Knock updates the message to `delivered` - The message was not delivered and Knock updates the message to `undelivered` - The message was not delivered due to a synchronous bounce and Knock updates the message to `bounced` When enabled, SendGrid will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your email messages in real-time. This provides more reliable tracking than polling-based methods and captures both synchronous and asynchronous bounce events. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A verified sender identity or domain in SendGrid 2. A SendGrid channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to your SendGrid webhook settings ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your SendGrid channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 5. Copy the generated webhook URL - you'll need this in the next step In the SendGrid dashboard, configure the Event Webhook to send delivery events to Knock: 1. Go to Settings > Mail Settings > Event Webhook in your SendGrid dashboard 2. Enable the Event Webhook if it's not already enabled 3. In the "HTTP POST URL" field, paste the webhook URL from Knock 4. Under "Select Actions", enable these event types: 5. Click "Save" to activate the webhook 6. SendGrid will begin sending events to Knock immediately ### Supported delivery statuses When delivery status webhooks are enabled for SendGrid, Knock will update message statuses based on these SendGrid webhook events: | SendGrid Event Type | Knock Status | Description | | ------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | delivered | `delivered` | The email was successfully delivered to the recipient's mail server. | | bounce | `bounced` | The email bounced due to invalid recipient, domain, or mailbox full. Only hard bounces with type `"bounce"` will be updated to the `bounced` status in Knock. Bounce events with type `"blocked"` are considered soft bounces in SendGrid and will not be updated. | | dropped | `bounced` | The email was not delivered due to invalid or previously-bounced recipient. | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check Event Webhook status.** Verify the Event Webhook is enabled in your SendGrid Mail Settings. 2. **Verify event selection.** Ensure both "Delivered" and "Bounce" events are selected in your Event Webhook configuration. 3. **Verify sender identity.** Ensure you're sending from a verified sender identity or domain in SendGrid. 4. **Test the webhook.** Use SendGrid's "Test Your Integration" button to send a test event and verify connectivity. 5. **Check event history.** Review the Event Webhook activity in SendGrid to ensure events are being sent. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## SMTP How to send transactional email notifications using SMTP with Knock. --- title: How to send email with SMTP description: How to send transactional email notifications using SMTP with Knock. section: Integrations > Email layout: integrations --- Knock supports sending email notifications to your users via the Simple Mail Transfer Protocol (SMTP). In this configuration, Knock acts as an SMTP client and sends email notifications to a specified SMTP server. This page describes how to get started sending email notifications using SMTP with Knock. ## Features - Attachments support - Knock link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new SMTP Relay channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. The following information is required to configure an SMTP Relay channel: - SMTP server host - SMTP server port - Username - Password Please note the following: - **Knock only supports authenticated SMTP connections.** - **The port must support TLS.** Email providers commonly use port 587 for TLS, but check your provider's documentation to confirm. - **We cannot currently track deliverability through SMTP Relay channels.** This means that all notifications sent via SMTP will show up as "Sent" in the Knock messages log, but not "Delivered." - **Some SMTP relay servers (including Gmail) have rate limits on authentication.** This means that they aren't well-suited to sending transactional email at scale. We recommend taking this into consideration when setting up an SMTP email channel in Knock. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your SMTP [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your SMTP channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for SMTP Relay** - **SMTP host** (`string*`) - The SMTP server host. - **Username** (`string*`) - The username to use when authenticating with the SMTP server. - **Password** (`string*`) - The password to use when authenticating with the SMTP server. - **Port** (`number*`) - The SMTP server port. This port must support TLS. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - For SMTP Relay channels, only header overrides are supported. See 'Setting SMTP headers' below for more details. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Setting SMTP headers For SMTP Relay channels, the JSON overrides field can be used to set SMTP headers. Overrides can be set in either the environment settings or per template. Knock supports overriding existing SMTP headers and adding custom headers. ```json title="Adding a custom SMTP header via JSON overrides" { "headers": { "X-SMTPAPI": "{\"category\": \"transactional\"}" } } ``` Headers must be nested under the `headers` key. Other keys will be ignored. If one of your header overrides is not already a string, Knock will automatically convert it to a string before sending. } /> ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. ## Frequently asked questions No, we currently do not support sending Loops emails via SMTP. Instead, you could use a [webhook channel](/integrations/webhook/overview) with the [Loops transactional email API](https://loops.so/docs/api-reference/send-transactional-email). ## SparkPost How to send transactional email notifications to SparkPost with Knock. --- title: How to send email with SparkPost description: How to send transactional email notifications to SparkPost with Knock. section: Integrations > Email layout: integrations --- Knock integrates with SparkPost to send email notifications to your users. This page describes how to get started with SparkPost in Knock, including necessary provider configurations and additional data you can pass through to SparkPost. ## Features - Attachments support - Delivery tracking - Knock link and open tracking - SparkPost link and open tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new SparkPost channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your SparkPost [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your SparkPost channel. - **Knock open tracking** (`boolean`) - Whether to enable Knock email-open tracking. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. - **CSS inlining** (`boolean`) - Whether Knock will inline CSS styles in your emails onto their associated HTML elements before sending, to improve email client compatibility. Defaults to true.
**Provider settings for SparkPost** - **API key** (`string*`) - The API key for your SparkPost account. - **SparkPost region** (`enum*`) - The region that your SparkPost account is in, either US or EU. - **Open tracking** (`boolean`) - Whether to enable SparkPost email-open tracking. - **Link tracking** (`boolean`) - Whether to enable SparkPost link-click tracking. - **From email address** (`string | liquid*`) - The default sender email address (can use Liquid tags). - **From name** (`string | liquid`) - The default sender name (can use Liquid tags).
When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. Learn more about email channel overrides [here](/integrations/email/settings). - **To** (`string | liquid`) - The To email address that email notifications will be sent to (can use Liquid tags). This value will override the designated recipient's email address. - **Cc** (`string | liquid`) - The CC email address that email notifications will be sent to (can use Liquid tags). - **Bcc** (`string | liquid`) - The BCC email address that email notifications will be sent to (can use Liquid tags). - **Reply-to** (`string | liquid`) - The reply-to email address that will be included on email notifications (can use Liquid tags). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped.
## Additional data sent Knock sends the following attributes along with your emails: - `metadata.knock_message_id`: the ID of the message this email is associated with - `metadata.knock_recipient_id`: the Knock ID of the recipient this email is being sent to ## Recipient data requirements In order to send an email notification you'll need a valid `email` property set on your recipient. # Chat ## Overview Learn how to use Knock to send notifications to chat apps such as Slack, Microsoft Teams, and Discord. --- title: Chat notifications with Knock description: Learn how to use Knock to send notifications to chat apps such as Slack, Microsoft Teams, and Discord. section: Integrations > Chat layout: integrations --- We make it effortless to send notifications to chat providers like Slack, Microsoft Teams, and Discord. Meet your users in the tools they use daily. ## Features - **Markdown template builder**: use our markdown editor to create templates for each platform and let us take care of sending the platform-specific markup. - **Use JSON to build interactive messages**: if you need to, it's easy to go to a fully customizable JSON template to power your chat notifications. - **Cross-provider, single template**: you can send the same chat templated message across all chat providers to reduce the amount of templates to maintain. - **Link tracking**: capture link-click events right within your Knock account. For more details, see the [Knock link tracking documentation](/send-notifications/tracking). ## Supported providers Knock currently has support for the following chat providers: - [Slack](/integrations/chat/slack) - [Discord](/integrations/chat/discord) - [Microsoft Teams](/integrations/chat/microsoft-teams) - [WhatsApp](/integrations/chat/whatsapp) If you want us to add a new provider to this list, please let us know through the feedback button at the top of this page. ## Replying to chat messages Learn how to reply to messages created by an earlier workflow step or in an existing thread. --- title: Replying to chat messages description: Learn how to reply to messages created by an earlier workflow step or in an existing thread. tags: ["steps", "channels", "chat", "slack", "discord", "msteams"] section: Integrations > Chat layout: integrations --- Chat channel steps can start a new conversation or reply to an existing provider thread. When a parent message was created by the current workflow run, you can send a reply to a previous chat step. To reply to a message that already exists outside the workflow run, you'll provide its provider identifiers instead. ## Reply to a previous chat step This is the recommended way to create a reply. Knock uses the earlier workflow step's provider message reference and destination, so you do not need to pass provider IDs or configure the destination again. In the workflow builder: 1. Add a chat channel step after the step that creates the parent message. 2. Set **Message behavior** to **Reply to a previous chat step**. 3. Select the **Parent step**. A step is available as a parent when it: - Is a previous chat channel step that is reachable on every execution path to the reply. Chat steps inside conditional [branches](/designing-workflows/branch-function) will not be eligible as a parent to subsequent steps outside of that branch. - Uses the same single Knock channel as the reply step. Channel groups are not supported. - Resolves to one provider message and destination for the current recipient workflow run. A chat step that creates multiple messages, such as when a recipient has multiple channel connections, is not eligible as a parent. The reply step inherits the exact provider message reference and recipient connection from its parent. For Microsoft Teams, Knock keeps the original thread root when the selected parent step is itself a reply. If the parent message is queued or scheduled, Knock waits for its provider message reference before attempting the reply. ## Reply to an existing thread Use this option when the parent message already exists outside of the current workflow run. In the workflow builder: 1. Set **Message behavior** to **Reply to an existing thread**. 2. Enter the provider identifiers described below, usually as dynamic Liquid references from your workflow trigger `data`. 3. For Slack, optionally enter the channel ID in **Send to**. See below for more detail on [configuring the destination](#configure-the-destination). The values depend on the provider: - For Slack, use the parent message's timestamp (`ts`). Knock sends it to Slack as `thread_ts`. - For Discord, use the parent Discord message ID. - For Microsoft Teams, enter the thread's conversation ID in **Conversation ID** and the root activity's ID in **Thread root message ID**. In an incoming Bot Framework activity, these values are `conversation.id` and `id`. Microsoft Teams replies require a bot-based connection. Incoming webhook connections cannot reply to messages. For a Slack message sent by Knock, use metadata.external_id from the message returned by the API or a webhook as the parent message ID. This is the Slack message's ts. } /> ### Configure the destination For Slack, **Send to** accepts a Slack channel ID, usually from workflow trigger data such as `{{ data.channel_id }}`. Leave it blank to use the recipient's Slack channel connection. In either case, the destination must be the channel that contains the parent message. For Discord, Knock uses the Discord channel data stored on the recipient. That connection must identify the channel that contains the parent message. For Microsoft Teams, Knock resolves a bot-based connection from the recipient and, when used, tenant channel data. Together, the channel data must include a Microsoft Entra tenant ID and either a Microsoft Teams channel ID or user ID. See [Microsoft Teams channel data requirements](/integrations/chat/microsoft-teams/overview#how-to-set-channel-data-for-a-microsoft-teams-integration-in-knock). The configured Slack, Discord, or Microsoft Teams channel supplies authentication. Do not put a bot token in workflow data or in a destination field. ### Slack trigger data example Send the Slack message timestamp and channel ID in the workflow trigger data: ```javascript title="Triggering a threaded Slack response" await knock.workflows.trigger("reply-to-slack-message", { recipients: ["user_123"], data: { parent_message_id: "1784920923.818589", channel_id: "C0123456789", }, }); ``` Then configure the Slack step with: | Setting | Value | | ----------------- | ------------------------------ | | Message behavior | Reply to an existing thread | | Parent message ID | `{{ data.parent_message_id }}` | | Send to | `{{ data.channel_id }}` | At execution time, Knock renders the destination as a recipient connection equivalent to: ```json title="A recipient Slack connection" { "channel_id": "C0123456789" } ``` ### Microsoft Teams trigger data example Send the conversation ID and root activity ID in the workflow trigger data: ```javascript title="Triggering a Microsoft Teams thread reply" await knock.workflows.trigger("reply-to-microsoft-teams-message", { recipients: ["user_123"], data: { conversation_id: "19:conversation-id@thread.tacv2", thread_root_message_id: "1742995142123", }, }); ``` Then configure the Microsoft Teams step with: | Setting | Value | | ---------------------- | ----------------------------------- | | Message behavior | Reply to an existing thread | | Conversation ID | `{{ data.conversation_id }}` | | Thread root message ID | `{{ data.thread_root_message_id }}` | The conversation and thread root identifiers select the existing Microsoft Teams thread. The recipient and tenant channel data provide the bot connection that Knock uses to send the reply. ## Troubleshooting Knock uses the recipient's stored Slack connection, which might point to another channel. Enter the channel ID that contains the parent message. The reply does not attach to the expected thread, or Slack rejects the request. Enter the channel ID that contains the parent message. Microsoft Teams rejects the reply or cannot attach it to the expected thread. Use the conversation.id and root activity{" "} id from the Microsoft Teams Bot Framework activity. Knock cannot reply because incoming webhooks do not provide the required bot-based connection. Configure bot-based channel data for the provider. Knock cannot render a required provider identifier or destination, so it does not send the reply. Verify the workflow trigger data and Liquid path. The step is not guaranteed to run first, uses another Knock channel, or is inside an unsupported group. Adjust the workflow graph or reply to an existing thread with its provider identifiers. Knock cannot identify one parent message and destination when the step produces no message or multiple messages. Make the parent resolve to exactly one chat message for the recipient run. ## Slack ## Overview Learn how to use Knock to send Slack notifications to your users. --- title: Slack notifications with Knock description: Learn how to use Knock to send Slack notifications to your users. section: Integrations > Slack layout: integrations --- This page covers how to use Knock to send notifications to Slack. You can use Knock's Slack extension to message your own team's workspace, or connect your own Slack app to build a customer-facing integration. ## What can I do with Knock's Slack integration? Knock's Slack integration enables you to: - Use [internal Slack messaging](/integrations/chat/slack/sending-an-internal-message) to send messages to your team's Slack workspace. - [Send messages to channels in your customers' workspaces](/integrations/chat/slack/sending-a-message-to-channels) using your own Slack app. - [Send direct messages to users in your customers' workspaces](/integrations/chat/slack/sending-a-direct-message) using your own Slack app. You can create rich message templates using [markdown](#markdown-templates) or [Slack's Block Kit framework](#block-based-templates). This integration does not support Slack modals, slash commands, shortcuts, or interactive dialogs. ## Connect your own Slack app The steps below apply when you are building a customer-facing Slack integration with your own Slack app. To send workflow messages to your own team's workspace without creating a Slack app, [create an internal Slack channel](/integrations/chat/slack/sending-an-internal-message) instead. Knock supports multiple ways to connect your own Slack app depending on your technical requirements: - Using Knock's managed approach with our [SlackKit components](/in-app-ui/react/slack-kit) - Using Knock's [Slack-related React hooks](https://docs.knock.app/in-app-ui/react/slack-kit#using-slackkit-headless) with your own components - Building the UI and OAuth flow entirely yourself If you haven’t built a Slack app yet, you can get started in Slack’s app documentation. Once you create your Slack app, you’ll be routed to its app management page within the Slack dashboard. It looks like this. Slack app management page If you are new to Slack apps, there are a few key concepts to understand: - A scope is a permission granted to your Slack app when it joins a Slack workspace. You configure which scopes your app asks for in the **OAuth & Permissions** sidebar of your app management page. - Your Slack app is installed to a customer's workspace through the Slack OAuth flow. In this flow, your app requests scopes and the user installing the app confirms which scopes to grant. You’ll need to surface this OAuth flow to your users wherever you want them to install your Slack app. Knock's [`SlackAuthButton` component](/in-app-ui/react/slack-kit#slackauthbutton--container) can help you with this. - A Slack app can have bot token scopes and user token scopes. For almost all use cases, you’ll be using bot token scopes (this Slack doc explains why). When you add bot token scopes to an app, you will **also** need to make sure your app has its display information configured. You can do this under the **App Home** sidebar on the app management page. In this section we'll complete the steps for setting up your Knock account and Slack app. First, you'll need to create the bot that you'll use to post notifications.
If you already have a Slackbot you want to use, you'll just need to make sure it has its redirect URL set and that it's publicly distributed, which is described below. } /> The following steps will be completed in the **OAuth & Permissions** sidebar of your Slack app's management page: When using an `access_token` stored in Knock to power your Slack integration, that token will need to contain the correct scopes to use the Slack API. Although If you're using SlackKit, the components can help you manage your scopes, but you have to add one in your Slack application's settings to start with so that it exposes the form for you to add a redirect URL. Here is the list of scopes we recommend adding to your app and the reasons they're required: - `chat:write`: This scope allows your bot to send messages to channels that the bot has been explicitly invited to, including private channels. It doesn't grant the ability to send messages to public channels without an invite. - `chat:write.public`: This scope extends the bot's capabilities to send messages to public channels without needing a specific invitation to those channels. It's an addition to the chat:write scope, offering broader access for the bot to interact within the workspace. - `channels:read`: This scope allows your bot to list all channels in a workspace and view details about specific channels (like name, topic, purpose, members), but does not grant access to private channels. - `groups:read`: This scope allows your app to list all private channels that the bot or user is a member of and to view details about those channels. - `users:read`: This scope allows your app to view all people in a workspace and is required if you want to query a user's Slack ID by email. **This scope is required to use [Knock's email-based user ID resolution](/integrations/chat/slack/sending-a-direct-message#enabling-email-based-user-id-resolution).** - `users:read.email`: This scope allows your app to view the email addresses of people in a workspace and is required if you want to query a user's Slack ID by email. **This scope is required to use [Knock's email-based user ID resolution](/integrations/chat/slack/sending-a-direct-message#enabling-email-based-user-id-resolution).** Under **OAuth & Permissions** in the **Features** sidebar, you need to add a redirect URL so that Slack knows where to redirect users after they complete the OAuth handshake. If you plan on using SlackKit, copy and paste this redirect URL: If you're building your own OAuth flow, you'll need to use a URL in your frontend application. You can reference this documentation on building a Slack OAuth flow from scratch. Under **Manage Distribution**, click the "Distribute app" button which will show you a list of items to complete to activate public distribution. If you've built the app from scratch and completed the previous steps, you should see all of these complete except for "Remove hard coded information." Check the box and then click "Activate Public Distribution."
Now that your Slack app is set up, you'll want to reference three attributes in the **Basic Information** section: 1. App ID 2. Client ID 3. Client secret You need to add this data to a new or existing Slack integration in the Knock dashboard to link your app to Knock. From the dashboard, you can [create a Slack integration](/concepts/channels#managing-channels) by navigating to **Channels and sources** in your account settings. A Slack message
Once the channel exists, you can click "Manage configuration" to access the "Provider settings" section. This is where you'll paste the three strings referenced above. A Slack message
Click "Update settings" to save.
In order to test this flow, you'll want to set up a workflow with a Slack channel step. Later on we'll discuss how you can [design your notification templates for Slack](/integrations/chat/slack/overview#designing-notification-templates-for-slack), but for now you can just add the channel step to a workflow. To actually send a notification to Slack, you'll need to add channel data to a user or object in Knock. We'll cover that in the next section. workflow with a Slack step
## How to set channel data for a Slack integration in Knock In Knock, the [`ChannelData`](/managing-recipients/setting-channel-data) concept provides you a way of storing recipient-specific connection data for a given integration. If you reference the [channel data requirements for Slack](/managing-recipients/setting-channel-data#chat-app-channels), you'll see that there are two different schemas for a `SlackConnection` stored on a [`User`](/concepts/users) or an [`Object`](/concepts/objects) in Knock. Here's an example of setting channel data on an `Object` in Knock.
In the example above, the KNOCK_SLACK_CHANNEL_ID variable is the id of the Knock channel you've created to represent your Slack app within the Knock dashboard. You can find it by going to{" "} Integrations {">"}{" "} Channels in the Knock dashboard and then copying the ID of your Slack app channel. } /> ### How Slack delivery works Every Slack notification needs two things, and Knock lets them live in different places: - **Auth** — permission to post: either a bot **access token** (`xoxb-…`) from Slack's OAuth flow, or a self-contained **incoming webhook URL**. A bot token authorizes exactly one Slack workspace. - **Destination** — where the message goes: a `channel_id` (a channel) or a `user_id` (a direct message). The **destination** is stored on the recipient (a [`User`](/concepts/users) or [`Object`](/concepts/objects)). The **auth** can be stored on the recipient too — or, in a multi-workspace product, once on the [tenant](#tenant-channel-data-requirements), so every recipient in that tenant shares a single connection. The two sections below cover each location. ### Recipient channel data requirements Here's an overview of the data requirements for [setting recipient channel data](/send-notifications/setting-channel-data) for either an incoming webhook or an access token Slack connection. Both will need to live under the `connections` key. | Property | Type | Description | | ----------- | ------------------- | --------------------------------- | | connections | `SlackConnection[]` | One or more connections to Slack. | A `SlackConnection` can have one of two schemas, depending on whether you're using standard Slack OAuth scopes or an incoming webhook. We cover Slack app scopes in detail in our [Slack scopes documentation](/integrations/chat/slack-diy/slack-apps-and-scopes). If you're using standard Slack OAuth with access token scopes, your `SlackConnection` schema looks like this. You'll use either a `channel_id` or `user_id` depending on whether you're storing connection data to message a channel or user in Slack: | Property | Type | Description | | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | access_token | `string` | A bot access token. Not required when the access token is stored on a tenant. | | channel_id | `string` | A Slack channel ID. | | user_id | `string` | A Slack user ID. |
```json title="Slack channel_data with an access token" { "connections": [ { "access_token": "ACCESS_TOKEN", "channel_id": "CHANNEL_ID", "user_id": "USER_ID" } ] } ```
If you're using a Slack app with the `incoming-webhook` scope your `SlackConnection` schema is quite simple: | Property | Type | Description | | -------------------- | -------- | ------------------------------- | | incoming_webhook.url | `string` | The Slack incoming webhook URL. | ```json title="Slack channel_data with an incoming webhook URL" { "connections": [ { "incoming_webhook": { "url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" } } ] } ```
### Tenant channel data requirements When you [map a Slack workspace to a tenant](/integrations/chat/slack/sending-a-message-to-channels#key-concepts) in Knock (as our [SlackKit](/in-app-ui/react/slack-kit) components do), you store that workspace's access token as `channel_data` on the tenant. At send time, when you trigger a workflow with that `tenant`, Knock takes the [destination](#how-slack-delivery-works) from the recipient's `channel_data` (the `channel_id` or `user_id`) and uses the tenant's access token for auth: the tenant provides the auth, the recipient provides the destination. If the recipient also carries an `access_token`, the tenant's takes precedence — so the `access_token` on the recipient's `SlackConnection[]` above is not required when a tenant holds one. **Do you need a tenant?** Store the token on a tenant when one Slack workspace connection should serve every recipient or object in that tenant. This keeps the workspace access token in one place while each recipient or object stores only its Slack destination (`channel_id` or `user_id`), which is useful as you add more destinations in the same workspace later. You don't need a tenant if you post to a single internal workspace (use an incoming webhook — there's no token to share) or only ever use one workspace's token and prefer to store it directly on each recipient or object. Knock never auto-creates a tenant for you (though SlackKit can, on the fly), so use one when shared workspace auth should live at the tenant level. Here's an overview of the data requirements for setting channel data when storing an access token on a tenant. | Property | Type | Description | | ------------------ | -------- | ------------------------------------------------------------ | | token | `object` | An object containing the `access_token` obtained from Slack. | | token.access_token | `string` | The `access_token` obtained from Slack. |
```json title="Slack channel_data on a tenant" { "token": { "access_token": "ACCESS_TOKEN" } } ``` ### Choosing where to store channel data: users vs. objects Depending on the Slack integration you build into your product, you’ll store the connection data you receive from Slack as `channel_data` on either a `User` or an `Object` in Knock. If your integration involves a user opting in to receive DMs from your Slack bot, you’ll be storing the channel data [on that user](/api-reference/users/set_channel_data) in Knock. When you want to notify this user, you'll include them as a recipient in a Knock workflow trigger. For this integration, you'll store a user's Slack `user_id` in the `SlackConnection` object. You can [enable email-based user ID resolution](/integrations/chat/slack/sending-a-direct-message#enabling-email-based-user-id-resolution) if you'd like Knock to automatically resolve these IDs on your behalf. Alternatively, you can manually find the correct `user_id` by querying Slack's API: - by a given user's email address - for a list of all of a workspace's users You'll need to be sure that you request the appropriate [`scopes`](/integrations/chat/slack/overview#set-up-slack-app) for these methods during the auth process. If your integration involves a customer connecting a _non-user resource_ in their product (such as a project or a page) to a Slack channel, you’ll want to store that channel data [on an object](/api-reference/objects/set_channel_data) in Knock, as it’s not specific to any single user. You can find the correct `channel_id` and display a list of channels for your user to select from by querying the Slack API: - for a list of all of a workspace's channels The [`SlackChannelCombobox`](/in-app-ui/react/slack-kit#slackchannelcombobox) component of Knock's SlackKit can help you with this. You can learn more about the Knock object model and see an example how to use it to power Slack notifications in our [Objects documentation](/concepts/objects).
## Designing notification templates for Slack When you add a new Slack channel step to a workflow in Knock, you'll need to configure a template for that step so Knock knows how to format the message to Slack. ### Markdown templates Editing a markdown template for Slack is just like editing any other markdown-based template in Knock. You can use Liquid to inject variables and add control tags (e.g. if-then, for-loop) into your template. Slack uses a markdown-variant syntax called{" "} mrkdwn , but Knock handles this for you automatically, so you can write your templates in good old {" "} markdown . } /> Here's an example Slack template written in Knock using markdown. ```markdown title="A markdown-based Slack template with for-loop iteration" Hi **{{ recipient.name | split:" " | first }}**, There are {{ total_activities }} comments left on {{ page_name }}. {% for activity in activities %} - From **{{activity.actor.name}}**: "{{activity.comment.body}}"" {% endfor %} [**View page**]({{vars.base_url}}/{{account_id}}/pages/{{ page_id }}) ``` In the example above we're using Liquid's for-loop tag to iterate over the activities array produced by a Knock batch function. You can learn more about Knock batch functions and the state they produce in our [batch function documentation](/send-notifications/designing-workflows/batch-function). ### Block-based templates For more advanced layouts in your Slack messages, including images and buttons, you'll need to use Slack's block kit UI framework to build your notification templates. The block kit framework is a set of different JSON objects you can use together and arrange to create Slack app and notification layouts. #### Designing block kit templates To start you'll want to design your block-based Slack message template. The best way to do this today is to use Slack's block kit builder. It gives you a drag-and-drop interface for building out your Slack templates, and outputs the JSON you'll need to bring into your Knock template. Once you've designed your Slack template, copy the JSON from the block kit builder and bring it into your Knock notification template. You'll use the "Switch to JSON editor" button at the bottom right of the Knock template editor page to switch to our JSON editor, and then paste in the JSON you copied from the block kit builder. In the future you'll be able to use a visual, drag-and-drop editor within Knock to build these block-based Slack templates without having to leave the Knock product.

If you're interested in this functionality, please shoot us a note at{" "} support@knock.app or use the feedback button at the bottom of this page. } /> #### Knock's JSON template editor You can use liquid in the Knock JSON template editor just as you would in the markdown editor. This is helpful for both injecting variables into the text of your Slack UI blocks, as well as for using liquid control tags to control when certain blocks should be displayed and for iterating through an array and mapping its items into a list of Slack blocks. Here's an example of a block kit UI template with liquid syntax added to iterate through a list of items. ```json title="A block kit UI template in Knock with for-loop iteration" { "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "Users marked for onboarding", "emoji": true } }, { "type": "section", "text": { "type": "mrkdwn", "text": "Hi John,\n\n The following users have been assigned to you for onboarding today:" } }, {% for activity in activities %} { "type": "section", "text": { "type": "mrkdwn", "text": "*{{activity.assigned_user.name}}* \n {{activity.assigned_user.email}} \n _{{activity.assigned_user.title}}_" }, "accessory": { "type": "image", "image_url": "{{activity.assigned_user.avatar_url}}", "alt_text": "avatar image" } }, { "type": "divider" }, {% endfor %} { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "View in dashboard", "emoji": true }, "value": "{{dashboard_url}}" } ] } ] } ``` In the template above, we're using the `activities` array produced by a [batch function](/send-notifications/designing-workflows/batch-function) to iterate over a number of items that we want to display in our Slack message. For each one of those items, we're producing a `section` that includes both `text` and `image` blocks which reference variables from the `activity` from our `activities` array. Here's an example of a Slack message produced with this template. **Note:** this template was produced by three separate workflow trigger calls to the Knock API, all of which were batched into a single message automatically using our batch function. An example Slack message built with block kit UI ## Messaging your workspace Use internal Slack messaging to send Knock workflow messages to channels and people in your team's Slack workspace. --- title: Sending messages to your team's Slack workspace description: Use internal Slack messaging to send Knock workflow messages to channels and people in your team's Slack workspace. tags: ["slack", "chat"] section: Integrations > Slack layout: integrations --- Internal Slack messaging gives your team a managed way to send workflow messages to your own Slack workspace. Connect the Knock Slack extension once, then choose a Slack channel or person as the destination for each workflow step. You don't need to create a Slack app, manage bot tokens, or store Slack `channel_data` on your workflow recipients. The internal Slack channel is a managed Knock channel backed by the Knock Slack extension. A Slack destination{" "} is the channel or person in your workspace that you select on a workflow step. One internal Slack channel can be used by many workflow steps, each with its own destination. } /> ## Before you begin To create an internal Slack channel, you need a Knock [role](/manage-your-account/roles-and-permissions) with permission to manage both channels and extensions. After it is created, team members with permission to edit workflows can use it in workflow steps. The Knock Slack app can send to: - Public channels in your workspace. - Private channels where the Knock Slack app is a member. Invite `@Knock` to a private channel before selecting it. - Active people in your workspace via direct message. ## Create an internal Slack channel In the Knock dashboard, open **Settings > Channels and sources** and click **Create channel**. Select **Chat**, then choose **Slack** with the `Internal` badge. Click **Next**. If your project is already connected to Slack through the [Knock Slack extension](/integrations/extensions/slack) with the required permissions, Knock uses that connection to create the internal Slack channel. If Slack is not connected, or if the existing connection needs additional permissions, Knock opens the Slack authorization flow. Choose your workspace and approve the requested permissions. After you return to Knock, the internal Slack channel is created automatically. The channel configuration shows the Slack workspace managed by the Knock Slack extension. Its credentials apply across your Knock environments and cannot be edited from the channel configuration. You can create one internal Slack channel per Knock account. The same channel can be reused across any number of workflows. ## Add internal Slack messaging to a workflow Open a workflow and add a chat app channel step. Select your internal Slack channel. Use the **Send to** field to search the connected workspace. Choose a destination from either the **Channels** or **People** tab. The destination is stored on this workflow step. Other steps using the same internal Slack channel can be configured to send elsewhere. Write the Slack message using Knock's markdown or Block Kit editor, then commit the workflow. You do not need to configure a recipient connection in the template settings; Knock supplies the Slack destination and credentials for you. ## Trigger the workflow Trigger a workflow that uses internal Slack messaging the same way you trigger any other Knock workflow. A recipient is still required for the workflow run, but that recipient does not need Slack channel data. ```javascript title="Trigger a workflow that uses internal Slack messaging" import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.workflows.trigger("support-alert", { recipients: ["user-1"], data: { summary: "A customer needs help with a failed import.", }, }); ``` The workflow recipient still provides the `recipient` variables used in Liquid templates, step conditions, and the rest of the workflow. The destination selected in **Send to** only changes where that internal Slack step is delivered. To continue an existing Slack thread instead of starting a new message, see [replying to chat messages](/integrations/chat/replying-to-chat-messages). Replies can inherit the destination from a previous chat step or target an existing thread using its message timestamp and matching channel ID. ## How recipient preferences work Messages sent through internal Slack messaging bypass the workflow recipient's notification [preferences](/preferences/overview) because the message is delivered to a configured destination, not to a Slack connection owned by that recipient. Channel and step [conditions](/concepts/conditions) still evaluate normally against the workflow `recipient`, if set. Each internal Slack step sends exactly one message to its selected destination. In workflow runs and message details, **Delivered to** shows the Slack channel or person that received the message, while the recipient associated with the message remains the workflow `recipient`. ## Manage or reconnect Slack The Knock Slack extension owns the workspace connection and credentials used by the internal Slack channel. Manage the connection from **Settings > Extensions**, not from the channel's provider settings. If the extension is disconnected or needs to be authorized again, Knock preserves the internal Slack channel and its workflow references but skips affected Slack steps until the workspace is reconnected. The workflow run log identifies the disconnected Slack connection as the reason. Open **Settings > Extensions** and reconnect Slack to resume delivery. ## When to use a different Slack setup Use internal Slack messaging only when you want Knock to message your own team's workspace through the Knock Slack app. If you are building a customer-facing Slack integration, use [your own Slack app and SlackKit](/integrations/chat/slack/sending-a-message-to-channels). ## Sending a direct message How to configure and send direct messages to Slack users. --- title: Sending a direct message to a user in Slack description: "How to configure and send direct messages to Slack users." tags: ["slack", "chat"] section: Integrations > Slack layout: integrations --- This page covers how to create a self-serve Slack integration for a multi-tenant application using Knock. It assumes that you have already created a Slack app and created a Slack channel in Knock as outlined in the [Slack integration overview](/integrations/chat/slack/overview). In this implementation, your application's users will connect their Slack workspace to Knock and be able to send messages to individual users via DM. To make this easier to implement, we'll use Knock's [SlackKit components](/in-app-ui/react/slack-kit) to facilitate the OAuth flow. ## Key concepts SlackKit connects multiple concepts in Knock to make it easier for your application's users to create a Slack integration. `Tenants` are a concept you'll see throughout the following docs that are foundational to how SlackKit works, but might not be used in every implementation of Knock. ### About tenants [Tenants](/concepts/tenants) in Knock are meant to represent groups of users who typically share the same resources. You might call these "accounts," "organizations," "workspaces," or something similar. In a standard SlackKit implementation, you'll store a Slack workspace's `access_token` on a corresponding tenant in Knock. If you already use Knock's tenant concept to power other 'account-based' features, you likely create tenants in Knock when an account or organization is created in your application. If you don't already use tenants in Knock, SlackKit can create tenants for you on the fly if they don't already exist. Our best-practice recommendation is that tenants in Knock should map one-to-one to whatever abstraction you use to model accounts, organizations, or workspaces. You can think of tenants as the top-level container within your data model that you use to power multi-tenancy in your application. } /> ### Merging channel data In this implementation, we'll actually store [the required channel data](/integrations/chat/slack/overview#channel-data-requirements) for a `SlackConnection` across two different entities in Knock: a `Tenant` and an `User`. This is because we want to store the `access_token` for the Slack workspace on the `Tenant` and the `user_id` for the Slack user on the `User`. When you trigger a workflow using this recipient and tenant, Knock will merge the channel data from the `Tenant` and the `User` to send the message to the correct Slack DM channel. By storing the `access_token` on the `Tenant`, your customers only need to complete the OAuth flow once to connect their Slack workspace to Knock. From there, you can create UI that allows users to link their Slack user ID to their Knock user ID or automate this process during user registration. ## Implementing SlackKit To facilitate the OAuth flow and channel selection process, we'll use Knock's [SlackKit components](/in-app-ui/react/slack-kit). SlackKit is a set of React components that make it easier to build Slack integrations in Knock. You can use SlackKit to build a self-serve Slack integration that allows your users to connect their Slack workspace to Knock. ### Signing a user token The only access you'll need to manage when using SlackKit are grants for your users to interact with their [Tenant](/concepts/tenants) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. You'll need to generate a token for your user that includes access to the tenant storing the Slack access token as well as any recipient objects storing Slack channel data described in this reference on [SlackKit resource access grants](/in-app-ui/react/slack-kit#resources-access-grants). Using the below example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; const token = await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "org_3sh72ds78" }, [ Grants.SlackChannelsRead, ]), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockSlackProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Adding provider components In order to give your components the data they need, they must be wrapped in the `KnockSlackProvider`. We recommend putting this high in your component tree so that any Slack components that you use will be rendered within it. The Slack provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockSlackProvider` gives your components access to the status of the connection to your Slack app, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Implementing the OAuth flow with `SlackAuthButton` Your users will give your Slack app access to their own Slack workspaces via the `SlackAuthButton`. This button can be used on its own, or nested in the `SlackAuthContainer` for a bigger visual footprint.
The SlackAuthButton component with SlackAuthContainer
The SlackAuthButton component with SlackAuthContainer
Since we'll also be using this `access_token` to resolve a user's email address to their Slack ID, we'll need to add some additional scopes to this OAuth request. We can do that by adding the `users:read` and `users:read.email` scopes to the `SlackAuthButton` component with the `additionalScopes` parameters. Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with SlackAuthButton" // Without container // With container } /> ``` The `SlackAuthButton` maps a tenant in your product to a customer's Slack workspace. This means in most cases you'll just need a single instance of the `SlackAuthButton`. Remember to consider which roles in your application can access the `SlackAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button and container in the settings area of your product. ### Resolving a user's Slack ID To send a direct message to a user in Slack, you'll need to resolve their email address to their Slack ID. You can do this one of two ways: automatically using Knock's email-based user ID resolution, or manually by making a request to the Slack API with the `access_token` stored in Knock. #### Enabling email-based user ID resolution This feature requires you to store an access_token for the Slack workspace as{" "} channel data on a tenant {" "} in Knock. Our SlackKit components will do this for you automatically. } /> Knock's email-based user ID resolution handles the complexity of resolving a user's Slack ID for you. Here's how it works. When the email-based user ID resolution setting is enabled and a workflow is triggered for a user recipient who does not have stored channel data, Knock will automatically make a request to the `users.lookupByEmail` endpoint of the Slack API to resolve the user's Slack ID before sending the message. When this Slack API request succeeds, Knock will automatically add the Slack user ID to the user's channel data so that the API request does not need to be repeated in the future. Note that this API request will only be made when the user has a [valid email attribute](/concepts/users#optional-attributes). To use this feature, you must request the `users:read` and `users:read.email` scopes during the OAuth flow. See [How to connect Slack to Knock](/integrations/chat/slack/overview#how-to-connect-slack-to-knock) for more information about Slack scopes. If you're using SlackKit's `SlackAuthButton` component to manage the OAuth flow, you can use the `additionalScopes` prop to request these scopes. ```javascript title="Requesting the users:read and users:read.email scopes with SlackAuthButton" // Without container ``` If you're using the `useSlackAuth` [hook](/in-app-ui/react/sdk/hooks/use-slack-auth) directly instead of `SlackAuthButton`, pass the scopes via the `additionalScopes` option: ```javascript title="Requesting the users:read and users:read.email scopes with the useSlackAuth hook" const { buildSlackAuthUrl } = useSlackAuth(slackClientId, redirectUrl, { additionalScopes: ["users:read", "users:read.email"], }); ``` If you enable the email-based user ID resolution setting but you haven't already been requesting the users:read and{" "} users:read.email scopes, any of your application's tenants who have already connected their Slack workspace to Knock will need to complete the OAuth flow again to re-authorize your app.{" "} Otherwise, the Slack API request will fail. } /> Once you're confident that all of your application's tenants have granted authorization with the required scopes, you can enable this setting on either a Slack channel or a step in a workflow. In the dashboard, navigate to the **Channels and sources** page under your account settings and select your Slack integration. Click the “Manage configuration” button for one of your environments to open the channel configuration dialog. Locate the toggle labeled “Enable email-based user ID resolution” and toggle it on. Channel configuration dialog with email-based user ID resolution setting Click the “Update settings” button to save your changes. In the dashboard, navigate to the **Workflows** page and select your workflow. Click on an existing Slack step in the workflow. Under “Channel settings” in the right panel, locate the toggle labeled “Enable email-based user ID resolution” and toggle it on. Chat step configuration with email-based user ID resolution setting #### Manually resolving a user's Slack ID If you wish to manually resolve a user's Slack ID from their email address, you can do so from your application's backend by making a request to the Slack API with the `access_token` stored in Knock. Here's an example of how you might create a `fetchUserId` function to do this using the Knock Node SDK: ```javascript export async function fetchUserId(email: string): Promise { // First, get the access token from Knock for the user's tenant const channelData = await knock.objects.getChannelData( "$tenants", 'knocklabs', process.env.NEXT_PUBLIC_KNOCK_SLACK_CHANNEL_ID as string, ); // Next, use that access to make a request to the Slack API const response = await fetch( `https://slack.com/api/users.lookupByEmail?email=${email}`, { headers: { Authorization: `Bearer ${channelData.data.token.access_token}`, }, }, ); const data = await response.json(); if (data.ok) { // If the request is successful, save the user's Slack ID as channel data on the user await knock.users.setChannelData( userId, process.env.NEXT_PUBLIC_KNOCK_SLACK_CHANNEL_ID as string, { data: { connections: [{ user_id: slackUserId }] }, }, ); } return data; } ``` We'll break this function down step-by-step: Since your users have already connected their Slack workspace to Knock, you can use the `knockClient.objects.getChannelData` method to get the `access_token` for the user's tenant. Tenants in Knock are stored in a system-reserved object collection called `$tenants`. ```javascript await knock.objects.getChannelData( "$tenants", 'knocklabs', process.env.NEXT_PUBLIC_KNOCK_SLACK_CHANNEL_ID as string, ); ``` Once you have the tenant's `access_token`, you can use it to make a request to the Slack API to resolve the user's email address to their Slack ID using the `users.lookupByEmail` endpoint. ```javascript const response = await fetch( `https://slack.com/api/users.lookupByEmail?email=${email}`, { headers: { Authorization: `Bearer ${channelData.data.token.access_token}`, }, }, ); ``` Assuming the request to the Slack API is successful, you can save the user's Slack ID as channel data on the user in Knock. This will allow you to send messages to the user's Slack DM channel. ```javascript await knock.users.setChannelData( userId, process.env.NEXT_PUBLIC_KNOCK_SLACK_CHANNEL_ID as string, { data: { connections: [{ user_id: slackUserId }] }, }, ); ``` ## Triggering a workflow Once you have saved the user's Slack ID as channel data, you can trigger a workflow to send a message to that user's DM channel. Here's an example of how to trigger a workflow using the Knock Node SDK: ```javascript const workflow_run_id = await knockClient.workflows.trigger("new-issue", { recipients: ["user_1n38knd"], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ## Sending a message to channels How to configure and send notifications to Slack channels. --- title: Sending a message to public and private channels description: "How to configure and send notifications to Slack channels." tags: ["slack", "chat"] section: Integrations > Slack layout: integrations --- In this documentation we'll cover how to create a self-serve Slack integration for a multi-tenant application using Knock. It assumes that you have already created a Slack app and created a Slack channel in Knock as outlined in the [Slack integration](/integrations/chat/slack/overview) documentation. In this implementation, your application's users will connect their Slack workspace to Knock and be able to send messages to public and private channels. To make this easier to implement, we'll use Knock's [SlackKit components](/in-app-ui/react/slack-kit) to facilitate the OAuth flow and channel selection process. ## Key concepts SlackKit connects multiple concepts in Knock to make it easier for your users to create a Slack integration. There are two key concepts you'll see throughout the following docs that are foundational to how SlackKit works, but might not be used in every implementation of Knock: tenants and objects. ### About tenants [Tenants](/concepts/tenants) in Knock are meant to represent groups of users who typically share the same resources. You might call these "accounts," "organizations," "workspaces," or something similar. In a standard SlackKit implementation, you'll store a Slack workspace's `access_token` on a corresponding tenant in Knock. See [how the tenant access token is used at send time](/integrations/chat/slack/overview#tenant-channel-data-requirements) for the full model. If you already use Knock's tenant concept to power other 'account-based' features, you likely create tenants in Knock when an account or organization is created in your application. If you don't already use tenants in Knock, SlackKit can create tenants for you on the fly if they don't already exist. Our best-practice recommendation is that tenants in Knock should map one-to-one to whatever abstraction you use to model accounts, organizations, or workspaces. You can think of tenants as the top-level container within your data model that you use to power multi-tenancy in your application. } /> ### About objects [Objects](/concepts/objects) in Knock are flexible abstractions meant to map to a resource in your system. Each individual object in Knock exists within a `collection` and requires an `id` unique to that collection. In the context of SlackKit, objects serve two purposes. First, they store the Slack channel or channels you want to notify. Second, they act as the recipient of the workflow that sends a message to Slack. You can think of collections as the different tables within your database that represent resources in your application. Objects are the rows within those tables. } /> #### Example Let's say we're building an example source control application like GitHub, where teams can collaborate and share code repositories. In this context, each GitHub organization would map to a tenant in Knock, and each repository would become an object inside of a `repositories` collection. If we want to be notified in Slack each time an issue is opened against a repository, we would store a Slack channel on each repository object and then trigger a `new-issue` workflow. Knock will use the data stored on the object and tenant to route a message to the correct Slack channel: ```javascript await knockClient.workflows.trigger("new-issue", { recipients: [ { collection: "repositories", id: "knocklabs/javascript", }, ], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ### Merging channel data In this implementation, we'll actually store [the required channel data](/in-app-ui/react/slack-kit#channel-data-requirements) for a `SlackConnection` across two different entities in Knock: a `Tenant` and an `Object`. This is because we want to store the `access_token` for the Slack workspace on the `Tenant` and the `channel_id` for the Slack channel on the `Object`. When you trigger a workflow using this recipient and tenant, Knock uses the destination stored on the `Object` (the `channel_id`) and the access token stored on the `Tenant` for auth — if the object also carries a token, the tenant's takes precedence (see [auth vs. destination](/integrations/chat/slack/overview#how-slack-delivery-works)). By storing the `access_token` on the `Tenant`, your customers only need to complete the OAuth flow once to connect their Slack workspace to Knock. ## Implementing SlackKit To facilitate the OAuth flow and channel selection process, we'll use Knock's [SlackKit components](/in-app-ui/react/slack-kit). SlackKit is a set of React components that make it easier to build Slack integrations in Knock. You can use SlackKit to build a self-serve Slack integration that allows your application's users to connect their Slack workspace to Knock and send messages to public and private channels. ### Signing a user token The only access you'll need to manage when using SlackKit are grants for your users to interact with their [Tenants](/concepts/tenants) and [Objects](/concepts/objects) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. You'll need to generate a token for your user that includes access to the tenant storing the Slack access token as well as any recipient objects storing Slack channel data described in this reference on [SlackKit resource access grants](/in-app-ui/react/slack-kit#resources-access-grants). Using the below example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; const token = await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "org_3sh72ds78" }, [ Grants.SlackChannelsRead, ]), buildUserTokenGrant( { type: "object", id: "repo-1", collection: "repositories" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockSlackProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Adding provider components In order to give your components the data they need, they must be wrapped in the `KnockSlackProvider`. We recommend putting this high in your component tree so that any Slack components that you use will be rendered within it. The Slack provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockSlackProvider` gives your components access to the status of the connection to your Slack app, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Implementing the OAuth flow with `SlackAuthButton` Your users will give your Slack app access to their own Slack workspaces via the `SlackAuthButton`. This button can be used on its own, or nested in the `SlackAuthContainer` for a bigger visual footprint.
The SlackAuthButton component with SlackAuthContainer
The SlackAuthButton component with SlackAuthContainer
Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with SlackAuthButton" // Without container // With container } /> ``` The `SlackAuthButton` maps a tenant in your product to a customer's Slack workspace. This means in most cases you'll just need a single instance of the `SlackAuthButton`. Remember to consider which roles in your application can access the `SlackAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button and container in the settings area of your product. ### Choosing channels with `SlackChannelCombobox` This combobox contains the list of channels in the connected Slack workspace. Your users will use this combobox to search and select a channel (or more than one channel) to be notified when an event in your application occurs, for example a comment on a video. They can also use this combobox to deselect a connected channel. The combobox automatically shows which channels are already connected, and gives users an easy way to remove them as well.
The SlackChannelCombobox component showing connected channels
The SlackChannelCombobox component showing connected channels
Add your combobox to your application where you'd like the user to select channels to notify: ```javascript title="The SlackChannelCombobox connects an object to one or more channels" ```
  • The combobox will only show channels for Slack workspaces with fewer than 1000 channels (public and private; not including archived). If there are more, the combobox will turn into a text input that accepts a channel ID to connect.
  • The combobox will only show private channels from your users' Slack workspaces if your Slack app has been invited to those channels.
  • The combobox does not show individual users for Slack direct messages.
} /> ## Triggering a workflow Once you have used the `SlackChannelCombobox` to connect an object to one or more channels, you can trigger a workflow to send a message to those channels. Here's an example of how to trigger a workflow using the Knock Node SDK: ```javascript const workflow_run_id = await knockClient.workflows.trigger("new-issue", { recipients: [ { collection: "repositories", id: "knocklabs/javascript", }, ], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ## Overriding recipient connections Override where a Slack chat message is delivered from the template's settings, instead of using the recipient's stored connections. --- title: Overriding a message's recipient connection description: Override where a Slack chat message is delivered from the template's settings, instead of using the recipient's stored connections. tags: ["slack", "chat"] section: Integrations > Slack layout: integrations --- By default, a Slack step delivers to the [channel data](/managing-recipients/setting-channel-data) stored on the recipient. A **recipient connection override** lets a single message template deliver somewhere else — a fixed channel, a specific user, or an incoming webhook — regardless of the recipient's stored connections. This is useful when a message should go somewhere other than the recipient's configured destination: an alerts channel, an internal operations channel, or a dynamic destination you compute per-recipient with Liquid. Recipient connection overrides also determine the destination for [replies to existing Slack threads](/integrations/chat/replying-to-chat-messages#reply-to-an-existing-thread). The parent message timestamp and destination channel are separate values and must refer to the same Slack conversation. ## Setting the override In the workflow builder, open the Slack step's template, then open **Template settings** (the gear icon) and set the **Recipient connection** field. The override is stored on the message template, so it travels with the workflow when you commit and promote it across environments. ## How it changes delivery The override **replaces** the recipient's connections for this message: exactly one message is delivered, to the connection you specify. If the value is empty or renders to invalid JSON at send time, the message is left undelivered with failure details you can inspect on the message. ## What you can specify The override is a JSON object in one of two shapes. ### Incoming webhook Post to one fixed Slack incoming webhook URL: ```json { "incoming_webhook": { "url": "https://hooks.slack.com/services/T00000000/B00000000/XXXX" } } ``` ### Channel or direct message with a bot token Send to a channel (`channel_id`) or a user's direct message (`user_id`), authenticated with a bot token: ```json { "channel_id": "C0123456789", "access_token": "xoxb-your-bot-token" } ``` You can leave out `access_token` and let it come from the [access token stored on the tenant](/integrations/chat/slack/overview#tenant-channel-data-requirements) you trigger the workflow with. In that case, provide only the destination: ```json { "channel_id": "C0123456789" } ``` ## Using Liquid for dynamic destinations The value is Liquid-capable and rendered at send time, so you can route each message dynamically from workflow or recipient data: ```json { "channel_id": "{{ recipient.slack_channel }}" } ``` Recipient connection overrides are currently supported for Slack chat channels. } /> ## Microsoft Teams ## Overview Learn how to use Knock to send Microsoft Teams notifications to your users. --- title: Microsoft Teams notifications with Knock description: Learn how to use Knock to send Microsoft Teams notifications to your users. tags: ["msteams", "teams", "chat"] section: Integrations > Microsoft Teams layout: integrations --- This page covers how to use Knock to send notifications to Microsoft Teams. Depending on your use case, there are a few different ways to approach this integration. This documentation serves as a starting point and will cover the basics of setting up a Microsoft Teams integration with Knock regardless of your use case. ## Supported notification methods Today, Knock supports sending notifications to Microsoft Teams using two different methods: - Using an incoming webhook URL. This is suitable for [internal or one-off integrations](/integrations/chat/microsoft-teams/sending-an-internal-message). - As a Microsoft Teams bot registered with Azure. This is suitable for bots published to the Microsoft Teams Store, and supports both [sending notifications to channels](/integrations/chat/microsoft-teams/sending-a-message-to-channels) and [sending direct messages to users](/integrations/chat/microsoft-teams/sending-a-direct-message). With a bot-based connection, you can [reply to a previous chat step or an existing Microsoft Teams thread](/integrations/chat/replying-to-chat-messages). Incoming webhook connections can only post new messages. How you configure your Microsoft Teams channel in Knock depends on the method you choose. ## Prerequisites If you're using an incoming webhook URL, there are no prerequisites. If you're using a Microsoft Teams bot, you'll need to follow the steps below to enable your users to connect your Teams app to their Microsoft Teams workspaces. Knock does not manage deploying and configuring your bot. Additionally, if you intend to use our [TeamsKit](/in-app-ui/react/teams-kit) SDK or Knock's [Microsoft Teams-related React hooks](/in-app-ui/react/teams-kit#using-teamskit-headless), you'll need to [configure a Graph API-enabled application in the Microsoft Entra admin center](#configure-graph-api-in-microsoft-entra). ### Register a new bot As of July 31, 2025, Microsoft no longer allows the creation of multi-tenant bots in Azure. If you're registering a new bot, you'll need to register it as single-tenant and{" "} publish it to the Microsoft Teams app store {" "} within a cross-tenant app so that it can be installed across customer workspaces. } /> If you haven't already, you'll need to create a bot with the Bot Framework SDK and register it in Azure. Once your bot is created, you'll need to deploy it to Azure so that it can be used by your customers. ### Configure Graph API in Microsoft Entra To use TeamsKit, you'll also need to configure the API permissions and OAuth redirect URL associated with a Graph API-enabled application in the Microsoft Entra admin center. You can use the bot application that has already been registered with Azure, or you can use a separate application. Log in to the Microsoft Entra admin center. In the sidebar, navigate to **Entra ID** > **App registrations**, and locate your application. On your app's registration page, click **Authentication**. Under **Platform configurations**, click **Add a platform** and select **Web**. Copy and paste the following URL into the **Redirect URI** text field: This will allow Knock to handle the OAuth redirect on behalf of your app, and manage connecting a Microsoft Entra tenant to a Knock tenant. Under **Supported account types**, select **Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)**. Click **Configure** to save your changes. In the top-level sidebar, navigate to **API permissions** and click **Add a permission**. Select **Microsoft Graph** > **Application permissions**. Under **Select permissions**, add the following permissions: - `Team.ReadBasic.All`: This permission allows your application to read a list of all teams within a Microsoft Entra tenant. - `Channel.ReadBasic.All`: This permission allows your application to read a list of all channels within a team. - `AppCatalog.Read.All`: This permission allows your application to locate itself in the Microsoft Teams app catalog. - `TeamsAppInstallation.ReadWriteSelfForTeam.All`: This permission allows your application to install itself into a team. - `TeamsAppInstallation.ReadWriteSelfForUser.All`: This permission allows your application to install itself into a user's personal scope. Click **Add permissions** to save your changes. ### Publish your app to the Microsoft Teams Store The Microsoft Teams Store submission process is managed entirely by Microsoft and typically takes 4–6 weeks to complete. We recommend reviewing Microsoft's{" "} app submission guide {" "} ahead of time to understand what's required, as there are several steps involved. } /> Before you can start sending production notifications to your customers, you'll need to publish an app package to the Microsoft Teams Store. The Teams app package includes a `manifest.json` file that contains the configuration for your bot. The required `scopes` will depend on the specific use case that you're targeting; see the documentation on [sending messages to public channels](/integrations/chat/microsoft-teams/sending-a-message-to-channels#adding-required-scopes-to-your-apps-manifest) and [sending direct messages](/integrations/chat/microsoft-teams/sending-a-direct-message#adding-required-scopes-to-your-apps-manifest) for more details. While your submission is under review, you can test your integration end-to-end by sideloading your app into a tenant directly. Download your Teams app as a `.zip` package from the Teams Developer Portal, then upload it to the Teams Admin Center of the tenant you want to test in under **Teams apps** > **Manage app** > **Upload**. This makes your app available in that tenant's catalog without needing to wait for store approval. ## How to connect to Teams with Knock To set up Knock to send notifications as your bot, you'll need your bot's ID and password. These are sometimes called the App ID and App Password, and were provided to you when you registered your bot with Azure or the Microsoft Teams Developer Portal. ### Add Teams to Knock as a channel First you'll need to add Teams as a channel in Knock. Navigate to Integrations > Channels in your Knock dashboard account settings and click “Create channel” to add Microsoft Teams. If you're using an incoming webhook URL, no additional environment configuration is required. If you're using a Microsoft Teams bot, follow these steps to configure your bot in the Knock dashboard. Click “Manage configuration” and scroll down to “Provider settings”. Before selecting a bot type in Knock, you'll first want to confirm how your bot is registered in Azure. You can locate both the Bot Type and App (Entra) Tenant ID in the Azure Portal by navigating to your Azure Bot resource and selecting “Settings” > “Configuration” in the sidebar to view your bot’s configuration details. The “Bot Type” dropdown menu will indicate whether your bot is single-tenant or multi-tenant (legacy). The “App Tenant ID” text field will display the ID of your Entra tenant.
Configuration page for an Azure Bot resource
“Bot Type” and “App Tenant ID” fields on the Azure Bot resource configuration page
If your bot is registered with Azure as a single-tenant bot, select “Single-tenant”. Then, in the “Entra tenant ID” text field, enter the ID of the Microsoft Entra tenant in which your bot is registered. If your bot is registered with Azure as a multi-tenant bot, select “Multi-tenant” in the “Bot type” dropdown. Entering an “Entra tenant ID” is not required for multi-tenant bots. Note that this applies only to existing legacy multi-tenant bots. New Microsoft Teams bots must be registered as single-tenant in Azure.
In the “Bot ID” and “Bot password” fields, enter the ID and password associated with your bot. Click the “Update settings” button to save your changes. If you intend to use TeamsKit, you'll need to enter the client ID and secret associated with a [Graph API-enabled application](#configure-graph-api-in-microsoft-entra) registered with Microsoft Entra. In the “Graph API client ID” and “Graph API client secret” fields, enter the client ID and secret associated with your application. If you registered your bot application in Entra for this purpose, the values for these fields will be the same as the bot ID and password in the previous step. Click the “Update settings” button to save your changes.
### Add a Teams channel step to a workflow Next, navigate to a workflow in Knock that you want to notify Teams and add a chat channel step. Select the Teams channel you just configured and create a notification template for the channel. You can learn more about how to write basic and advanced templates for Teams in the [designing notifications templates section](#designing-notification-templates-for-teams) below. ### Trigger the workflow Now you're ready to notify Teams. [Trigger the workflow](/send-notifications/triggering-workflows) that you added your Teams channel to. You'll need to include a user or object that has [Teams channel data](#how-to-set-channel-data-for-a-microsoft-teams-integration-in-knock) set as the `recipient` on the workflow trigger call; if no `channel_data` is set on the recipient, the Teams step will be skipped. Your Teams channel should have received a notification. If you need to debug your integration, you can view the logs page in the Knock dashboard. ## How to set channel data for a Microsoft Teams integration in Knock In Knock, the [`ChannelData`](/managing-recipients/setting-channel-data) concept provides you a way of storing recipient-specific connection data for a given integration. If you reference the [channel data requirements for Microsoft Teams](/managing-recipients/setting-channel-data#microsoft-teams-channel-data), you'll see that there are two different schemas for an `MsTeamsConnection` stored on a [`User`](/concepts/users) or an [`Object`](/concepts/objects) in Knock. Here's an example of setting channel data on an `Object` in Knock.
In the example above, the KNOCK_TEAMS_CHANNEL_ID variable is the id of the Knock channel you've created to represent your Microsoft Teams integration within the Knock dashboard. You can find it by going to{" "} Integrations {">"}{" "} Channels {" "} in the Knock dashboard and then copying the ID of your Microsoft Teams channel. } /> ### How Teams delivery works When you're sending as a Microsoft Teams bot, every notification needs two things, and Knock lets them live in different places: - **Scope** — which Microsoft Entra tenant to reach: the `ms_teams_tenant_id`. - **Destination** — where the message goes: a `ms_teams_channel_id` (a channel) or a `ms_teams_user_id` (a direct message). The **destination** is stored on the recipient (a [`User`](/concepts/users) or [`Object`](/concepts/objects)). The **scope** can be stored on the recipient too — or, in a multi-workspace product, once on the [tenant](#tenant-channel-data-requirements), so every recipient in that tenant shares the same Entra tenant ID. The two sections below cover each location. If you're using an incoming webhook instead of a bot, none of the above applies: the webhook URL is self-contained and requires no scope or destination. ### Recipient channel data requirements Here's an overview of the data requirements for [setting recipient channel data](/send-notifications/setting-channel-data) for either an incoming webhook URL or a Microsoft Teams bot connection. Both will need to live under the `connections` key. | Property | Type | Description | | ----------- | --------------------- | ------------------------------------------ | | connections | `MsTeamsConnection[]` | One or more connections to Microsoft Teams | An `MsTeamsConnection` can have one of two schemas, depending on whether you're using a Microsoft Teams bot or an incoming webhook. If you're using a Microsoft Teams bot, your `MsTeamsConnection` schema looks like this. You'll use either `ms_teams_channel_id` or `ms_teams_user_id` depending on whether you're storing connection data to message a channel or user in Microsoft Teams: | Property | Type | Description | | ------------------- | -------- | ------------------------------ | | ms_teams_tenant_id | `string` | A Microsoft Entra tenant ID | | ms_teams_team_id | `string` | A Microsoft Teams team ID | | ms_teams_channel_id | `string` | A Microsoft Teams channel ID | | ms_teams_user_id | `string` | A Microsoft Teams user ID | If you're using an incoming webhook, your `MsTeamsConnection` schema is quite simple: | Property | Type | Description | | -------------------- | -------- | --------------------------------------------------------------------------- | | incoming_webhook.url | `string` | The Microsoft Teams incoming webhook URL (to be used instead of the properties above) | ### Tenant channel data requirements When you [map a Microsoft Entra tenant to a Knock tenant](/integrations/chat/microsoft-teams/sending-a-message-to-channels#key-concepts) (as our [TeamsKit](/in-app-ui/react/teams-kit) components do), you store that Entra tenant's ID as `channel_data` on the Knock tenant. At send time, when you trigger a workflow with that `tenant`, Knock takes the [destination](#how-teams-delivery-works) from the recipient's `channel_data` (the `ms_teams_channel_id` or `ms_teams_user_id`) and uses the tenant's `ms_teams_tenant_id` for scope: the tenant provides the scope, the recipient provides the destination. If the recipient also carries an `ms_teams_tenant_id`, the tenant's takes precedence — so the `ms_teams_tenant_id` on the recipient's `MsTeamsConnection[]` above is not required when a tenant holds one. **Do you need a tenant?** Store the Entra tenant ID on a Knock tenant when one Microsoft Entra tenant connection should serve every recipient or object in that Knock tenant. This keeps the Entra tenant ID in one place while each recipient or object stores only its Teams destination (`ms_teams_channel_id` or `ms_teams_user_id`), which is useful as you add more destinations within the same Entra tenant later. You don't need a Knock tenant if you post to a single internal workspace (use an incoming webhook — there's no Entra tenant ID to share) or only ever use one Entra tenant's ID and prefer to store it directly on each recipient or object. Knock never auto-creates a `tenant` for you (though TeamsKit can, on the fly), so use one when a shared Entra tenant ID should live at the `tenant` level. Here's an overview of the data requirements for setting channel data when storing a Microsoft Entra tenant ID on a Knock tenant. | Property | Type | Description | | ------------------ | -------- | --------------------------- | | ms_teams_tenant_id | `string` | A Microsoft Entra tenant ID | ```json title="Microsoft Teams channel_data on a tenant" { "ms_teams_tenant_id": "MS_TEAMS_TENANT_ID" } ``` ### Setting channel data: users vs. objects Depending on the Microsoft Teams integration you build into your product, you'll store the connection data you receive from Microsoft Teams as `channel_data` on either a `User` or an `Object` in Knock. If your integration involves a user opting in to receive direct messages from your Microsoft Teams integration, you’ll be storing the channel data [on that user](/api-reference/users/set_channel_data) in Knock. When you want to notify this user, you'll include them as a recipient in a Knock workflow trigger. For this integration, you'll store a user's Microsoft Teams `ms_teams_user_id` in the `MsTeamsConnection` object. If your integration involves a customer connecting a _non-user resource_ in their product (such as a project or a page) to a Microsoft Teams channel, you’ll want to store that channel data [on an object](/api-reference/objects/set_channel_data) in Knock, as it’s not specific to any single user. For this integration, you'll store a Microsoft Teams `ms_teams_channel_id` in the `MsTeamsConnection` object. The [`MsTeamsChannelCombobox`](/in-app-ui/react/teams-kit#msteamschannelcombobox) component of Knock's TeamsKit can help you with this. ## Designing notification templates for Teams When you add a new Teams channel step to a workflow in Knock, you'll need to configure a template for that step so Knock knows how to format the message to Teams. By default, we provide a basic markdown editor that you can use for sending simple messages to Teams. Just write in Markdown and we'll handle the rest. (Note: As of February 2022, Teams only supports the following markdown styles: bold, italic, unordered lists, ordered lists, hyperlinks. All other markdown styles are not supported.) ### Advanced Teams notifications If you find yourself wanting to send notifications that include more advanced formatting and interactivity, such as buttons, data layouts, and so on, you'll need to use Microsoft's Adaptive Card format to build your notification templates in Knock. This is essentially a JSON block language you use to lay out your Microsoft Teams message. To switch to the JSON editor in the Knock template designer, look for the "Switch to JSON editor" button at the bottom of the template editor page. When you're in JSON editing mode, you can provide adaptive card JSON and we'll pass it to Microsoft Teams on your behalf. Here's an example of the JSON you'll need to provide. Note that you must include your Adaptive Card JSON within the `attachments` array and set the `contentType` to `application/vnd.microsoft.card.adaptive`. ```json title="Example JSON for sending an Adaptive Card to Microsoft Teams" { "attachments": [ { "content": { "type": "AdaptiveCard", "$schema": "https://adaptivecards.io/schemas/adaptive-card.json", "version": "1.5", "body": [ { "type": "TextBlock", "text": "Lorem ipsum dolor sit" } ], "speak": "Lorem ipsum dolor sit" }, "contentType": "application/vnd.microsoft.card.adaptive", "contentUrl": null } ], "type": "message" } ```
We do not support adaptive card previews in Knock at this time.} /> ## Sending an internal message How to send a message to an internal Microsoft Teams workspace using Knock. --- title: Sending a message to an internal Microsoft Teams workspace description: How to send a message to an internal Microsoft Teams workspace using Knock. tags: ["msteams", "teams", "chat"] section: Integrations > Microsoft Teams layout: integrations --- In this documentation we'll cover how to send a message to an internal Microsoft Teams workspace using Knock. It assumes that you have already created a Microsoft Teams channel in Knock as outlined in the [Microsoft Teams integration](/integrations/chat/microsoft-teams/overview) documentation. ## Microsoft Teams channels are connections on Objects In Knock, we model channels in a Microsoft Teams workspace as connections on Objects. [Objects](/concepts/objects) allow you to model any resource in your system within Knock, and while their primary purpose is to act as non-user recipients, they are very flexible abstractions. ### An overview of Objects Individual Objects exist within [a collection](/concepts/objects#collection-naming) and always have [a unique ID](/concepts/objects#the-object-identifier) or key within that collection. The Object itself can store any number and type of properties as key-value pairs. You can see some examples of possible Object structures in [the official documentation on setting Object data](/concepts/objects#sending-object-data-to-knock). Let’s say you are building a devtool product like GitHub and want to set up Microsoft Teams notifications whenever someone comments on an issue within a repository. First, you’ll want to create an Object to model your repository as part of the `repositories` collection: ```javascript import Knock from "@knocklabs/node"; const knockClient = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knockClient.objects.set("repositories", "repo-1", { name: "My repo", }); ``` Once you have a repository object created, you can add the channel data for Microsoft Teams as a connection on the object. ## Objects as workflow recipients To add channel data, we’ll set up an incoming webhook in Microsoft Teams using the Workflows app (powered by Power Automate). Follow Microsoft’s documentation on creating webhooks using Workflows to generate a webhook URL for the Teams channel you want to post to. ### Set the webhook as channel data Now that you have the webhook URL, we’ll store that webhook as a special property on the repository Object called channel data. [Channel data is both channel and recipient-specific data](https://docs.knock.app/managing-recipients/setting-channel-data) stored for use with particular channels, like a token used for push notifications or webhooks stored for chat apps like Slack, Teams, and Discord. Both Users and Objects can store channel data. In the code example below, we’ll use the `knockClient.objects.setChannelData` method to update the channel data for our repository Object. ```javascript import Knock from "@knocklabs/node"; const knockClient = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knockClient.objects.setChannelData( "repositories", repository.id, process.env.KNOCK_MS_TEAMS_CHANNEL_ID, { connections: [ { incoming_webhook: { url: "url-from-ms-teams" }, }, ], }, ); ``` Here, you’ll also need your `KNOCK_MS_TEAMS_CHANNEL_ID` as the third parameter, which is the channel ID of your Microsoft Teams integration within Knock, so that Knock can reference that channel when it processes workflows that use it. The last parameter is an object of a specific format that varies based on [the type of message provider](/managing-recipients/setting-channel-data#provider-data-requirements). In this case, it is an `MsTeamsConnection` object with an `incoming_webhook` property that contains the URL of an incoming webhook in Microsoft Teams. ### Trigger a workflow with an object recipient With the channel data in place, you can add Microsoft Teams as a workflow step in any workflow. For this example, we’ll create a `new-issue` workflow that pings users in our connected Microsoft Teams channel whenever someone adds a new issue. A workflow with a Microsoft Teams step As you create your message template, remember that in this case the repository Object is the recipient of your workflow. That means any properties you reference on your Liquid template tags need to exist as properties of the Object as well: ```markdown There was an issue opened on the following repo: **{{ recipient.name }}** ``` Finally, we’ll add the workflow trigger to our code with the repository Object as a recipient. ```javascript import Knock from "@knocklabs/node"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); await knock.workflows.trigger("new-issue", { recipients: [{ collection: "repositories", id: "repo-1" }], }); ``` With that, you should see a message in your selected Microsoft Teams channel: A Microsoft Teams message ## Sending a direct message How to send a message to a user in Microsoft Teams using Knock. --- title: Sending a direct message to a user in Microsoft Teams description: How to send a message to a user in Microsoft Teams using Knock. tags: ["msteams", "teams", "chat"] section: Integrations > Microsoft Teams layout: integrations --- This page covers how to update a Microsoft Teams bot to send direct messages to Teams users using Knock. It assumes that you have already created a Microsoft Teams channel in Knock as outlined in the [Microsoft Teams integration overview](/integrations/chat/microsoft-teams/overview). In this implementation, your application's users will connect their Microsoft Entra tenant to Knock and be able to send messages to individual users via direct message. To make this easier to implement, we'll use Knock's [TeamsKit components](/in-app-ui/react/teams-kit) to facilitate the OAuth flow. ## Prerequisites Make sure your bot has been registered and deployed with Azure. Knock does not manage deploying and configuring your bot. To set up Knock to send notifications as your bot, see [How to connect to Teams with Knock](/integrations/chat/microsoft-teams/overview#how-to-connect-to-teams-with-knock). ## Key concepts TeamsKit connects multiple concepts in Knock to make it easier for your application's users to start using your Microsoft Teams integration. `Tenants` are a concept you'll see throughout the following docs that are foundational to how TeamsKit works, but might not be used in every implementation of Knock. ### About tenants [Tenants](/concepts/tenants) in Knock are meant to represent groups of users who typically share the same resources. You might call these "accounts," "organizations," "workspaces," or something similar. In a typical implementation using TeamsKit, you'll store the ID of a Microsoft Entra tenant on a corresponding tenant in Knock. See [how the tenant's Entra tenant ID is used at send time](/integrations/chat/microsoft-teams/overview#tenant-channel-data-requirements) for the full model. If you already use Knock's tenant concept to power other 'account-based' features, you likely create tenants in Knock when an account or organization is created in your application. If you don't already use tenants in Knock, TeamsKit can create tenants for you on the fly if they don't already exist. Our best-practice recommendation is that tenants in Knock should map one-to-one to whatever abstraction you use to model accounts, organizations, or workspaces. You can think of tenants as the top-level container within your data model that you use to power multi-tenancy in your application. } /> ### Merging channel data In this implementation, we'll actually store [the required channel data](/integrations/chat/microsoft-teams/overview#how-to-set-channel-data-for-a-microsoft-teams-integration-in-knock) for an `MsTeamsConnection` across two different entities in Knock: a `Tenant` and an `User`. This is because we want to store the `ms_teams_tenant_id` for the Microsoft Entra tenant on the Knock `Tenant` and the `ms_teams_user_id` for the Microsoft Teams user on the Knock `User`. When you trigger a workflow using this recipient and tenant, Knock uses the destination stored on the `User` (the `ms_teams_user_id`) and the Entra tenant ID (`ms_teams_tenant_id`) stored on the `Tenant` for scope — if the user also carries an Entra tenant ID, the tenant's takes precedence (see [scope vs. destination](/integrations/chat/microsoft-teams/overview#how-teams-delivery-works)). By storing the `ms_teams_tenant_id` on the Knock `Tenant`, your customers only need to complete the OAuth flow once to connect their Microsoft Entra tenant to Knock. From there, you can create UI that allows users to link their Microsoft Teams user ID to their Knock user ID or automate this process during user registration. ## Adding required scopes to your app's manifest In order for your bot to send direct messages to users in Microsoft Teams, you'll need to update your Microsoft Teams app's manifest so that it includes the `personal` scope for your bot. In your `manifest.json` file, add `personal` to your bot's array of scopes: ```json { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json", "manifestVersion": "1.17", "version": "1.0.0", "id": "{{YOUR_TEAMS_APP_ID}}", "bots": [ { "botId": "{{YOUR_BOT_ID}}", "scopes": ["personal"] } ] } ``` ## Implementing TeamsKit To facilitate the OAuth flow and channel selection process, we'll use Knock's [TeamsKit components](/in-app-ui/react/teams-kit). TeamsKit is a set of React components that make it easier to build Microsoft Teams integrations in Knock. You can use TeamsKit to build a self-serve Microsoft Teams integration that allows your users to connect their Microsoft Teams instances to Knock. ### Signing a user token The only access you'll need to manage when using TeamsKit are grants for your users to interact with their [Tenant](/concepts/tenants) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. You'll need to generate a token for your user that includes access to the Knock tenant storing the Microsoft Entra tenant ID as well as any recipient objects storing Microsoft Teams channel data described in this reference on [TeamsKit resource access grants](/in-app-ui/react/teams-kit#resource-access-grants). Using the below example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; const token = await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "org_3sh72ds78" }, [ Grants.MsTeamsChannelsRead, ]), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockMsTeamsProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Adding provider components In order to give your components the data they need, they must be wrapped in the `KnockMsTeamsProvider`. We recommend putting this high in your component tree so that any TeamsKit components that you use will be rendered within it. The Microsoft Teams provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockMsTeamsProvider` gives your components access to the status of the connection to your Microsoft Teams bot, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Implementing the OAuth flow with `MsTeamsAuthButton`
The MsTeamsAuthButton component with MsTeamsAuthContainer
The MsTeamsAuthButton component with MsTeamsAuthContainer
Your users will connect your Microsoft Teams bot to their own Microsoft Entra tenants via the `MsTeamsAuthButton`. This button can be used on its own, or nested in the `MsTeamsAuthContainer` for a bigger visual footprint. Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with MsTeamsAuthButton" // Without container // With container } /> ``` The `MsTeamsAuthButton` maps a tenant in your product to a customer's Microsoft Entra tenant. This means in most cases you'll just need a single instance of the `MsTeamsAuthButton`. Remember to consider which roles in your application can access the `MsTeamsAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button/container in the settings area of your product. The MsTeamsAuthButton component does not automatically install your Microsoft Teams bot into a user's personal scope. Your users will need to{" "} manually install your bot into their personal scope {" "} before you can send direct messages to them. Alternatively, provide instructions to your app's admins to{" "} preinstall your bot for all Microsoft Teams users in their organization . } /> ## Setting User channel data Once your Microsoft Teams bot is installed in a user's personal scope, your bot's messaging endpoint will receive an installation update event. You can use this event to update the channel data associated with the Knock `User`. In order to determine the user ID of the Knock `User`, you'll likely want to query your application's database based on the attributes of the Microsoft Teams user who installed your bot. The get user API of the Microsoft Graph API provides a convenient way to look up the email address of the user who installed your bot. If you're using the Bot Framework SDK for JavaScript and the Microsoft Graph JavaScript client library with the Knock Node SDK, your code will look something like this: ```javascript import Knock from "@knocklabs/node"; import { Client } from "@microsoft/microsoft-graph-client"; const knockClient = new Knock({ apiKey: process.env.KNOCK_API_KEY }); const graphApiClient = Client.initWithMiddleware({ // Initialize your Microsoft Graph API client here }); export class TeamsBot extends TeamsActivityHandler { constructor() { super(); this.onInstallationUpdateAdd(async (context, next) => { const { activity } = context; // Bot was installed into a user's personal scope if (activity.conversation.conversationType === "personal") { // The unique ID of the Microsoft user in Entra const { aadObjectId } = activity.from; // Get the user's email address via the Microsoft Graph API const userDetails = await graphApiClient .api(`/users/${aadObjectId}`) .get(); const emailAddress = userDetails.mail; const knockUserId = getKnockUserIdFromEmailAddress(emailAddress); // This user ID is unique to the Microsoft Teams user AND your bot const msTeamsUserId = activity.from.id; knockClient.users .setChannelData(knockUserId, process.env.KNOCK_MS_TEAMS_CHANNEL_ID, { connections: [{ ms_teams_user_id: msTeamsUserId }], }) .catch(console.error); } await next(); }); } } ``` Here, `KNOCK_MS_TEAMS_CHANNEL_ID` is the channel ID of your Microsoft Teams integration within Knock. `getKnockUserIdFromEmailAddress` is a function that you'll need to implement to look up a Knock `User` ID in your application's database for a given email address. How you get this ID depends upon your specific application. Please keep in mind that if you intend to use the Microsoft Graph API in this fashion, you'll need to add the `User.Read.All` API permission when [configuring your Graph API-enabled app in Microsoft Entra](/integrations/chat/microsoft-teams/overview#configure-graph-api-in-microsoft-entra). ## Triggering a workflow Once you have saved the user's Microsoft Teams user ID as channel data, you can trigger a workflow to send a message to that user's DM channel. Here's an example of how to trigger a workflow using the Knock Node SDK: ```javascript const workflow_run_id = await knockClient.workflows.trigger("new-issue", { recipients: ["user_1n38knd"], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ## Sending a message to channels How to send a message to public channels in Microsoft Teams using Knock. --- title: Sending a message to public channels description: How to send a message to public channels in Microsoft Teams using Knock. tags: ["msteams", "teams", "chat"] section: Integrations > Microsoft Teams layout: integrations --- This page covers how to update a Microsoft Teams bot to send messages to channels in Microsoft Teams using Knock. It assumes that you have already created a Microsoft Teams channel in Knock as outlined in the [Microsoft Teams integration overview](/integrations/chat/microsoft-teams/overview). In this implementation, your application's users will connect their Microsoft Entra tenant to Knock and be able to send messages to public channels. To make this easier to implement, we'll use Knock's [TeamsKit components](/in-app-ui/react/teams-kit) to facilitate the OAuth flow and channel selection process. Microsoft Teams bots do not support sending messages to private channels. } /> ## Prerequisites Make sure your bot has been registered and deployed with Azure. Knock does not manage deploying and configuring your bot. To set up Knock to send notifications as your bot, see [How to connect to Teams with Knock](/integrations/chat/microsoft-teams/overview#how-to-connect-to-teams-with-knock). ## Key concepts TeamsKit connects multiple concepts in Knock to make it easier for your users to create a Microsoft Teams integration. There are two key concepts you'll see throughout the following docs that are foundational to how TeamsKit works, but might not be used in every implementation of Knock: tenants and objects. ### About tenants [Tenants](/concepts/tenants) in Knock are meant to represent groups of users who typically share the same resources. You might call these "accounts," "organizations," "workspaces," or something similar. In a typical implementation using TeamsKit, you'll store the ID of a Microsoft Entra tenant on a corresponding tenant in Knock. See [how the tenant's Entra tenant ID is used at send time](/integrations/chat/microsoft-teams/overview#tenant-channel-data-requirements) for the full model. If you already use Knock's tenant concept to power other 'account-based' features, you likely create tenants in Knock when an account or organization is created in your application. If you don't already use tenants in Knock, TeamsKit can create tenants for you on the fly if they don't already exist. Our best-practice recommendation is that tenants in Knock should map one-to-one to whatever abstraction you use to model accounts, organizations, or workspaces. You can think of tenants as the top-level container within your data model that you use to power multi-tenancy in your application. } /> ### About objects [Objects](/concepts/objects) in Knock are flexible abstractions meant to map to a resource in your system. Each individual object in Knock exists within a `collection` and requires an `id` unique to that collection. In the context of TeamsKit, objects serve two purposes. First, they store the Microsoft Teams channel or channels you want to notify. Second, they act as the recipient of the workflow used to send a message to Microsoft Teams. You can think of collections as the different tables within your database that represent resources in your application. Objects are the rows within those tables. } /> #### Example Let's say we're building a source control application like GitHub, where teams can collaborate and share code repositories. In this context, each GitHub organization would map to a tenant in Knock, and each repository would become an object inside of a `repositories` collection. If we want to be notified in Microsoft Teams each time an issue is opened against a repository, we would store a Microsoft Teams channel on each repository object and then trigger a `new-issue` workflow. Knock will use the data stored on the object and tenant to route a message to the correct Microsoft Teams channel: ```javascript await knockClient.workflows.trigger("new-issue", { recipients: [ { collection: "repositories", id: "knocklabs/javascript", }, ], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ### Merging channel data In this implementation, we'll actually store [the required channel data](/integrations/chat/microsoft-teams/overview#how-to-set-channel-data-for-a-microsoft-teams-integration-in-knock) for an `MsTeamsConnection` across two different entities in Knock: a `Tenant` and an `Object`. This is because we want to store the `ms_teams_tenant_id` for the Microsoft Entra tenant on the Knock `Tenant` and the `ms_teams_channel_id` for the Microsoft Teams channel on the Knock `Object`. When you trigger a workflow using this recipient and tenant, Knock uses the destination stored on the `Object` (the `ms_teams_channel_id`) and the Entra tenant ID (`ms_teams_tenant_id`) stored on the `Tenant` for scope — if the object also carries an Entra tenant ID, the tenant's takes precedence (see [scope vs. destination](/integrations/chat/microsoft-teams/overview#how-teams-delivery-works)). By storing the `ms_teams_tenant_id` on the Knock `Tenant`, your customers only need to complete the OAuth flow once to connect their Microsoft Entra tenant to Knock. ## Adding required scopes to your app's manifest In order for your bot to send messages to channels in Microsoft Teams, you'll need to update your Microsoft Teams app's manifest so that it includes the `team` scope for your bot. In your `manifest.json` file, add `team` to your bot's array of scopes: ```json { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json", "manifestVersion": "1.17", "version": "1.0.0", "id": "{{YOUR_TEAMS_APP_ID}}", "bots": [ { "botId": "{{YOUR_BOT_ID}}", "scopes": ["team"] } ] } ``` ## Implementing TeamsKit To facilitate the OAuth flow and channel selection process, we'll use Knock's [TeamsKit components](/in-app-ui/react/teams-kit). TeamsKit is a set of React components that make it easier to build Microsoft Teams integrations in Knock. You can use TeamsKit to build a self-serve Microsoft Teams integration that allows your users to connect their Microsoft Teams instances to Knock and send messages to public channels. ### Signing a user token The only access you'll need to manage when using TeamsKit are grants for your users to interact with their [Tenants](/concepts/tenants) and [Objects](/concepts/objects) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. You'll need to generate a token for your user that includes access to the Knock tenant storing the Microsoft Entra tenant ID as well as any recipient objects storing Microsoft Teams channel data described in this reference on [TeamsKit resource access grants](/in-app-ui/react/teams-kit#resource-access-grants). Using the below example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; const token = await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "org_3sh72ds78" }, [ Grants.MsTeamsChannelsRead, ]), buildUserTokenGrant( { type: "object", id: "repo-1", collection: "repositories" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockMsTeamsProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Adding provider components In order to give your components the data they need, they must be wrapped in the `KnockMsTeamsProvider`. We recommend putting this high in your component tree so that any TeamsKit components that you use will be rendered within it. The Microsoft Teams provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockMsTeamsProvider` gives your components access to the status of the connection to your Microsoft Teams bot, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Implementing the OAuth flow with `MsTeamsAuthButton`
The MsTeamsAuthButton component with MsTeamsAuthContainer
The MsTeamsAuthButton component with MsTeamsAuthContainer
Your users will connect your Microsoft Teams bot to their own Microsoft Entra tenants via the `MsTeamsAuthButton`. This button can be used on its own, or nested in the `MsTeamsAuthContainer` for a bigger visual footprint. Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with MsTeamsAuthButton" // Without container // With container } /> ``` The `MsTeamsAuthButton` maps a tenant in your product to a customer's Microsoft Entra tenant. This means in most cases you'll just need a single instance of the `MsTeamsAuthButton`. Remember to consider which roles in your application can access the `MsTeamsAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button/container in the settings area of your product. The MsTeamsAuthButton component does not automatically install your Microsoft Teams bot into a team. Your users will need to{" "} manually add your bot to their teams {" "} before you can send messages to channels within those teams. Alternatively, provide instructions to your app's admins to{" "} install your bot into existing teams {" "} and{" "} preinstall your bot when new teams are created . } /> ### Choosing channels with `MsTeamsChannelCombobox` This combobox contains the list of teams and channels belonging to the connected Microsoft Entra tenant. Users will use this combobox to search and select a channel (or more than one channel) to be notified when an event in your application occurs, for example a comment on a video. They can also use this combobox to deselect a connected channel. The combobox automatically shows which channels are already connected, and gives users an easy way to remove them as well.
The MsTeamsChannelCombobox component showing connected channels
The MsTeamsChannelCombobox component showing connected channels
Add your combobox to your application where you'd like the user to select channels to notify: ```javascript title="The MsTeamsChannelCombobox connects an object to one or more channels" ```
  • The combobox will only show public channels. Microsoft Teams bots do not support sending messages to private channels.
  • The combobox does not show individual users for Microsoft Teams direct messages.
} /> ## Triggering a workflow Once you have saved the Microsoft Teams channel ID as channel data on an object, you can trigger a workflow to send a message to that channel. Here's an example of how to trigger a workflow using the Knock Node SDK: ```javascript const workflow_run_id = await knockClient.workflows.trigger("new-issue", { recipients: [ { collection: "repositories", id: "knocklabs/javascript", }, ], tenant: "knocklabs", data: { message: formData.get("newIssue"), }, }); ``` ## Discord Get started sending Discord notifications with Knock. --- title: How to send Discord notifications with Knock description: Get started sending Discord notifications with Knock. tags: ["discord", "chat"] section: Integrations layout: integrations --- This page covers how to use Knock to send notifications to Discord. We'll cover the two methods for sending notifications to Discord, configuration requirements, and how to trigger a notification to your Discord channel. ## Methods for sending notifications to Discord We support two main methods for sending notifications to Discord: 1. Discord incoming webhooks 2. Discord bots Incoming webhooks are simpler in terms of configuration and will probably serve best for most cases but if that's not enough, using bots should provide enough flexibility for all use cases. The main difference with both approaches is that for incoming webhooks, you need to create one per Discord channel. If you are planning to notify just a few Discord channels, this is probably the easiest, but if you need to notify multiple Discord channels the bot approach will work best. Additionally, Message Components cannot be used with the incoming webhook approach, so if you're planning on leveraging those you should create a bot. Next we'll walk through how to configure each of these methods to send Discord notifications using Knock. ## Method 1: Incoming webhooks ### Configuring Discord To create an incoming webhook for a Discord channel, hover over the channel name and you'll see a gear icon appear. Click the gear and you'll be taken to the channel's edit screen. Click on "Integrations." Next, click on "Create Webhook," provide a name, image and a channel and copy the webhook url by clicking on "Copy Webhook URL." The channel you have just selected will be the one notified when a Knock workflow gets triggered. We'll use the URL you copied to set that data in Knock. Here's an animation of where to find these details in Discord: ![Channel integrations screen](/images/integrations/chat/discord/create-discord-webhook.gif) ### Configuring Knock Set a new Object in Knock that will contain the Discord channel data ([Object documentation here](/api-reference/objects/set)). For this example, let's call our object "My project" and give it an id of `project-1`. Its collection will be `projects`. Then we set that in Knock: If we check the **Objects** page under the **Recipients** section in our Knock dashboard, we can see the project exists there. Now we can give it specific channel information so it knows where to post in Discord. Take the URL you copied from the Discord webhook and set channel data on the Object we just set:
In the example above, the KNOCK_DISCORD_CHANNEL_ID variable is the id of the Knock channel you've created to represent your Discord integration within the Knock dashboard. You can find it by going to{" "} Integrations {">"}{" "} Channels in the Knock dashboard and then copying the ID of your Discord channel. } /> You can navigate back to your Objects dashboard and verify that `project-1` has the appropriate data set. Now you're ready to send notifications to Discord via incoming webhook! Jump to the [Triggering the workflow](/integrations/chat/discord#triggering-the-workflow) section to do so. ## Method 2: Bots ### Configuring Discord #### 1. Create an app and bot You'll need to set up a bot and app to handle incoming messages from Knock. You can learn more in the documentation for creating Discord apps and creating Discord bots (note: you'll need to be logged in to access the applications docs). #### 2. Set OAuth permissions for the bot Once you have a bot created for your app in Discord, you can set permissions and authorize it to join your Discord server. Within the Application you've created on Discord, find the URL Generator under `OAuth2`. You can access the URL generator using the URL below: `https://discord.com/developers/applications//oauth2/url-generator`.
There are two types of permissions you'll be working with when using the Discord API: Scopes and Bot permissions.
  • A{" "} scope {" "} is a permission granted to a Discord app when it joins a Discord server.
  • Bot permissions {" "} dictate what the bot can do in the server.
} /> The URL Generator will build our bot invitation link for us. Under `Scopes` check "bot" and under `Bot permissions` check "Send Messages." If you have more advanced use cases, here's where you can set various permissions for your bot, but this is the minimum we need to give it the ability to pass along messages from Knock. Accessing OAuth in Discord Accessing OAuth in Discord 2 Scroll down and copy the generated URL, and **paste it into your browser**. You will see a prompt to allow it access to the Discord servers you are an administrator of. Select your desired server and proceed to authorize it. Accessing OAuth in Discord 2 Now you should see your new bot enter your Discord server! We can move on to configuring your channel in Knock. ### Configuring Knock #### 1. Add your Discord channel to Knock For this, you'll need your bot's token. In your Discord account in the browser, navigate back to your bot's settings: `https://discord.com/developers/applications//bot` If you don't see a token, click "Reset token" and copy the result. Getting a token from the Discord bot Now you can go to your Knock channels and create a new `Chat` channel, selecting Discord as the type. Once it's created, click "Edit configuration" and paste in the Discord bot token you just created/copied above. #### 2. Set a Discord channel's data on a Knock Object Set a new Object in Knock that will contain the Discord channel data ([Object documentation here](/api-reference/objects)). For this example, let's call our object "My project" and give it an id of `project-1`. Its collection will be `projects`. Then we set that in Knock: For Knock to know what Discord channel should post when "My project" is a recipient of a workflow trigger action, we need to set that information on "My project"'s channel data. To do this we need the channel ID from Discord. Open Discord, navigate to `Preferences` in the main menu, and go to the `Advanced` tab under `App Settings`. From here you'll see a toggle for Developer Mode. Make sure that's turned on, then navigate back to your Discord server. Now if you right-click on a channel, you'll see "Copy ID" as an option in the list. Copy the ID for the channel you want to post to. For clarity, we'll call this the `Discord channel ID`. ![Get discord channel ID](/images/integrations/chat/discord/discord-channel-id.gif)
When you're ready to go to production with your integration you'll need a way for your customers to select which Discord channel they want your integration to publish to. You can learn more about{" "} fetching Discord channels for display in your app here.
} /> We'll also need the ID for the Knock chat channel we set up in step 3. We'll refer to this as the `Knock chat channel ID`, which you can get by navigating to the **Channels and sources** page under the account settings section of your Knock dashboard. Now we can set channel data for our project: You can navigate back to your Objects dashboard and verify that Project 1 has the appropriate data set. We've finished the Knock and Discord configurations and it's time to test triggering a workflow! ## Triggering the workflow We'll navigate back to our example app which will notify Discord users when a new comment is made. We just need to make sure we include our project as one of the recipients for this notification by including its ID and collection, like in the example below: Depending on how you have configured your integration you should be seeing either your Discord bot or an incoming webhook ping the appropriate Discord channel. ## Channel data requirements In order to send a message to a recipient, you'll need to have the following [channel data](/managing-recipients/setting-channel-data) set for the recipient. - **connections** (`DiscordConnection[]*`) - One or more connections to Discord. ### `DiscordConnection` with incoming webhook URL - **incoming_webhook.url** (`string*`) - The incoming webhook URL. ### `DiscordConnection` with a bot token - **channel_id** (`string*`) - A Discord channel ID. ## WhatsApp Get started sending WhatsApp notifications with Knock. --- title: How to send WhatsApp notifications with Knock description: Get started sending WhatsApp notifications with Knock. section: Integrations > Chat layout: integrations --- Knock integrates with WhatsApp to send notifications to your recipients via the WhatsApp Business API. This integration enables you to send template-based messages directly to your users' WhatsApp accounts, providing a familiar and widely-used communication channel for important notifications. ## Features - Per environment configuration - Sandbox mode ## Getting started Before you set up your WhatsApp chat channel in Knock, you'll need to take the following steps in WhatsApp. ### 1. Create a business app on Facebook Login to your Facebook developer account and click on the Create app button, then choose the first "Business" type app and complete the details with your personal information. Create business app ### 2. Add WhatsApp as a product Now that your app is created you need to add a product, scroll down to find the "WhatsApp" product and click "Set up." After this you will be redirected to the WhatsApp get started page, here you can "create a business account" or use an existing one. Dashboard app ### 3. Send a test message In case you want to send a test message, you can go to _WhatsApp/first steps_, where you will find the **temporary access token**, **phone number id** and a **curl of send messages**. Send test message **Note: The `phone_number_id` is from a Facebook test phone number which cannot be used in production.** Then you need to add your personal number in the **recipient phone number field** and click "Send message" Keep in mind that to configure Knock, you are looking for your{" "} WhatsApp Access Token and your{" "} Phone Number Id (not your phone number).

} /> ### 4. Add a valid business phone number Next, add a business phone number to send messages from. You can do this using the _Add Phone Number_ button which is below the current page. Add business phone number ### 5. Generate an access token Because the testing access token only last 24 hours, we need to create a token that can last forever: 1. Create an admin user. To do this you need to go to the business settings page. You will see the system users under the section of Users on the left sidebar. After you have created your new user click on _Add Assets_ and choose _App>Select App Name>Full control option_ and save changes. Create user with full control 2. Click on _WhatsApp Accounts_ on the left sidebar and then select the WhatsApp business app and click the _Add people_ button. Then choose the recently created system user and check the **full control option** and click the "Assign" button. Add user to WhatsApp account 3. Go back to the system users page and select the recently created system user from the list. Then click the **Generate new token** button. Choose your app from the dropdown and make sure the `whatsapp_business_management` and `whatsapp_business_messaging` options are checked. Generate token Generate token ### 6. Add a message template The only way of starting a business conversation with a client is using [message templates](https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/). In order to use this you must go to your [message templates dashboard](https://business.facebook.com/wa/manage/message-templates/) and click "Create template" Then you must choose a **category**, **name** and **language** for your template and click "Continue." New template configuration After creating your template you will gain access to your editor, where you must add a **Body** for your message (you can also add a _Header_, _Footer_ or a _Button_ if you want) Here you can use parameters like {"{{1}}"}, which are dynamically incorporated into the message. These are going to be overridden with the parameters you send in your template object with Knock } /> Whatsapp Template editor Once you have finished you must click on "Submit," then you have to wait for WhatsApp to [approve your template](https://developers.facebook.com/docs/whatsapp/message-templates/guidelines/) in order to start using it. ## Configuring WhatsApp in Knock Now that you have a business **phone number id**, an **access token** and a **template message** you're ready to configure your WhatsApp channel within Knock. ### 1. Create a WhatsApp channel You can create a new WhatsApp channel in the dashboard by navigating to **Channels and sources** in your account settings. From there, you'll need to configure the channel for each environment you have using your **access token** and **phone number id**. ### 2. Send a template object In order to use a WhatsApp template message, you must send a template object. For this you must specify this object in your message body using your template editor and JSON (with Liquid if you want), following the next format: ``` { "template": { "name": "{{template_name}}", "language": { "code": "{{template_language}}" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "{{parameter}}" } ] } ] } } ```
Keep in mind that your {"{{template_name}}"} and{" "} {"{{template_language}}"} must be your WhatsApp message template name and language respectively.
Notice how the {"{{parameter}}"} is going to override the parameter from your WhatsApp template, and the amount of parameters you send here must the same you have in your WhatsApp template too. } /> ## Additional information Here are a few other things to keep in mind once you have your WhatsApp channel configured in Knock: - **Deliverability tracking.** We cannot currently track deliverability through WhatsApp channels. This means that all notifications sent through WhatsApp will show up as "Sent" in the Knock messages log, but not "Delivered." ## Provider configuration - **Authentication token** (`string*`) - The authentication token from your WhatsApp app. - **Phone Number ID** (`string*`) - The phone number ID associated with your business phone number. ## Recipient data requirements In order to send a notification you'll need a valid `phone_number` property set on your recipient in E.164 format. # In-app ## Overview Learn how to build in-app notifications like feeds and inboxes with Knock. --- title: In-app notifications with Knock description: "Learn how to build in-app notifications like feeds and inboxes with Knock." tags: ["inbox", "feeds", "toasts", "in app", "in-app"] section: Integrations > In-app layout: integrations --- In addition to delivering to out-of-app channels such as email, push, SMS, and chat apps like Slack, you can also use Knock to build great in-app notifications experiences too. You can use our in-app feed channel to build stateful, in-app notifications experiences like floating feeds, inboxes, toasts and banners, and you can use our preferences API to build powerful user-facing preference controls. You can also use [Knock link tracking](/send-notifications/tracking) to capture link-click events right within your Knock account. For more information about powering in-app notifications with Knock, see our [building in-app UI documentation](/in-app-ui/overview). [See a live demo ->](https://in-app-demo.knock.app/) ## Supported providers - [Knock in-app](/integrations/in-app/knock) If you want us to add a new provider to this list, please let us know through the feedback button at the top of this page. ## Knock Learn how to build in-app notifications using Knock's notification system. --- title: Knock in-app notifications description: "Learn how to build in-app notifications using Knock's notification system." tags: ["inbox", "feeds", "toasts"] section: Integrations > In-app layout: integrations --- The Knock in-app notification channel provides an easy way to bring in-app notifications into your application. You can use our [pre-built components](/in-app-ui/react/overview) to get up and running quickly with feeds, toasts, or inboxes embedded into your application, or you can build custom UI on top of our [in-app API](/api-reference/users/feeds). **Related resources**: - [Building in-app experiences with Knock](/in-app-ui/overview) - [Integrating the Knock feed into your application](/in-app-ui/react/feed) ## Editing in-app notification templates You can edit the template for an in-app notification by clicking the "Edit content" button on your workflow's in-app channel step. This will open the [template editor](/template-editor/overview), where you can edit the content for the in-app notification. ### Action URL The action URL is the URL that will be opened when a user clicks on the notification cell in their in-app feed. Toggle "include click action" on your template to enable this functionality. You can provide a static URL, or configure a dynamic URL using a [Liquid variable](/template-editor/variables). By default, the in-app feed will redirect to the action URL when the notification cell is clicked. See our documentation on [handling interactivity](/in-app-ui/feeds/handling-interactivity) for more details on how you can customize this behavior with `onNotificationClick` and `onNotificationButtonClick` handlers. ### Template variants Your in-app notification template has three variants available: - **Standard.** A simple notification with content and an optional action URL. When the action URL is enabled, clicking the notification cell will redirect to the configured URL. - **Single-action.** A notification with a single primary button. This variant enables you to add a label to your action URL. - **Multi-action.** A notification with two buttons, one primary and one secondary. This variant enables you to configure two separate action URLs for your notification. See the [frequently asked questions](#frequently-asked-questions) section below for more information on customizing the action button styles. ## Understanding message statuses Every in-app feed notification sent by Knock has an [engagement status](/send-notifications/message-statuses#engagement-status) of `unseen`, `seen`, or `read`. You can use these statuses to power notification badge counts, filtering, and mark-as-read functionality. Here's more context on each status: - **Unseen.** The notification has not been rendered in the user's in-app feed, meaning the user hasn't seen the notification yet. - **Seen.** The notification has been rendered in the user's in-app feed, meaning the user opened the feed and saw the notification. If a notification has a status of `seen` it means that it has not yet been marked as read by the user. - **Read.** The notification has been marked as read by the user. There are a few ways notifications can be marked as read by users depending on the behavior you'd like to provide. We cover how this works in our own feed in the section below. ### How the Knock in-app feed uses status Message status is used to power a number of different features in the in-app feed. - **Badge counting.** By default, the Knock in-app feed badge counter will show users a count of how many `unseen` messages they have waiting for them in the feed. This means that as soon as they open the feed and see them the messages will be marked as `seen` and the badge count will go back to zero. Some apps prefer to show a count of `unread` messages on their badge counter; we default to `unseen` as it results in less noise and disruption for the user. - **Mark as read.** The Knock in-app feed lets users mark notifications as read in one of two ways. They can mark an individual item as read by clicking on it (which takes them to the URL assigned to that notification) or they can click "mark all as read" to mark all notifications in the feed as `read`. - **Filtering.** You can use the filter button at the top of the feed to filter messages based on their status. This helps users quickly access notifications they've read or haven't read so that they can quickly get to the high priority items in their feed. Note that the functionality above is just the default for what we've provided in our in-app feed. If you'd like to use our message statuses in a different way, you can fork the Knock in-app feed and customize its behavior as you need to. ### Click-tracking events Knock's in-app feed component tracks two separate engagement events related to user clicks on notifications: - **[Interacted](/send-notifications/message-statuses#interacted).** Clicks on the notification cell itself (including buttons) are tracked with the `interacted` [event](/developer-tools/outbound-webhooks/event-types#messageinteracted). Where applicable, this event will include metadata about the [action URL](#action-url) that was clicked. - **[Link clicked](/send-notifications/message-statuses#link-clicked).** When [Knock link tracking](/send-notifications/tracking) is enabled, clicking a link within the in-app notification's body will issue a `link_clicked` [event](/developer-tools/outbound-webhooks/event-types#messagelink_clicked) in addition to the `interacted` event. Action URLs are not wrapped in a trackable link and will not issue a `link_clicked` event. ## Archiving messages Sometimes a user wants to remove a notification from their feed altogether. This is where notification archiving comes in. When a notification is archived, it is removed from the feed and is no longer visible to the end user. In the Knock in-app feed, a message can be archived by clicking the "x" in the top-right corner of a given notification. The in-app feed component doesn't give users a way to see their archived notifications out-of-the-box, but if you'd like to incorporate this into your own in-app feed you can use the `archived: only` parameter on the [GET feed request](/api-reference/users/feeds/list_items). ## Customizing API response content By default, the [in-app feed API](/api-reference/users/feeds/list_items) will return: - One or more actors associated with each in-app notification (when set), including all custom properties under each actor - All of the public variables (note: secret variables are never returned) - The entire workflow trigger data associated with each in-app notification - The recipient associated with each notification, including all custom properties set In some situations, you may need control over exactly what the in-app feed API returns. This is where our API response filter can be used to completely customize the keys returned for the entities in your in-app feed responses. | Property | Description | | --------- | ------------------------------------------------------------------------------ | | vars | Apply a filter to the environment variables returned | | actor | Apply a filter to all actors returned from the feed endpoint | | recipient | Apply a filter to the recipient returned from the feed endpoint | | data | Apply a filter to workflow trigger data returned from the in-app feed endpoint | Each property accepts the following: - A boolean to indicate whether this entity should be omitted or included, entirely (defaults to `true`). - An object with either `except` or `only` key paths for keys to exclude or include. Note: a nested keypath can be given using a `.`. To add the JSON filter for your in-app feed, navigate to **Channels and sources** in your dashboard account settings and select your in-app channel. Then, under each set of environment settings, enter the JSON filter before clicking "Update settings." ### Example: customizing the actor data As an example, if you want to customize the properties returned under an actor to only include `id`, `name`, and `avatar` you can set a response filter such as: ```json { "actor": { "only": ["id", "name", "avatar"] } } ``` If you want to omit certain keys from the actor you can use the `except` keyword: ```json { "actor": { "except": ["email", "phone_number", "credit_card.last4"] } } ``` Or, if you want to exclude the actor from rendering you can set: ```json { "actor": false } ``` ## Frequently asked questions Yes. However, because the in-app feed is rendered as a front-end component in your application, button colors and other visual styles are not part of your message's content and cannot be configured in the Knock dashboard. Knock's in-app feed component styles, including button colors, are fully customizable. You can learn more about how to style the in-app feed in our [styling documentation](/in-app-ui/feeds/styling). Alternatively, you can [build your own UI components](/in-app-ui/feeds/custom-ui) and use them with Knock in a headless way. # Push ## Overview Learn how to send mobile push notifications with Knock. --- title: Push notifications with Knock description: Learn how to send mobile push notifications with Knock. tags: ["push", "android", "ios", "react native"] section: Integrations > Push layout: integrations --- Knock supports sending push notifications directly to native services such as Apple Push Notifications service (APNs) (for iOS notifications) and Firebase Cloud Messaging (for Android push notifications). We also support delivery of notifications through push intermediary services such as Expo. ## Features - **No stateful connections to manage**: we take care of all of the complexity of managing and maintaining stateful connections to your push providers, just simply send us notifications and we'll get them delivered! - **Cross-provider, single template**: you can send the same templated message across multiple providers to reduce the amount of templates to maintain. - [**Token deregistration**](/integrations/push/token-deregistration): if a recipient's invalid device token results in a `bounced` message when attempting to send, Knock can optionally remove the token. - [**Device metadata**](/integrations/push/device-metadata): set device-level `locale` and `timezone` properties for translations and send window evaluation with supported providers. ## Channel groups When you create your first push channel in Knock, you'll notice that we offer a channel group that combines both Apple Push Notifications service (APNs) and Firebase Cloud Messaging. If you are sending notifications directly to each of these services today, consider using a channel group in Knock. A **channel group** can send to multiple providers at once from a single channel step within a workflow. Here's why that's powerful. Without a channel group in place, any workflows that send push notifications will need two channel steps in place, one for Apple and one for Firebase. This can be valuable when you want to take advantage of provider-specific functionality, but in cases where you want to send identical notifications to both providers, it means duplicating (and maintaining) the notification design across both channel steps. With a channel group in place, you can send identical notifications to Apple and Firebase, from the same channel step. This means you can design the notification once, and move on. Workflow with push channel group. One step with a single notification design to update and manage. Request a push channel group. Channel groups are currently enabled on an on-demand basis. To request a push channel group for your account, reach out to us at{" "} support@knock.app. } /> ## Push overrides For push-specific sending needs, you can configure payload overrides at two levels: - **Channel configuration.** When you configure payload overrides on a push channel, these overrides apply to all workflow steps that use that channel in the configured environment. You can set this by navigating to **Channels and sources** in your dashboard account settings, selecting your push channel, then clicking "Manage configuration" under the environment you want to configure. - **Template settings.** You can also configure overrides at the template level by clicking the gear icon (⚙️) at the top of the template editor to access the template settings modal. Precedence. If payload overrides are set at both the channel configuration and template level, the template-level overrides will take precedence and replace the channel-level overrides. } /> These overrides are merged into the push payload sent to the underlying provider and can be used to set badge counts, custom sound files, and other provider-specific settings. By default, all overrides are applied with a `merge` strategy, which can be customized by adding a `__strategy__` key to the top level of the JSON payload and setting as `replace`. When the strategy is set to replace, all existing properties will be overridden with what's included in the overrides JSON. ## Supported providers - [Amazon SNS](/integrations/push/aws-sns) - [Apple Push Notifications service (APNs)](/integrations/push/apns) - [Expo (React Native)](/integrations/push/expo) - [Firebase Cloud Messaging (Android)](/integrations/push/firebase) - [OneSignal](/integrations/push/one-signal) If you want us to add a new provider to this list, please reach out to us at [support@knock.app](mailto:support@knock.app). ## Token deregistration How to use Knock token deregistration to manage recipient tokens by removing invalid tokens. --- title: Token deregistration description: How to use Knock token deregistration to manage recipient tokens by removing invalid tokens. tags: ["token deregistration", "channel data", "bounced"] section: Integrations > Push layout: integrations --- For push providers only, Knock provides an opt-in, provider-agnostic token management capability known as token deregistration. Knock removes invalid tokens (and devices, when using [device metadata](/integrations/push/device-metadata)) from a recipient's corresponding channel data if that token results in a `bounced` message on send. This feature is available for all push providers, except OneSignal when the recipient mode is set to `external_id`. In this case, Knock only has access to the user's ID and therefore cannot deregister the associated external token. Workflow overrides are **not** available for token deregistration. This capability can only be configured at the provider level. ## Availability | Provider | Token deregistration available? | | ------------------------- | ------------------------------- | | APNs | ✅ | | FCM | ✅ | | Expo | ✅ | | Amazon SNS | ✅ | | OneSignal (`player_id`) | ✅ | | OneSignal (`external_id`) | ❌ | ## Configuring Knock token deregistration You can configure Knock tracking on a per-environment basis using your channel's [per-environment configurations](/integrations/overview#per-environment-configurations). Token deregistration will default to `ON` when you first create a channel. An image of a preference set ## How it works When Knock attempts to deliver a message through a supported push provider, any errors caused by invalid or expired tokens result in a bounce. This generates a `message.bounced` event containing the invalid token. Knock will remove the invalid token from the list of tokens present in the recipient's channel data. This allows for an automated audit of the tokens present for recipients. ## Working with Knock token deregistration ### Outbound webhooks If you use Knock's outbound webhooks, you can view the invalid token in the `message.bounced` events captured. If token deregistration is `ON`, no further intervention is needed for token removal. See the [outbound webhooks documentation](/developer-tools/outbound-webhooks/overview) for more details. ## Device metadata How to use channel data to store device-level metadata for push notifications. --- title: Push notification device metadata description: How to use channel data to store device-level metadata for push notifications. tags: [ "push token", "channel data", "device token", "device metadata", "locale", "timezone", ] section: Integrations > Push layout: integrations --- When [setting channel data for push channels](/managing-recipients/setting-channel-data#push-channels), Knock provides the option to set additional metadata alongside a device token. When set, a device-level `locale` will be used when [translating](/template-editor/translations) message content for the device, and the `timezone` will be used to evalute [send windows](/designing-workflows/send-windows). This feature is available for all push providers except OneSignal. ## Availability | Provider | Device metadata supported? | | ---------- | -------------------------- | | APNs | ✅ | | FCM | ✅ | | Expo | ✅ | | Amazon SNS | ✅ | | OneSignal | ❌ | ## How it works When setting a recipient's channel data for a supported push channel, you can pass a list of `devices` objects (containing `token`, `locale`, and `timezone`) rather than a list of `tokens` strings. When a workflow includes a push channel step and the `recipient` channel data includes device metadata, any device-level values will take precedence over the recipient-level `locale` and `timezone` [properties](/managing-recipients/identifying-recipients#reserved-properties). This means: - The translation language used to render message content for the device will be according to the device-level `locale` property. - The timezone used to evaluate the send window for the device will be according to the device-level `timezone` property. All other features of push channels (including [token deregistration](/integrations/push/token-deregistration)) will function the same way, regardless of whether device metadata is set. If you're using Knock's Amazon SNS push notification integration, a{" "} target_arn or target_arns will take the place of{" "} token or tokens when setting channel data. Their functionality is interchangeable. Reference the{" "} provider-specific documentation{" "} for more details. } /> ### Setting device metadata In the example below, we're setting a user's device token by passing `devices` rather than `tokens`. If you do not require device-level locale or timezone properties, you can simply set channel data by passing a list of `tokens` strings. ### Getting push channel data Regardless of whether you set `tokens` or `devices`, you'll see them returned in both formats when retrieving channel data. Devices will include `null` values for the `locale` and `timezone` properties if they were not provided when channel data was set. ```json title="Example push channel data response" { "__typename": "ChannelData", "channel_id": "123e4567-e89b-12d3-a456-426614174000", "data": { "devices": [ { "locale": "en-US", "timezone": "America/New_York", "token": "user_device_token_1" }, { "locale": null, "timezone": null, "token": "user_device_token_2" } ], "tokens": ["user_device_token_1", "user_device_token_2"] } } ``` ## Amazon SNS How to send push notifications with Amazon SNS and Knock. --- title: How to send push notifications to Amazon SNS description: How to send push notifications with Amazon SNS and Knock. tags: ["amazon", "aws", "sns", "push", "android", "ios", "silent push"] section: Integrations layout: integrations --- This page walks through how to configure an Amazon Simple Notification Service (Amazon SNS) provider in Knock to send mobile push notifications. You'll need an Amazon SNS channel in your Knock dashboard to follow along. ## Getting started You can create a new Amazon SNS channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to take some steps in AWS before you can configure your SNS channel within Knock. Knock supports two authentication schemes with Amazon SNS: To send notifications via Amazon SNS using an IAM User, Knock requires the **access key ID** and a **secret access key** of an AWS user with SNS send permissions (you can use the `sns:AmazonSNSFullAccess` permission for this). If you don't already have a user with send permissions, you can create an IAM user in AWS to use with the Knock API. You can learn more about creating IAM users in AWS here. Once you've created your new IAM user, you'll need to provision them with the policy below. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sns:AmazonSNSFullAccess"], "Resource": "*" } ] } ``` Now that you have an AWS user created and provisioned with SNS send access, grab the **access key ID** and a **secret access key** of the user—we'll use these later when configuring the SNS channel within Knock. To send notifications via Amazon SNS by delegating an IAM Role in your AWS account to Knock, secured with an External ID: 1. Create a new AWS Role: - For "Trusted Entity Type" choose "AWS Account." - Select "Another AWS account" and put "496685847699" in the Account ID. - Check "Require external ID" and enter the ID of the SNS channel you created in your Knock dashboard. Configuring a new AWS role with an external ID
2. Attach the following permission policy to that role. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sns:AmazonSNSFullAccess"], "Resource": "*" } ] } ``` 3. Use that role's ARN when configuring your Amazon SNS channel in Knock.
Now that you have either an AWS User's credentials or an AWS IAM Role to delegate to Knock, you're ready to [configure your SNS channel](#channel-configuration) in the Knock dashboard under the **Channels and sources** page in your account settings.
## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Amazon SNS [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Amazon SNS channel. - **Knock token deregistration** (`boolean`) - Whether to enable Knock token deregistration. **Provider settings for Amazon SNS** - **Authentication scheme** (`enum*`) - The authentication scheme (Access Key or External ID) to use for your SNS channel. - **Access key ID** (`string*`) - The access key ID from your AWS account. Required when using Access Key authentication. - **Secret access key** (`string*`) - The secret access key from your AWS account. Required when using Access Key authentication. - **AWS IAM Role ARN to assume** (`string*`) - The ARN of the role in your AWS Account that this channel will use. Required when using External ID authentication. - **External ID** (`string*`) - The external ID for your AWS IAM Role. Required when using External ID authentication. When configured, the optional payload overrides set here will apply to all push notifications sent from this channel in the configured environment. Learn more about push channel overrides [here](/integrations/push/overview#push-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Using Amazon SNS with Knock In order to use Amazon SNS with Knock, you'll need the ARNs of the platform endpoints you created using your platform applications configured in SNS and the device tokens of your users. See Setting up an Amazon SNS platform endpoint for mobile notifications for more details on creating platform endpoints. Once you have an endpoint ARN, you can use the Knock SDK to [set the channel data](/managing-recipients/setting-channel-data) for your recipient, passing an array of endpoint ARNs as `target_arns`. We cannot currently track deliverability through SNS channels. This means that all notifications sent through SNS will show up as "Sent" in the Knock messages log, but not "Delivered". } /> ## Managing platform endpoints and device tokens By default, Knock makes no assumptions about managing your platform endpoints and corresponding device tokens. This means you are responsible for disabling a platform endpoint when a recipient opts out of notifications on a device or when their token expires. We recommend following Amazon's guidance on managing platform endpoints. However, Knock does provide an opt-in token deregistration feature to make managing endpoint ARNs easier. When this feature is enabled on an Amazon SNS channel and a message bounces due to a platform endpoint being disabled, Knock will automatically remove the ARN of that platform endpoint from the recipient's channel data. You can configure token deregistration on a per-environment basis in your channel's environment configurations. See our [token deregistration documentation](/integrations/push/token-deregistration) for more details on enabling and working with this feature. ## Data passed to Amazon SNS When sending a notification to Amazon SNS, we also pass through the following attributes: | Property | Type | Description | | ------------------ | ------ | ------------------------------------------------------ | | knock_message_id\* | string | The message ID of the corresponding Knock message | | data \* | string | Any key/value data passed through in your trigger call | ## Silent/background notifications We support sending Amazon SNS notifications as "silent", data-only notifications within Knock. You can enable this per push notification template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. When silent push is enabled, we'll no longer pass through the message payload, but all properties in the data payload described above will still be sent with your notification. When sending background notifications to APNs,{" "} Amazon SNS will automatically set the appropriate APNs header values . If you need more control, you can specify custom APNs headers by setting overrides on your channel step. } /> ## Using overrides to customize notifications We have full support for overriding the payload sent to Amazon SNS for adding things like badge counts, extra data properties, and sound files. You can configure payload overrides at two levels: - **Channel configuration.** Overrides set on the channel apply to all workflow steps that use the channel in the configured environment. - **Template settings.** Overrides can be set on a specific template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. Template-level overrides take precedence over channel-level overrides. Push overrides support Liquid for injecting `data` properties and referencing attributes on your recipients. ```json title="Setting overrides for FCM when sending via Amazon SNS" { "Message": { "GCM": { "fcmV1Message": { "message": { "android": { "notification": { "color": "#0000FF" } } } } } } } ``` By default, overrides are merged into the notification payload sent to Amazon SNS's `Publish` API. If you want to fully replace the payload rather than merge additional properties, you'll also need to set a replace `__strategy__`: ```json title="Setting overrides with the replace strategy" { "__strategy__": "replace", "Message": { "GCM": { "fcmV1Message": { "message": { "notification": { "title": "New email", "body": "New message from {{ actor.name }}" } } } } } } ``` When testing with a sandbox/development APNs environment, you'll need to include both `APNS` and `APNS_SANDBOX` keys in your overrides: ```json title="Setting overrides for APNs when sending via Amazon SNS" { "Message": { "APNS": { "aps": { "badge": 9, "sound": "bingbong.aiff" } }, "APNS_SANDBOX": { "aps": { "badge": 9, "sound": "bingbong.aiff" } } } } ``` If you wish to add custom APNs headers, you can do so by overriding the `MessageAttributes` property: ```json title="Setting custom APNs headers using payload overrides" { "MessageAttributes": { "entry": [ { "Name": "AWS.SNS.MOBILE.APNS.TOPIC", "Value": { "DataType": "String", "StringValue": "com.example.MyApp" } }, { "Name": "AWS.SNS.MOBILE.APNS.PUSH_TYPE", "Value": { "DataType": "String", "StringValue": "background" } }, { "Name": "AWS.SNS.MOBILE.APNS.PRIORITY", "Value": { "DataType": "String", "StringValue": "5" } } ] } } ``` See the Amazon SNS docs for a full list of valid message attributes. ## Channel data requirements In order to use a configured Amazon SNS channel, you must store a list of one or more platform endpoint ARNs for the user or the object that you wish to deliver a notification to. See Setting up an Amazon SNS platform endpoint for mobile notifications for more details on creating platform endpoints. Alternatively, you can store a list of `devices` objects when using [device metadata](/integrations/push/device-metadata). | Property | Type | Description | | ------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | target_arns\* | `string[]` | One or more platform endpoint ARNs associated with a platform application and a device token. Required when not using `devices`. | | devices\* | [`PushDevice[]`](/managing-recipients/setting-channel-data#the-pushdevice-object) | One or more device objects. Required when not using `target_arns`. | ## Apple (APNS) How to send iOS push notifications with Apple Push Notification service (APNs) and Knock. --- title: How to send push notifications to Apple Push Notifications service description: How to send iOS push notifications with Apple Push Notification service (APNs) and Knock. tags: ["apns", "ios", "push", "silent push"] section: Integrations layout: integrations --- This page walks through how to configure an Apple Push Notifications service (APNs) provider in Knock to send iOS push notifications. You'll need an APNs channel in your Knock dashboard to follow along. ## How to configure Apple Push Notifications service with Knock There are two ways to configure APNs with Knock. You can use a token-based authentication scheme or a certificate-based authentication scheme. Depending on which you choose, you'll need to get different information from Xcode and your Apple developer account. ### Token-based authentication configuration **Note**: Knock recommends token-based authentication for all APNs channel connections. You can read about how to set up a token connection to APNs in the documentation. For your Knock channel configuration, you will need: 1. A provider token signing key (a private key) 2. The key identifier (a 10 digit identifier from your Apple developer account) 3. The team identifier (a 10 digit identifier from your Apple developer account) ### Certificate-based authentication configuration You can read about how to set up a certificate connection to APNs in the documentation. For your Knock channel configuration, you will need: 1. A provider certificate (from your Apple developer account) 2. A private key (generated in the process above) Both of these values should be converted according to the instructions here prior to providing them to your Knock channel configuration. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your APNs [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your APNs channel. - **Knock token deregistration** (`boolean`) - Whether to enable Knock token deregistration. **Provider settings for APNs** - **Application mode** (`enum*`) - Whether to use the Production or Sandbox APNs environment for your APNs channel. Defaults to Production. - **Bundle ID** (`string*`) - The unique bundle ID of your iOS application. - **Authentication scheme** (`enum*`) - The authentication scheme (Token or Certificate) to use for your APNs channel. - **Provider token signing key** (`string*`) - The provider token signing key from your Apple developer account. Required when using Token authentication. - **Provider certificate** (`string*`) - The provider certificate from your Apple developer account. Required when using Certificate authentication. - **Private cryptographic key** (`string*`) - The private key from your Apple developer account. Required when using Certificate authentication. - **Key identifier** (`string*`) - The 10-character key identifier from your Apple developer account. Required when using Token authentication. - **Team identifier** (`string*`) - The 10-character Team ID from your Apple developer account. Required when using Token authentication. When configured, the optional payload overrides set here will apply to all push notifications sent from this channel in the configured environment. Learn more about push channel overrides [here](/integrations/push/overview#push-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Using APNs with Knock In order to use APNs with Knock you'll need to synchronize your users device tokens retrieved from the APNs SDK to Knock by [setting channel data](/managing-recipients/setting-channel-data) for your recipient. You can follow the quickstart documentation on APNs to see how to get the device token. ## Managing tokens By default, Knock makes no assumptions about managing your device tokens. This means you are responsible for removing tokens when a recipient opts out of notifications on a device or when their token expires. However, Knock does provide an opt-in token deregistration feature that automatically removes invalid tokens from a recipient's channel data when a message bounces. When enabled, Knock will automatically remove invalid or expired tokens upon receiving a bounce event from the provider. You can configure token deregistration on a per-environment basis in your channel's environment configurations. See our [token deregistration documentation](/integrations/push/token-deregistration) for more details on enabling and working with this feature. ## Data passed to APNs When sending a notification to APNs, we also pass through the following attributes: | Property | Type | Description | | ------------------ | ------ | ------------------------------------------------------ | | knock_message_id\* | string | The message ID of the corresponding Knock message | | data \* | string | Any key/value data passed through in your trigger call | ## Silent/background notifications We support sending APNs notifications as "silent", data-only notifications within Knock. You can enable this per push notification template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. When silent push is enabled, we'll no longer pass through the content payload and your message will be sent with the `content-available: 1` flag as expected by APNs. All properties in the data payload described above will be sent with your notification. ## Using overrides to customize notifications We have full support for overriding the payload sent to APNs for adding things like badge counts, extra data properties, and sound files. You can configure payload overrides at two levels: - **Channel configuration.** Overrides set on the channel apply to all workflow steps that use the channel in the configured environment. - **Template settings.** Overrides can be set on a specific template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. Template-level overrides take precedence over channel-level overrides. Push overrides support Liquid for injecting `data` properties and referencing attributes on your recipients. Overrides are merged into the notification payload sent to APNs. See the APNs documentation for more details. | Property | Type | Description | | -------- | ---------- | -------------------------------------------------------------------------------------------------------------- | | headers | dictionary | APNs specific headers (`apns-priority`, `apns-expiration`, `apns-push-type`, `apns-topic`, `apns-collapse-id`) | | aps | dictionary | Overrides to send to the push payload (`sound`, `alert`, `badge`, `thread-id`) | | any | any | Any other key values to send as part of the push message | ## Channel data requirements In order to use a configured APNs channel you must store a list of one or more device tokens for the user or the object that you wish to deliver a notification to. You can retrieve a device token by following the tutorial in the Apple developer documentation. Alternatively, you can store a list of `devices` objects when using [device metadata](/integrations/push/device-metadata). | Property | Type | Description | | --------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------- | | tokens\* | `string[]` | One or more device tokens. Required when not using `devices`. | | devices\* | [`PushDevice[]`](/managing-recipients/setting-channel-data#the-pushdevice-object) | One or more device objects. Required when not using `tokens`. | ## Expo (React Native) How to send mobile push notifications with Expo and Knock. --- title: How to send push notifications using Expo description: How to send mobile push notifications with Expo and Knock. tags: ["react native", "ios", "android", "push"] section: Integrations layout: integrations --- This page walks through how to configure an Expo provider in Knock to send push notifications. This documentation assumes that you've already created an Expo channel in the Knock dashboard and that your React Native application is already setup to support Push notifications. If you're new to setting up push in your Expo enabled React Native project, you can follow Expo's push notification overview. ## How to configure Expo with Knock To configure Expo with Knock, you'll need your Expo project name (sometimes referred to as an `experience_id`) and if you've enabled enhanced push security, you'll also need an auth token. You can read more about Enhanced Push Security in the Expo docs. You can get both of these by logging into the Expo console. Once you have them, go back to the environment configuration for your Expo channel, complete the configuration, and you're good to go. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Expo [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Expo channel. - **Knock token deregistration** (`boolean`) - Whether to enable Knock token deregistration. **Provider settings for Expo** - **Expo project name** (`string*`) - The unique project name of your Expo project. Sometimes referred to as an `experience_id`. - **Auth token** (`string`) - The auth token for your Expo project. Required when enhanced push security is enabled in Expo. When configured, the optional payload overrides set here will apply to all push notifications sent from this channel in the configured environment. Learn more about push channel overrides [here](/integrations/push/overview#push-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Using Expo with Knock In order to use Expo with Knock you'll need to synchronize your users device tokens retrieved from the Expo SDK in either Android, iOS, or on the Web to Knock by [setting channel data](/managing-recipients/setting-channel-data) for your recipient. You can follow the appropriate quickstart tutorial for your platform on Expo to see how to get the device token. ## Managing tokens By default, Knock makes no assumptions about managing your device tokens. This means you are responsible for removing tokens when a recipient opts out of notifications on a device or when their token expires. However, Knock does provide an opt-in token deregistration feature that automatically removes invalid tokens from a recipient's channel data when a message bounces. When enabled, Knock will automatically remove invalid or expired tokens upon receiving a bounce event from the provider. You can configure token deregistration on a per-environment basis in your channel's environment configurations. See our [token deregistration documentation](/integrations/push/token-deregistration) for more details on enabling and working with this feature. ## Data passed to Expo When sending a notification to Expo, we also pass through the following attributes: | Property | Type | Description | | ------------------ | ------ | ------------------------------------------------------ | | knock_message_id\* | string | The message ID of the corresponding Knock message | | data \* | string | Any key/value data passed through in your trigger call | ## Silent/background notifications We support sending Expo notifications as "silent," data-only notifications within Knock. You can enable this per push notification template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. When silent push is enabled, we'll no longer pass through the content payload and your message will be sent with the `_contentAvailable: true` flag as expected by Expo. All properties in the data payload described above will be sent with your notification. ## Using overrides to customize notifications We have full support for overriding the payload sent to Expo for adding things like badge counts, extra data properties, and sound files. You can configure payload overrides at two levels: - **Channel configuration.** Overrides set on the channel apply to all workflow steps that use the channel in the configured environment. - **Template settings.** Overrides can be set on a specific template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. Template-level overrides take precedence over channel-level overrides. Push overrides support Liquid for injecting `data` properties and referencing attributes on your recipients. Overrides are merged into the notification payload sent to Expo. See the Expo documentation for more details. ### Accessing custom data client-side with `expo-notifications` When using the `expo-notifications` SDK, the device's platform determines where custom payload data lands in the notification object. Your Knock payload overrides should account for both iOS and Android to ensure that the data is accessible under the same `notification.request.content.data` key on both platforms. - **iOS (APNs):** Nest custom data under the `body` key. The `expo-notifications` SDK maps `request.trigger.payload.body` properties to `request.content.data` in the notification object received by the Expo iOS event listeners. - **Android (FCM):** Nest custom data under the `data` key. Note that FCM requires `data` to be a flat dictionary with string values. You can include both keys in the same override; each platform will use its own key and ignore the other. ```json title="A payload override to send a custom URL property to both iOS and Android" { "body": { "url": "{{ data.url }}" }, "data": { "url": "{{ data.url }}" } } ``` With the above override, `request.content.data.url` will be accessible on both platforms. ## Channel data requirements In order to use a configured Expo channel you must store a list of one or more device tokens for the user or the object that you wish to deliver a notification to. You can retrieve a device token by following the tutorial in the Expo developer documentation. Alternatively, you can store a list of `devices` objects when using [device metadata](/integrations/push/device-metadata). | Property | Type | Description | | --------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------- | | tokens\* | `string[]` | One or more device tokens. Required when not using `devices`. | | devices\* | [`PushDevice[]`](/managing-recipients/setting-channel-data#the-pushdevice-object) | One or more device objects. Required when not using `tokens`. | ## Firebase (FCM) How to send push notifications with Firebase Cloud Messaging (FCM) and Knock. --- title: How to send push notifications to Firebase Cloud Messaging description: How to send push notifications with Firebase Cloud Messaging (FCM) and Knock. tags: ["android", "fcm", "push", "silent push", "web push", "browser push"] section: Integrations layout: integrations --- This page walks through how to configure a Firebase Cloud Messaging (FCM) provider in Knock to send mobile or web push notifications. You'll need an FCM channel in your Knock dashboard to follow along. ## How to configure FCM with Knock To configure FCM with Knock, you'll need your Firebase Project ID and the complete contents of your Service Account JSON file. You can get both of these by logging into the Firebase console and navigating to project settings. Once you have them, go back to the environment configuration for your FCM channel, enter in your Project ID and Service Account JSON file, and you're good to go. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your FCM [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your FCM channel. - **Knock token deregistration** (`boolean`) - Whether to enable Knock token deregistration. **Provider settings for FCM** - **Project ID** (`string*`) - The unique project ID of your Firebase project from FCM. - **Service account JSON** (`string*`) - The complete contents of your Service Account JSON file downloaded from FCM. When configured, the optional payload overrides set here will apply to all push notifications sent from this channel in the configured environment. Learn more about push channel overrides [here](/integrations/push/overview#push-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ```json title="Example service account JSON" { "type": "service_account", "project_id": "XXX", "private_key_id": "XXX", "private_key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----\n", "client_email": "XXX@XXX.com", "client_id": "XXX", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/XXX" } ``` ## Using FCM with Knock In order to use FCM with Knock you'll need to synchronize your users device tokens retrieved from the FCM SDK in either Android, iOS, or Web to Knock by [setting channel data](/managing-recipients/setting-channel-data) for your recipient. You can follow the appropriate quickstart tutorial for either iOS, Android, or Web on FCM to see how to get the device token. Once you have the device token, you can use the Knock SDK to set the channel data for your recipient. In the case of web push, you can send the token to your app server and use the corresponding Knock SDK to set the channel data: ## Managing tokens By default, Knock makes no assumptions about managing your device tokens. This means you are responsible for removing tokens when a recipient opts out of notifications on a device or when their token expires. However, Knock does provide an opt-in token deregistration feature that automatically removes invalid tokens from a recipient's channel data when a message bounces. When enabled, Knock will automatically remove invalid or expired tokens upon receiving a bounce event from the provider. You can configure token deregistration on a per-environment basis in your channel's environment configurations. See our [token deregistration documentation](/integrations/push/token-deregistration) for more details on enabling and working with this feature. ## Data passed to FCM When sending a notification to FCM, we also pass through the following attributes: | Property | Type | Description | | ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | knock_message_id\* | string | The message ID of the corresponding Knock message | | data\* | string | Any key/value data passed through in your trigger call, formatted for FCM (all nested objects are flattened, and all values are converted to strings) | The following example illustrates how Knock will format your trigger data for FCM: ```json { "data": { "foo": { "bar": [21, 13, 1], "baz": false }, "biz": "abc" } } ``` becomes ```json { "data" { "foo.bar.0": "21", "foo.bar.1": "13", "foo.bar.2": "1", "foo.baz": "false", "biz": "abc" } } ``` ## Silent/data notifications We support sending FCM notifications as silent, data-only notifications within Knock. You can enable this per push notification template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. When silent push is enabled, we'll no longer pass through the message payload. All properties in the data payload described above will be sent with your notification still. ## Using overrides to customize notifications We have full support for overriding the payload sent to FCM for adding things like badge counts, extra data properties, and sound files. You can configure payload overrides at two levels: - **Channel configuration.** Overrides set on the channel apply to all workflow steps that use the channel in the configured environment. - **Template settings.** Overrides can be set on a specific template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. Template-level overrides take precedence over channel-level overrides. Push overrides support Liquid for injecting `data` properties and referencing attributes on your recipients. By default, payload overrides will be merged into the base `data` and will not replace other trigger `data` being passed to FCM. The override also needs to match the format that is expected by FCM, meaning that any custom key-value pairs should be contained inside of a `data` dictionary: ```json { "data": { "foo": "bar", "baz": true } } ``` If you want to fully replace the trigger data with your override rather than merge additional properties, you'll also need to set a replace `__strategy__`: ```json { "__strategy__": "replace", "data": { "foo": "bar", "baz": true } } ``` See the FCM documentation for details. | Property | Type | Description | | ------------ | ---------- | ---------------------------- | | apns | dictionary | APNs specific overrides | | data | dictionary | Extra data properties to add | | android | dictionary | Android specific overrides | | fcm_options | dictionary | FCM specific options | | notification | dictionary | Notification overrides | | webpush | dictionary | Webpush specific overrides | ## Common FCM errors The following are common FCM errors you may see in your message delivery logs: | Error | Meaning / action to take | | ------------------ | ---------------------------------------------------------------------------------------------- | | `UNREGISTERED` | The device token provided is not valid and should be removed from the recipients channel data. | | `INVALID_ARGUMENT` | The device token given may be incorrect. | ## Channel data requirements In order to use a configured FCM channel you must store a list of one or more device tokens for the user or the object that you wish to deliver a notification to. If you use multiple device tokens for a single user or object, Knock will generate and try to deliver a notification for each unique token. Alternatively, you can store a list of `devices` objects when using [device metadata](/integrations/push/device-metadata). | Property | Type | Description | | --------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------- | | tokens\* | `string[]` | One or more device tokens. Required when not using `devices`. | | devices\* | [`PushDevice[]`](/managing-recipients/setting-channel-data#the-pushdevice-object) | One or more device objects. Required when not using `tokens`. | ## OneSignal How to send mobile push notifications with OneSignal and Knock. --- title: How to send push notifications using OneSignal description: How to send mobile push notifications with OneSignal and Knock. tags: ["react native", "ios", "android", "push"] section: Integrations layout: integrations --- This page walks through how to configure a OneSignal provider in Knock to send push notifications. You'll need a OneSignal channel in your Knock dashboard to follow along. Your OneSignal channel expects that you are using Knock to author Push notification templates. Those templates are then passed to OneSignal as content. ## How to configure OneSignal Push with Knock This documentation assumes that you have already set up OneSignal with push certificates and everything needed in order to start sending push notifications. You should also have already integrated the OneSignal SDK within your application. } /> To set up OneSignal Push with Knock you will need: - Your OneSignal App ID - Your OneSignal API Key for sending notifications - To select a mode of operation between using `external_ids` (recommended) or `player_ids` (deprecated by OneSignal). ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your OneSignal [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your OneSignal channel. - **Knock token deregistration** (`boolean`) - Whether to enable Knock token deregistration. **Provider settings for OneSignal** - **App ID** (`string*`) - The unique app ID of your OneSignal project. - **API Key** (`string*`) - The private API key from OneSignal. - **Recipient mode** (`enum*`) - The mode used to target recipients. One of External ID or Player ID. When configured, the optional payload overrides set here will apply to all push notifications sent from this channel in the configured environment. Learn more about push channel overrides [here](/integrations/push/overview#push-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Using OneSignal Push with Knock ### Using `external_id` (recommended) If you're using an External ID to reference your OneSignal users within Knock, you do not need to supply any additional data to initiate a request. We will automatically pass your Knock user ID to OneSignal as the `external_id`. ### Using `player_id` When using Player IDs you will need to store the `player_id` for a user within Knock as ChannelData for the OneSignal Push channel. This ensures that Knock has the correct value to pass to send a notification to a recipient when a workflow is triggered. You can retrieve the `player_id` from OneSignal once a user has been registered, either by using the OneSignal mobile SDK, or by using a webhook from OneSignal. ## Data passed to OneSignal When sending a push notification to OneSignal, we will automatically pass the following into the attachments data: | Property | Type | Description | | ------------------ | ------ | ------------------------------------------------------ | | knock_message_id\* | string | The message ID of the corresponding Knock message | | data \* | string | Any key/value data passed through in your trigger call | ## Silent/background notifications When selecting to send as a silent/background notification, Knock will passthrough the content_available=true option to OneSignal. You can enable this per push notification template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. ## Using overrides to customize notifications We have full support for overriding the payload sent to OneSignal for adding things like badge counts, extra data properties, and sound files. You can configure payload overrides at two levels: - **Channel configuration.** Overrides set on the channel apply to all workflow steps that use the channel in the configured environment. - **Template settings.** Overrides can be set on a specific template by clicking the gear icon (⚙️) at the top of the template editor to open the template settings modal. Template-level overrides take precedence over channel-level overrides. Push overrides support Liquid for injecting `data` properties and referencing attributes on your recipients. Overrides are merged into the notification payload sent to OneSignal. See the OneSignal documentation for more details. Knock uses the "Aliases" targeting strategy to send push notifications to specific users via External ID. ## Managing tokens By default, Knock makes no assumptions about managing your device tokens. This means you are responsible for removing tokens when a recipient opts out of notifications on a device or when their token expires. When using `player_id` mode, Knock provides an opt-in token deregistration feature that automatically removes invalid tokens from a recipient's channel data when a message bounces. This feature is not available when using `external_id` mode since Knock doesn't directly manage any tokens in this case. You can configure token deregistration on a per-environment basis in your channel's environment configurations. See our [token deregistration documentation](/integrations/push/token-deregistration) for more details. ## Channel data requirements When your OneSignal push channel is configured to use `player_ids` you must supply ChannelData per-recipient that contains one or more `player_ids` for a user. ## Frequently asked questions No, you can currently only use one or the other. Currently this is not supported but we'd love to hear a use case you have for it! Yes, you can use push overrides to override the JSON payload and specify the template_id for OneSignal instead. No, only Push is currently supported. # SMS ## Overview Learn how to send transactional SMS notifications with Knock. --- title: SMS notifications with Knock description: Learn how to send transactional SMS notifications with Knock. section: Integrations > SMS layout: integrations --- Effortlessly design and deliver SMS notifications to downstream providers. Let Knock manage the delivery for you. ## Features - **Easy templating**: it's easy to create and maintain SMS notification templates in Knock's editor. - **Delivery tracking**: track the delivery status of your SMS messages with webhook-based status updates from supported providers. - **Link tracking**: capture link-click events right within your Knock account. For more details, see the [Knock link tracking documentation](/send-notifications/tracking). ## Supported providers - [Africa's Talking](/integrations/sms/africas-talking) - [Amazon SNS](/integrations/sms/aws-sns) - [Mailersend](/integrations/sms/mailersend) - [MessageBird](/integrations/sms/messagebird) - [Plivo](/integrations/sms/plivo) - [Sinch](/integrations/sms/sinch) - [Sinch MessageMedia](/integrations/sms/sinch-message-media) - [Telnyx](/integrations/sms/telnyx) - [Twilio](/integrations/sms/twilio) - [Vonage](/integrations/sms/vonage) If you want us to add a new provider to this list, please let us know through the feedback button at the top of this page. ## Settings and overrides Learn more about how to configure your SMS messages in Knock. --- title: SMS settings and overrides description: Learn more about how to configure your SMS messages in Knock. section: Integrations layout: integrations --- ## Overriding the default `to` number By default Knock will send your SMS messages to the `phone_number` property stored on the `recipient` for the workflow run. If you need to override this, you can do so by setting the `to` field in the SMS template settings. As an example, if you wanted to send an SMS to a single number, you could set the `to` field to either a static value (like `+1234567890`) or a dynamic value (like `{{ data.phone_number_to_override }}`). ## Provider JSON overrides Sometimes you may want to customize the API call Knock sends to your SMS provider. A good example of this is passing custom arguments as part of the API payload, or using another feature of the provider that Knock doesn't support out of the box. You can configure payload overrides at two levels: - **Channel configuration.** When you configure payload overrides on an SMS channel, these overrides apply to all workflow steps that use that channel in the configured environment. You can set this by navigating to **Channels and sources** in your dashboard account settings, selecting your SMS channel, then clicking "Manage configuration" under the environment you want to configure. - **Template settings.** You can also configure overrides at the template level by clicking the gear icon (⚙️) at the top of the template editor to access the template settings modal. Note: If payload overrides are set at both the channel and template level, the template-level overrides will take precedence and replace the channel-level overrides. } /> ## Africa's Talking Get started sending SMS notifications with Africa's Talking and Knock. --- title: How to send SMS messages with Africa's Talking description: Get started sending SMS notifications with Africa's Talking and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Africa's Talking to send SMS notifications to your recipients. ## Features - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Africa's Talking channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Africa's Talking [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Africa's Talking channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Africa's Talking** - **API key** (`string*`) - The API key from Africa's Talking. - **Username** (`string*`) - The application username from Africa's Talking. - **Short code** (`string`) - The short code to send messages from. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Amazon SNS Get started sending SMS notifications with Amazon SNS and Knock. --- title: How to send SMS messages with Amazon SNS description: Get started sending SMS notifications with Amazon SNS and Knock. tags: ["amazon", "aws", "sns", "sms"] section: Integrations > SMS layout: integrations --- Knock integrates with Amazon Simple Notification Service (Amazon SNS) to send SMS notifications to your recipients. ## Features - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Amazon SNS channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to take some steps in AWS before you can configure your SNS channel within Knock. Knock supports two authentication schemes with Amazon SNS: To send notifications via Amazon SNS using an IAM User, Knock requires the **access key ID** and a **secret access key** of an AWS user with SNS send permissions (you can use the `sns:AmazonSNSFullAccess` permission for this). If you don't already have a user with send permissions, you can create an IAM user in AWS to use with the Knock API. You can learn more about creating IAM users in AWS here. Once you've created your new IAM user, you'll need to provision them with the policy below. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sns:AmazonSNSFullAccess"], "Resource": "*" } ] } ``` Now that you have an AWS user created and provisioned with SNS send access, grab the **access key ID** and a **secret access key** of the user—we'll use these later when configuring the SNS channel within Knock. To send notifications via Amazon SNS by delegating an IAM Role in your AWS account to Knock, secured with an External ID: 1. Create a new AWS Role: - For "Trusted Entity Type" choose "AWS Account." - Select "Another AWS account" and put "496685847699" in the Account ID. - Check "Require external ID" and enter the ID of the SNS channel you created in your Knock dashboard. Configuring a new AWS role with an external ID
2. Attach the following permission policy to that role. ```json title="IAM user policy" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sns:AmazonSNSFullAccess"], "Resource": "*" } ] } ``` 3. Use that role's ARN when configuring your Amazon SNS channel in Knock.
Now that you have either an AWS User's credentials or an AWS IAM Role to delegate to Knock, you're ready to [configure your SNS channel](#channel-configuration) in the Knock dashboard under the **Channels and sources** page in your account settings.
Here are a few other things to keep in mind once you have your SNS channel configured in Knock: - **SNS sandbox mode.** By default, AWS places all new accounts in the SNS sandbox. While your account is in the sandbox, you can only send messages to verified destination phone numbers—keep this in mind if you're testing in development before you've moved your account out of the SNS sandbox. For more information on the SNS sandbox and how to move your account out of it, see the SNS sandbox documentation. - **Deliverability tracking.** We cannot currently track deliverability through SNS channels. This means that all notifications sent through SNS will show up as "Sent" in the Knock messages log, but not "Delivered". ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Amazon SNS [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Amazon SNS channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Amazon SNS** - **AWS region** (`enum*`) - The region your AWS account is in. - **Authentication scheme** (`enum*`) - The authentication scheme (Access Key or External ID) to use for your SNS channel. - **Access key ID** (`string*`) - The access key ID from your AWS account. Required when using Access Key authentication. - **Secret access key** (`string*`) - The secret access key from your AWS account. Required when using Access Key authentication. - **AWS IAM Role ARN to assume** (`string*`) - The ARN of the role in your AWS Account that this channel will use. Required when using External ID authentication. - **External ID** (`string*`) - The external ID for your AWS IAM Role. Required when using External ID authentication. - **Message Type** (`enum`) - The message type of your SMS (Promotional or Transactional). - **Sender ID** (`string`) - The Amazon SNS Sender ID to send messages from. - **Originator number** (`enum`) - The originator number type (Phone number or Short code) to send messages from. - **Phone number** (`string*`) - The phone number to send SMS messages from. Required when Originator number is set to Phone number. - **Short code** (`string*`) - The Amazon SNS short code to send SMS messages from. Required when Originator number is set to Short code. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## MailerSend Get started sending SMS notifications with MailerSend and Knock. --- title: How to send SMS messages with MailerSend description: Get started sending SMS notifications with MailerSend and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with MailerSend to send SMS notifications to your recipients. ## Features - Delivery tracking - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new MailerSend channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your MailerSend [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your MailerSend channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for MailerSend** - **API key** (`string*`) - The API key from MailerSend. Must have SMS sending permission. - **Phone number** (`string*`) - The phone number to send messages from. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## MessageBird Get started sending SMS notifications with MessageBird and Knock. --- title: How to send SMS messages with MessageBird description: Get started sending SMS notifications with MessageBird and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with MessageBird to send SMS notifications to your recipients. On February 1, 2024,{" "} MessageBird announced a rebrand as Bird , along with the introduction of a Bird CRM product and new APIs for sending messages.

This integration is with the legacy MessageBird SMS API , which continues to be supported by Bird but is no longer accepting new customers. If integrating with the new Bird API is a blocker to your Knock integration, please reach out to support@knock.app to let us know. } /> ## Features - Delivery tracking - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new MessageBird channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your MessageBird [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your MessageBird channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for MessageBird** - **API key** (`string*`) - The API key from MessageBird. You can find this under Developers > API access. - **From** (`enum*`) - The method used to send your SMS messages. One of Phone number, Short code, or Sender ID. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The MessageBird short code to send messages from. Required when From is set to Short code. - **Sender ID** (`string*`) - The MessageBird Sender ID to send messages from. Required when From is set to Sender ID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Plivo Get started sending SMS notifications with Plivo and Knock. --- title: How to send SMS messages with Plivo description: Get started sending SMS notifications with Plivo and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Plivo to send SMS notifications to your recipients. ## Features - Delivery tracking - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Plivo channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to take the following steps in Plivo before you can configure your channel within Knock. Sign up for a Plivo account if you haven't already. After doing this you will gain access to your dashboard where you can find your **Auth ID** and **Auth Token**. You can add a sandbox number for testing, or you can buy a Plivo phone number to use as the from field on your Knock channel. Now that you have your **verified phone number**, **Auth ID** and **Auth Token**, you're ready to configure your Plivo channel in the Knock dashboard under the **Channels and sources** page in your account settings. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Plivo [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Plivo channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Plivo** - **Authentication ID** (`string*`) - The authentication ID from Plivo. - **Authentication token** (`string*`) - The authentication token from Plivo. - **From** (`enum*`) - The method used to send your SMS messages. One of Phone number, Short code, or Sender ID. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The Plivo short code to send messages from. Required when From is set to Short code. - **Sender ID** (`string*`) - The Plivo Sender ID to send messages from. Required when From is set to Sender ID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Sinch Get started sending SMS notifications with Sinch and Knock. --- title: How to send SMS messages with Sinch description: Get started sending SMS notifications with Sinch and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Sinch to send SMS notifications to your recipients. ## Features - Per environment configuration - Delivery tracking - Knock link tracking - Sandbox mode ## Getting started You can create a new Sinch channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Sinch [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Sinch channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Sinch** - **API token** (`string*`) - The API token from Sinch. - **Service Plan ID** (`string*`) - The Service Plan ID from Sinch. - **Region** (`enum*`) - The region of your domain. One of AU, BR, CA, EU, US. - **From** (`enum`) - The method used to send your SMS messages. One of Phone number, Short code, or Sender ID. Optional if you have an automatic originator number on your Sinch account. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The Sinch short code to send messages from. Required when From is set to Short code. - **Sender ID** (`string*`) - The Sinch Sender ID to send messages from. Required when From is set to Sender ID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Sinch MessageMedia Get started sending SMS notifications with Sinch MessageMedia and Knock. --- title: How to send SMS messages with Sinch MessageMedia description: Get started sending SMS notifications with Sinch MessageMedia and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Sinch MessageMedia to send SMS notifications to your recipients. ## Features - Per environment configuration - Delivery tracking - Knock link tracking - Sandbox mode ## Getting started You can create a new Sinch MessageMedia channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Sinch MessageMedia [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Sinch MessageMedia channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Sinch MessageMedia** - **API key** (`string*`) - The API key from Sinch MessageMedia. - **API secret** (`string*`) - The API secret from Sinch MessageMedia. - **Source number** (`string*`) - The source number to send messages from. - **Source number type** (`enum`) - The optional type of the source number. One of Alpha (alphanumeric), International, or ShortCode. Will be inferred if not provided. - **Region** (`enum*`) - The region of your account. One of APAC, EU. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Telnyx Get started sending SMS notifications with Telnyx and Knock. --- title: How to send SMS messages with Telnyx description: Get started sending SMS notifications with Telnyx and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Telnyx to send SMS notifications to your recipients. ## Features - Delivery tracking - Knock link tracking - Per environment configuration - Sandbox mode - Number Pool messaging ## Getting started You can create a new Telnyx channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Telnyx [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Telnyx channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Telnyx** - **API key** (`string*`) - The API key from Telnyx. - **From** (`enum*`) - The method used to send your SMS messages. One of Phone number, Short code, Sender ID, or Messaging Profile ID. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The Telnyx short code to send messages from. Required when From is set to Short code. - **Sender ID** (`string*`) - The Telnyx Sender ID to send messages from. Required when From is set to Sender ID. - **Messaging Profile ID** (`string*`) - The Telnyx Messaging Profile ID to use for Number Pool messaging. Required when From is set to Messaging Profile ID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Twilio Get started sending SMS notifications with Twilio and Knock. --- title: How to send SMS messages with Twilio description: Get started sending SMS notifications with Twilio and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Twilio to send SMS notifications to your recipients. ## Features - Delivery tracking - Bounce Support - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Twilio channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to take some steps in Twilio before you can configure your Twilio channel within Knock. Sign up for a Twilio account if you haven't already. Get your first SMS-enabled phone number in Twilio. You'll use this as the "From" phone number in your channel configuration within Knock. (We also support Twilio short codes and messaging services.) If your Twilio account is in trial mode, you'll need to pre-verify any phone numbers that you plan to send SMS messages to during testing with Knock. Now that you have your **Twilio phone number**, **account ID** and **auth token**, you're ready to configure your Twilio channel in the Knock dashboard under the **Channels and sources** page in your account settings. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Twilio [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Twilio channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Twilio** - **Account ID** (`string*`) - The account ID from Twilio. - **Auth token** (`string*`) - The auth token from Twilio. - **From** (`enum*`) - The method used to send your SMS messages. One of Phone number, Short code, or Messaging Service SID. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The short code to send messages from. Required when From is set to Short code. - **Messaging Service SID** (`string*`) - The Messaging Service SID to send messages from. Required when From is set to Messaging Service SID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. ## Delivery status webhooks When enabled, Twilio will send delivery status updates directly to Knock via webhooks, allowing you to track the full lifecycle of your SMS messages in real-time. ### Prerequisites Before enabling delivery status webhooks, you need: 1. A Twilio account with an SMS-enabled phone number, short code, or messaging service 2. A Twilio channel configured in Knock (see the [getting started](#getting-started) section above) 3. Access to your Twilio phone number or messaging service configuration ### Setting up delivery status webhooks 1. Navigate to **Channels and sources** in your Knock dashboard 2. Select your Twilio channel 3. Click "Manage configuration" for the environment you want to configure 4. Scroll to the "Incoming message status updates" section and enable incoming webhooks 5. Copy the generated webhook URL - you'll need this in the next step The setup process depends on whether you're using a phone number/short code or a messaging service: **For phone numbers or short codes:** 1. Go to the Phone Numbers page in your Twilio Console 2. Select the phone number or short code you're using with Knock 3. Scroll to the "Messaging" section 4. Under "Status Callback URL", paste the webhook URL from Knock 5. Click "Save" to apply the changes **For messaging services:** 1. Go to the Messaging Services page in your Twilio Console 2. Select the messaging service you're using with Knock 3. Go to the "Integration" settings 4. Under "Status Callback URL", paste the webhook URL from Knock 5. Click "Save" to apply the changes ### Supported delivery statuses When delivery status webhooks are enabled for Twilio, Knock will update message statuses based on these Twilio status callback events: | Twilio Status | Knock Status | Description | | ------------- | ------------- | --------------------------------------------------------------- | | delivered | `delivered` | The SMS was successfully delivered to the recipient's device | | failed | `bounced` | The SMS failed due to invalid phone number or carrier rejection | | undelivered | `undelivered` | The SMS could not be delivered due to a temporary error | ### Troubleshooting If delivery status updates aren't appearing in Knock: 1. **Check status callback URL.** Verify the Status Callback URL is correctly configured in your Twilio phone number or messaging service settings. 2. **Verify phone number format.** Ensure recipients have valid phone numbers in E.164 format (e.g., +1234567890). 3. **Check trial account limits.** If using a trial account, verify the recipient phone number has been verified in Twilio. 4. **Review Twilio logs.** Check the Messaging Logs in Twilio Console to see if messages are being sent and if status callbacks are being triggered. 5. **Test with a real number.** Send a test SMS to a real phone number to verify the full delivery flow. If you're having trouble setting up delivery status webhooks, contact our support team at support@knock.app. } /> ## Vonage Get started sending SMS notifications with Vonage and Knock. --- title: How to send SMS messages with Vonage description: Get started sending SMS notifications with Vonage and Knock. section: Integrations > SMS layout: integrations --- Knock integrates with Vonage to send SMS notifications to your recipients. ## Features - Knock link tracking - Per environment configuration - Sandbox mode ## Getting started You can create a new Vonage channel in the dashboard under the **Channels and sources** page in your account settings. From there, you'll need to configure the channel for each environment you have. ## Channel configuration The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Vonage [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. Fields marked with an `*` are required. **Knock settings** - **Sandbox mode** (`boolean`) - Whether to enable sandbox mode for your Vonage channel. - **Knock link tracking** (`boolean`) - Whether to enable Knock link-click tracking. **Provider settings for Vonage** - **API key** (`string*`) - The API key from Vonage. - **API secret** (`string*`) - The API secret from Vonage. - **From** (`enum*`) - The method used to send your SMS messages. One of Phone number, Short code, or Sender ID. - **Phone number** (`string*`) - The phone number to send messages from. Required when From is set to Phone number. - **Short code** (`string*`) - The short code to send messages from. Required when From is set to Short code. - **Sender ID** (`string*`) - The Sender ID to send messages from. Required when From is set to Sender ID. When configured, the optional payload overrides set here will apply to all SMS notifications sent from this channel in the configured environment. Learn more about SMS channel overrides [here](/integrations/sms/settings-and-overrides#provider-json-overrides). - **Payload overrides** (`JSON (string) | liquid`) - Provide a JSON object to merge into the API payload that is sent to the downstream provider. Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. ## Recipient data requirements In order to send an SMS notification you'll need a valid `phone_number` property set on your recipient. # Webhook ## Overview Learn more about how to use Knock webhook channels to send to custom destinations, build reusable fetch steps, and to power customer-facing webhooks within your own product. --- title: Webhook channel overview description: Learn more about how to use Knock webhook channels to send to custom destinations, build reusable fetch steps, and to power customer-facing webhooks within your own product. tags: ["webhook", "custom channel"] section: Integrations > Webhook layout: integrations --- Learn more about how to use Knock webhook channels to send to custom destinations, build reusable fetch steps, and to power customer-facing webhooks within your own product. ## Features and use cases You can use the Knock webhook channel type to build a custom channel that sends a webhook request to a configured endpoint. This endpoint can be static, or can be dynamically built using liquid variables during workflow run time. The Knock webhook channel supports GET, POST, PUT, DELETE, and PATCH requests, making it a flexible tool to use for a number of different use cases. You might use the Knock webhook channel to... - Configure a custom channel (examples: PagerDuty, a proprietary in-house service) that you want to send a request to as part of your Knock workflow - Codify commonly used fetch requests (such as fetching information about a user) for use across your different Knock workflows - Build user-facing, configurable webhooks into your own product so your users can receive a webhook when something happens in your product In this overview, we'll cover how to configure webhook channels in Knock and use them in your notification workflows. ## Create and configure your webhook channel To create your webhook channel, go to the Knock dashboard and navigate to **Channels and sources** in your account settings. Click "Create channel," select the Webhook channel type, and click "Next." Provide a name, key, and description for your webhook channel. the name and description provided will be used in the workflow builder on steps that use this webhook channel, so be as descriptive as possible so other members of your account know how to use your webhook channel. } /> Once your webhook channel has been created, you'll be able to manage its configuration on a per-environment basis. As with all Knock channels, webhooks can be used in [sandbox mode](/integrations/overview#sandbox-mode) and can be used with [channel conditions](/integrations/overview#channel-conditions). You'll also need to build the actual webhook request that you want your webhook channel to send when it's triggered within a Knock workflow. We cover this topic in the next section. ## Build your webhook request To start building your webhook request, navigate to your webhook channel and click "Edit webhook." You'll now be looking at the webhook channel configuration page. You build a webhook request the same way you build a [fetch function](/designing-workflows/fetch-function) request: you define the endpoint, method, headers, params, and body payload, and you can use liquid in each of those fields. To learn how to build a webhook request in detail, you can read [our fetch function documentation](/designing-workflows/fetch-function). You can use liquid in your webhook channel request URL, headers, params, and body to dynamically build your webhook request during workflow runtime. As an example, if you want to build a webhook that will always fetch the recipient's user information, you can use a URL like the following to dynamically call the endpoint based on the active workflow run's recipient ID:{" "} {"https://foobar.com/api/users/{{ recipient.id }}"} } /> ## Define request inputs A webhook channel's request configuration is shared by every workflow step that sends to it in that environment. **Request inputs** enable you to keep that configuration in one place while giving each workflow step the ability to pass its own values into it. You define an input schema on the webhook channel's environment configuration. Each workflow step that sends to the channel then supplies values for those inputs, and Knock exposes the resolved values to the request template under the `inputs` namespace. As an example, you might have a single webhook channel that posts to an internal events API, where each workflow step sets its own `event_type` and `priority` without overriding the rest of the request. ### Define an input schema The **input schema** is a JSON Schema object that describes the inputs available to workflow steps: their names, types, descriptions, default values, and whether they're required. To define one, navigate to your webhook channel, click "Edit webhook," then click "Define input schema" in the left pane of the configuration page. the input schema belongs to the channel, so a workflow step can't override it. Because Knock configures channels{" "} per environment , define the same input schema in every environment where your workflows run. } /> ### Set input values on a workflow step When a webhook channel has an input schema, Knock renders a **Request inputs** field for each property on every workflow step that sends to that channel. You'll find these fields in two places: the step panel in the workflow builder, and the right pane of the step's template editor. Each field accepts a static value or liquid, so you can reference workflow run state such as `recipient`, `data`, `actor`, and `vars`. As an example, you might set an `order_id` input to `{{ data.order_id }}` so each workflow run passes its own order. Knock validates the values you enter against the schema when you save the step. Fields that contain liquid skip validation, since their values aren't known until the workflow runs. Properties that declare a default in the schema prefill their field until the step has values of its own. input values can't reference the inputs namespace itself, and request inputs aren't supported when the step's destination is a{" "} channel group. } /> ### Use inputs in your request template At send time, Knock renders the step's input values and merges them into the liquid scope for the request under `inputs`. You can reference them in the URL, headers, query parameters, and body like any other variable, alongside `recipient`, `data`, and `vars`. ```txt title="Example request URL using inputs" https://api.example.com/events/{{ inputs.event_type }} ``` ```json title="Example request body using inputs" { "priority": "{{ inputs.priority }}", "user_id": "{{ recipient.id }}", "order_id": "{{ inputs.order_id }}" } ``` If an input value fails to render, or the rendered values don't produce a JSON object, Knock fails the send with a rendering error that you can inspect in the [workflow run logs](/send-notifications/debugging-workflows). ### Preview your inputs The **request input preview** section of the webhook channel configuration page renders a field for each property in your input schema. Values you enter there feed the cURL preview above them, so you can see the request Knock will build with inputs applied. These preview values are local to the page: Knock doesn't save them to the channel configuration and doesn't use them at runtime. ## Use your webhook channel Once your webhook channel is configured and you've built its webhook request, you're ready to add your webhook channel to a workflow. Webhook channels are added to Knock workflows the same way as any other channel. Just go to the Knock workflow builder and add your webhook step. ### Overriding webhook configurations When you add your webhook channel to a workflow, the step uses the webhook request you built in your channel configuration. You can override this on a per-step basis. A step that matches its channel configuration opens in a **locked** state. The request fields are read-only, and a callout tells you the step is using the channel-level configuration. To change the request for a single step, click "Unlock" in that callout, or the lock button in the top right of the editor, and edit the fields. Saving your changes creates channel setting overrides on the step, replacing the default environment settings with your changes. Any modifications mean _the entire template_ (URL, headers, params, and body) will be treated as an override. These overrides apply to all environments where the workflow is promoted. A step with overrides shows a callout telling you it overrides the channel-level configuration. To reset the step to its channel default, click "Reset" in that callout. overriding a step's request doesn't detach it from the channel's{" "} input schema. The step keeps its request input fields, and any {"{{ inputs.* }}"} references in the overridden template continue to resolve. } /> ## Error handling and retries Knock treats any webhook channel response outside the `2xx` range, along with connection errors and timeouts, as a failed send. Knock waits up to 50 seconds for a response before treating the request as a timeout. Most failures are retried according to the retry logic documented [here](/send-notifications/delivering-notifications#retry-logic), for a maximum of 8 attempts. Webhook channel sends use a small list of statuses that indicate a request will never succeed; everything else is considered retryable. ### Non-retryable responses Knock will not retry a webhook send when your endpoint responds with: | Status | Meaning | | ------ | ------------------ | | `401` | Unauthorized | | `403` | Forbidden | | `404` | Not found | | `405` | Method not allowed |
Knock also fails without retrying when it cannot build or issue the request at all. These cases include a malformed URL, a URL that uses a scheme other than `https`, and a hostname that does not resolve. In each of these cases, the message's delivery status moves to `undelivered` after the first attempt. Read more about message delivery statuses [here](/send-notifications/message-statuses#delivery-status). ### Retryable responses Every other failure is retried, including: - **Client errors.** `4xx`-status responses other than those listed above, most notably `400 Bad Request`, `409 Conflict`, `422 Unprocessable Entity`, and `429 Too Many Requests`. - **Server errors.** Any `5xx` level HTTP status code. - **Redirects.** Any `3xx` level HTTP status code. Knock does not follow redirects, so these responses are always treated as retryable failures. - **Connection errors and timeouts.** This includes any request that does not receive a response within 50 seconds. ## Securing your webhooks The webhook channel offers request signing as a setting on the channel. When request signing is enabled, Knock will generate a signing key and use that to sign the request in a `x-webhook-signature` header that can be verified by the consumer. Request signing is enabled per-environment configuration of the webhook channel under the "Manage configuration" modal. Once request signing is enabled, Knock will generate a signing key for you to verify the signature against. This key can be configured to be any value, or even a dynamic value resolved from the workflow run scope if necessary (see below for more). ### Using a dynamic signing key In some cases, you may wish to use a **dynamic signing key** to verify your webhooks. For example, if you're using the webhook channel to power customer-configurable webhooks, you may want a different signing key per webhook configuration. You can add a dynamic signing key by using liquid in the signing key input field. For example, if your signing key was stored on an object that represented the webhook you can reference the key as `{{ recipient.webhook_signing_key }}`. if the signing key is empty the webhook request will be skipped.} /> ### Verifying the signature The signature is generated with an HMAC using the SHA256 algorithm and, before being encoded, is comprised of the timestamp and the stringified JSON payload of the request. We encode `"timestamp in numerical form"."stringified payload"` as the signature of the request. The `x-webhook-signature` header is a string comprised of the timestamp used in the encoding and the encoded value above. It will look like this: `t=timestamp,s=encoded-signature` To test that the payload sent has not been compromised, you can recreate the signature using the signing key found on the webhook channel configuration and compare it to the one sent in the header. 1. Split the `x-webhook-signature` on the comma (",") and extract the values of timestamp and signature. 2. Construct the value of the signature by concatenating: - The timestamp (as a string) - The character `.` - The stringified JSON payload 3. Generate the signature with an HMAC and SHA256 algorithm using the signing key from your webhook's channel configuration. 4. Compare your generated signature with the one extracted in step one; they should match exactly. If the timestamp is more than five minutes old compared to the current time, you may decide you want to reject the payload for additional security. ## Frequently asked questions Yes. When a webhook channel exists in a workflow, it will send for every workflow run. You do not need to store any channel data on the recipient for the webhook to be triggered. If you want webhooks to be recipient-specific, you can use the `recipient.*` namespace to use [recipient](/concepts/recipients) variables in the URL of your webhook. If a recipient doesn't have the requisite variables configured for the webhook request to build correctly, the webhook will not be sent at runtime. Yes! We cover this in more detail in our tutorial on [building customer configurable webhooks](/tutorials/customer-webhooks). # Extensions ## Overview Learn how to extend Knock with connections to popular third-party platforms. --- title: Knock Extensions description: Learn how to extend Knock with connections to popular third-party platforms. section: Integrations > Extensions layout: integrations --- Knock integrates with popular third-party platforms, enabling new functionality across your team's tools. - [Slack](/integrations/extensions/slack) - [Data warehouse sync](/integrations/extensions/data-sync) - [Segment](/integrations/extensions/segment) - [Datadog](/integrations/extensions/datadog) - [New Relic](/integrations/extensions/new-relic) - [Heap](/integrations/extensions/heap) - [Vercel](/integrations/extensions/vercel) If you want us to add a new extension to this list, please let us know through the feedback button at the top of this page. ## Slack Use the Knock Slack extension to launch Knock agents and send workflow messages in your team's Slack workspace. --- title: Connecting Knock to your Slack workspace description: Use the Knock Slack extension to launch Knock agents and send workflow messages in your team's Slack workspace. layout: integrations tags: ["slack", "extensions"] section: Integrations > Extensions --- The Knock Slack extension connects your team's Slack workspace to Knock. It lets your team launch Knock agents directly in Slack and powers [internal Slack messaging](/integrations/chat/slack/sending-an-internal-message) for sending workflow messages to channels and people in your workspace. ## Setting up the Knock Slack extension 1. Go to **[Settings > Extensions](https://dashboard.knock.app/~/settings/integrations/extensions)** in the Knock dashboard. 2. Click "Connect" next to the Slack extension or go to the [installation page](https://control.knock.app/slack/install) here. 3. You'll be prompted to install the Knock app for Slack in your workspace. 4. After installing, you'll be redirected to Knock to finalize the installation. 5. Prompt the Knock agent in a direct message or tag `@Knock` in any channel. Connecting the extension does not create an internal Slack channel automatically. To use the connection for workflow messages, create an internal Slack channel from **Settings > Channels and sources**. ## Authenticating users In order to use the Knock Slack extension, users will need to link their Slack user account to their Knock account. Users within your Slack workspace can authenticate by prompting the Knock agent in a direct message or tagging `@Knock` in any channel. They will receive a DM with a button to authenticate. Clicking the button will take them to the Knock dashboard where they can authenticate their account. ## Using the Knock Slack extension ### Sending workflow messages to Slack After connecting the extension, you can create one managed internal Slack channel for your Knock account. Workflow authors can use that Knock channel to select a Slack channel or person as the destination for each step, without managing a Slack app, bot token, or recipient channel data. See [Sending messages to your team's Slack workspace](/integrations/chat/slack/sending-an-internal-message) for setup and delivery behavior. ### Kicking off an agent Users can kick off an agent session by prompting the Knock agent in a direct message or tagging `@Knock` in any channel. The agent will respond with a message indicating that it has started and will begin processing the user's request. The Knock app will only be available in the public channels that it has been invited to. You can invite the Knock app to a channel by at-mentioning it in the channel, or using the "Add to channel" button in Slack. Here are some example prompts you can use to kick off an agent session: - `@Knock create a new onboarding workflow that sends a welcome campaign to new users` - `@Knock draft our May newsletter using the most recent changelogs from our website` - `@Knock build a new in-app guide promoting a customer discount for our upcoming event` - `@Knock create a new audience of paid subscribers` - `@Knock find all of my workflows that are sending emails` ### Modified resources For every resource that the agent modifies, it will return a link to each resource, visible in the conversation history. Clicking a link will open the resource in the Knock dashboard. ### Specifying an environment By default, the Knock Slack extension will operate in the `development` environment. You can specify a different environment by referencing it within your prompt. The Knock agent will attempt to automatically determine the environment based on the context of the prompt. As an example, you can specify the environment in a prompt like this: `@Knock in the production environment, update the subject line of the welcome email to 'Welcome to our platform!'` ### Viewing an agent session In each agent session, you can click the "Open agent" button to view the full conversation history in the Knock dashboard. Any user within your Slack workspace can click the "Open agent" button to view that thread's conversation history. ## Disconnecting Slack Users can disconnect their linked Slack account from their user settings under **Settings > Profile > Overview** at any time. Once disconnected, they will no longer be able to use the Knock Slack extension as that user. An administrator can manage or disconnect the workspace extension from **Settings > Extensions**. Disconnecting the workspace stops agent sessions and internal Slack messaging. Any internal Slack channel and its workflow references are preserved so delivery can resume after the workspace is reconnected. ## Permissions Knock requests these Slack permissions for the Knock Slack extension to work within your workspace: | Permission | Why we need it | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `app_mentions:read` | Lets the Knock agent respond when a user @mentions the app in a channel. This is the primary way users start a conversation with the agent in shared channels. | | `assistant:write` | Powers the Slack Assistant experience: shows thread status (e.g. "Thinking…") while the agent runs, and uses Slack's Assistant thread APIs so responses feel native in Slack. | | `chat:write` | Sends agent replies and internal Slack workflow messages to conversations the Knock Slack app can access. | | `chat:write.public` | Sends internal Slack workflow messages to public channels without requiring the Knock Slack app to be invited first. | | `channels:read` | Lists public channels so workflow authors can choose an internal Slack destination. | | `channels:history` | Reads recent messages in public channel threads (via `conversations.replies`) so the agent has conversation context when a user continues a thread. Limited to the 25 most recent messages in that thread. | | `groups:read` | Lists private channels where the Knock Slack app is a member so workflow authors can choose them as internal Slack destinations. | | `groups:history` | Same as `channels:history`, but for private channels and group DMs (multi-party instant messages), so thread context works outside public channels. | | `im:history` | Reads recent messages in 1:1 DMs so the agent can follow an ongoing direct-message conversation. DMs don't emit `app_mention` events, so message history is needed for context on follow-ups. | | `im:read` | Finds direct-message conversations used by the Knock agent. | | `im:write` | Opens direct-message conversations for agent replies and internal Slack messages sent to a person. | | `users:read` | Lists people for internal Slack destinations and looks up display names when building agent thread transcripts. | | `files:read` | Downloads image files shared in a thread (when users attach screenshots or other images) so the agent can understand visual context in the conversation. Images are capped in size and count. | ## Data warehouse sync Sync notification analytics data from Knock into your own data warehouse --- title: Data warehouse sync description: Sync notification analytics data from Knock into your own data warehouse layout: integrations section: Integrations > Extensions --- You can bring your notification analytics from Knock into your own data warehouse so that you can analyze it alongside the rest of your data. Data warehouse sync is available exclusively with our{" "} Enterprise plan . } /> ## Connecting your data warehouse Please [contact our support team](mailto:support@knock.app) to get started with the warehouse connection process. Your message should include the [destination type](#supported-destinations) of the warehouse that you'd like to connect, as well as which of the tables you'd like to sync from the [available data](#available-data) listed below. We will ask you for additional information according to the destination type that you'd like to connect. Once we have the required details, Knock will provide you with a magic link to a step-by-step wizard for completing the setup process. Upon successful connection, your first data sync will include a backfill of historical data. You will receive up-to-date data once every 24 hours after the initial backfill. ## Supported destinations We currently support the following destination database types: | Vendor | Type | | ----------------- | -------------- | | `athena` | OLAP | | `bigquery` | OLAP | | `clickhouse` | OLAP | | `databricks` | OLAP | | `redshift` | OLAP | | `snowflake` | OLAP | | `aurora_mysql` | OLTP | | `aurora_postgres` | OLTP | | `mysql` | OLTP | | `postgres` | OLTP | | `abs` | Object storage | | `gcs` | Object storage | | `s3` | Object storage | | `s3_compatible` | Object storage | | `google_sheets` | Spreadsheet | ## Available data ### Messages table Our data warehouse connector syncs data from our `messages` table. Each `message` represents a notification that was executed for a single recipient. You can read more about messages as a concept [here](/concepts/messages). The backfill of messages on initial connection will include all historical message data that is available in Knock. Below is a description of the columns included in the table and the data type of each. To see how this data type will map onto the data types of your destination table, check the destination type mapping table. Please note that the table name will vary based on the name of the schema provided when filling out the form. ```sql CREATE TABLE .messages ( message_id character varying(65535) NOT NULL ENCODE raw distkey, account_id character varying(65535) ENCODE lzo, environment_id character varying(65535) ENCODE lzo, environment_name character varying(65535) ENCODE lzo, environment_slug character varying(65535) ENCODE lzo, channel_id character varying(65535) ENCODE lzo, channel_name character varying(65535) ENCODE lzo, channel_key character varying(65535) ENCODE lzo, channel_type character varying(65535) ENCODE lzo, channel_provider character varying(65535) ENCODE lzo, external_provider_id character varying(65535) ENCODE lzo, workflow_id character varying(65535) ENCODE lzo, workflow_key character varying(65535) ENCODE lzo, workflow_run_id character varying(65535) ENCODE lzo, workflow_recipient_run_id character varying(65535) ENCODE lzo, combined_trigger_data character varying(32768) ENCODE lzo, step_ref character varying(65535) ENCODE lzo, recipient_id character varying(65535) ENCODE lzo, recipient_type character varying(65535) ENCODE lzo, tenant_id character varying(65535) ENCODE lzo, exec_mode character varying(65535) ENCODE lzo, message_status character varying(65535) ENCODE lzo, inserted_at timestamp with time zone ENCODE az64, updated_at timestamp with time zone ENCODE raw, seen_at timestamp with time zone ENCODE az64, read_at timestamp with time zone ENCODE az64, clicked_at timestamp with time zone ENCODE az64, interacted_at timestamp with time zone ENCODE az64, archived_at timestamp with time zone ENCODE az64, has_been_seen bigint ENCODE az64, has_been_read bigint ENCODE az64, has_been_clicked bigint ENCODE az64, has_been_interacted bigint ENCODE az64, has_been_archived bigint ENCODE az64, actors character varying(65535) ENCODE lzo, guide_id character varying(65535) ENCODE lzo, guide_key character varying(65535) ENCODE lzo, branch_path character varying(65535) ENCODE lzo, PRIMARY KEY (message_id) ) DISTSTYLE KEY SORTKEY (message_id, updated_at); ``` See possible [engagement statuses](/send-notifications/message-statuses#engagement-status) for a message. Combined and truncated trigger data for the workflow run that generated the message, at the time the message was created. See the{" "} trigger data filtering documentation {" "} for more info. ], [ "step_ref", "string", "The reference of the step on the workflow that the message belongs to", ], ["recipient_id", "string", "The ID of the recipient for the message"], [ "recipient_type", "string",
The{" "} type of recipient for the message , can be a user or an object
, ], ["tenant_id", "string", "The tenant associated with this message"], [ "exec_mode", "string",
The execution mode of the workflow. Possible values are:
  • audience - from audience member events
  • trigger - from the API
  • rehearse - test run of a workflow
  • integration - from an event integration source
  • rehearse_integration - test run of an integration-triggered message
  • scheduled - previously scheduled workflow execution
  • workflow_step_trigger - from another workflow's trigger workflow step
  • broadcast_trigger - from a broadcast
  • rehearse_broadcast - test run of a broadcast
  • guide - messages generated from guides
, ], [ "message_status", "string", , ], [ "inserted_at", "timestamp", "The timestamp of when the message was created", ], [ "updated_at", "timestamp", "The timestamp of when the message was last updated", ], [ "archived_at", "timestamp", "The timestamp of when the message was archived", ], ["seen_at", "timestamp", "The timestamp of when the message was seen"], ["read_at", "timestamp", "The timestamp of when the message was read"], [ "clicked_at", "timestamp", "The timestamp of when a link in the message was clicked", ], [ "interacted_at", "timestamp", "The timestamp of when the message was interacted with", ], [ "has_been_seen", "integer (0 = false, 1 = true)", "Whether the message has been seen", ], [ "has_been_read", "integer (0 = false, 1 = true)", "Whether the message has been read", ], [ "has_been_clicked", "integer (0 = false, 1 = true)", "Whether a link in the message has been clicked", ], [ "has_been_interacted", "integer (0 = false, 1 = true)", "Whether the message has been interacted with", ], [ "has_been_archived", "integer (0 = false, 1 = true)", "Whether the message has been archived", ], [ "actors", "json",
A JSON array of actor users or objects. Users are provided as string IDs. Objects are provided as JSON dictionaries with keys for "id" and "collection". See the{" "} RecipientIdentifier definition {" "} for more info.
], [ "guide_id", "string", "Unique identifier for the guide, if message was sent for a guide" ], [ "guide_key", "string", "They key of the guide, if message was sent for a guide" ], [ "branch_path", "string",
The branch ancestry of the step that produced the message, as a JSON array of {`{step_ref, step_type, branch_id}`} segments ordered outermost to innermost. Null if not nested under a branch step.
, ], ]} /> ### Message events table The `message_events` export contains the event-level records behind message delivery and engagement. Each row captures one delivery or engagement event for a message, along with the workflow, channel, and recipient context around it. You can read more about [message events](/send-notifications/message-statuses#message-events). The historical backfill of message events on initial connection will include the previous 90 days of data. Use the column reference below to understand the fields included in this export. To see how these data types map onto the data types of your destination table, check the destination type mapping table. The table name will vary based on the schema name provided during setup. ```sql CREATE TABLE .message_events ( id character varying(65535) NOT NULL, environment_id character varying(65535), message_id character varying(65535), channel_id character varying(65535), channel_type character varying(65535), channel_provider character varying(65535), type character varying(65535), data super, inserted_at timestamp with time zone, recipient_type character varying(65535), object_collection_name character varying(65535), recipient_id character varying(65535), exec_mode character varying(65535), tenant_id character varying(65535), workflow_key character varying(65535), workflow_id character varying(65535), step_ref character varying(65535), guide_key character varying(65535), guide_id character varying(65535), branch_path character varying(65535), PRIMARY KEY (id) ); ```
The{" "} type of recipient for the message event , such as users, tenants, or{" "} objects, ], [ "object_collection_name", "string",
The object collection name when recipient_type is{" "} objects. Blank for user and tenant recipients.
, ], [ "recipient_id", "string", "The ID of the recipient for the message event", ], ["exec_mode", "string", "The execution mode of the workflow"], ["tenant_id", "string", "The tenant associated with this message event"], [ "workflow_key", "string", "The unique key of the workflow the message event belongs to", ], [ "workflow_id", "string", "The UUID of the version of the workflow the message event belongs to", ], [ "step_ref", "string", "The reference of the workflow step the message event belongs to", ], [ "guide_key", "string", "The key of the guide, if the message event was emitted for a guide", ], [ "guide_id", "string", "The UUID of the version of the guide, if the message event was emitted for a guide", ], [ "branch_path", "string",
The branch ancestry of the step the message event belongs to, as a JSON array of {`{step_ref, step_type, branch_id}`} segments ordered outermost to innermost. Null if not nested under a branch step.
, ], ]} /> ### Recipient change stream table at this time, the recipient change stream table contains only users. Please contact us if you need to sync other recipient types. } /> Our data warehouse connector syncs data from our `recipients` table. Each row captures the properties and preferences of a [Recipient](/concepts/recipients) at a given moment in time. Each row has an `event_type` indicating how the event was generated. Possible values are: - `recipient.created` - indicates the first time a recipient was identified - `recipient.snapshot` - is emitted every time a recipient's properties or preferences are updated, and contains the complete set of properties and preferences at that moment in time. This event may also be generated manually for all or a subset of recipients (by ID) by contacting support. - `recipient.deleted` - contains no properties or preferences, but indicates when a recipient was deleted. A recipient may be re-identified after being deleted, which will generate another recipient.created event Events in the recipient change stream table are retained for 7 days. The data available on initial connection will include a snapshot of the current state of all recipients, but no historical data. Below is a description of the columns included in the table and the data type of each. To see how this data type will map onto the data types of your destination table, check the destination type mapping table. Please note that the table name will vary based on the name of the schema provided when filling out the form. ```sql CREATE TABLE .recipient_change_stream ( id character varying(65535) NOT NULL ENCODE raw distkey, account_id character varying(65535) ENCODE lzo, environment_id character varying(65535) ENCODE lzo, environment_name character varying(65535) ENCODE lzo, environment_slug character varying(65535) ENCODE lzo, recipient_id character varying(65535) ENCODE lzo, recipient_type character varying(65535) ENCODE lzo, event_Type character varying(65535) ENCODE lzo, properties json ENCODE lzo, preferences json ENCODE lzo, timestamp timestamp with time zone ENCODE az64, PRIMARY KEY (id) ) DISTSTYLE KEY SORTKEY (id, timestamp); ```
The{" "} type of recipient recorded , ], ["event_type", "string",
Possible values are:
  • recipient.created - indicates the first time a recipient was identified
  • recipient.snapshot - is emitted every time a recipient properties or preferences are updated, and contains the complete set of properties and preferences at that moment in time
  • recipient.deleted - contains no properties or preferences, but indicates when a recipient was deleted. A recipient may be re-identified after being deleted, which will generate another recipient.created event
A manual snapshot of the current state of all or a subset of recipients (by ID) can be made by contacting support.
, ], ["properties", "json",
All properties currently assigned to the recipient. Will be empty for recipient.deleted.
, ], ["preferences", "json",
All preference sets currently assigned to the recipient, keyed by preference set ID. Will be empty for recipient.deleted.
, ], ["timestamp", "timestamp", "The timestamp of when the event was emitted."] ]} /> ## Segment Learn more about how to connect Knock with your Segment account. --- title: How to send Knock data to Segment description: Learn more about how to connect Knock with your Segment account. layout: integrations tags: ["segment", "extensions", "analytics"] section: Integrations > Extensions --- This documentation covers how to use our Segment extension to send Knock's normalized notification data into [Segment](https://segment.io) to forward on to your data warehouse or other tools where you run data analysis, such as Amplitude or Mixpanel. Once the extension is enabled, Knock automatically passes a stream of `track` events to Segment (e.g. `Notification delivered`, `Notification seen`, `Notification read`) which you can then use in your downstream tools. Knock also provides a separate integration for using Segment as a [Knock source](/integrations/sources/overview) to bring `track` and `identify` event data from Segment into Knock to power your notifications. You can learn more in our [Segment source docs](/integrations/sources/segment). The Knock Segment extension is only available on our{" "} Enterprise plan . } /> ## Getting started Knock uses the Segment HTTP Source to send events to your Segment account. Each HTTP Source has a Write Key associated with it that Knock can use to push events into Segment. A best practice is to create a separate Segment HTTP Source for each Knock environment from which you plan to collect events. 1. Create a new Segment HTTP Source in your Segment Account for each Knock environment from which you plan to collect events 1. Log into Segment, click **Connections** > **Sources**, and then choose "Add Source" 2. Find the "HTTP API" source and click "Add Source" 3. Give it a useful name, e.g. "Knock Production" 4. Make note of the write key 5. Repeat these steps for each Knock environment you need to configure 2. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 3. Find the Segment extension and click "Connect" 4. In the modal that appears, enter the write key for each environment you want to configure. Leave the field blank to disable writing events from that environment 5. Once you click save, events will begin flowing from that environment to Segment ## Events sent to Segment When connected, Knock will forward the following `track` events to Segment.
## Event schema Knock uses the Segment Track spec for the event data we pass to Segment. You can find an example payload of what you can expect from Knock events coming into Segment below. ```json title="example Segment Track event payload" { "context": { "library": { "name": "unknown", "version": "unknown" } }, "event": "Notification Seen", "integrations": {}, "messageID": "2XoNHQx7lPp0zsCtZh3BGgCJxxx", "messageId": "api-2ZjG9Ho7EeqVV9uqkdlSRYbDxxx", "originalTimestamp": "2023-12-18T19:31:58.653982Z", "properties": { "channelId": "26d3f6ad-eebc-4ce4-9125-bb856dad8xxx", "channelType": "in_app_feed", "environment": "Production", "messageId": "2XoNHQx7lPp0zsCtZh3BGgCJxxx", "provider": "in_app_feed_knock", "stepRef": "YJAxjVC-ya69fBhoSFxxx", "workflowKey": "account-invite-accepted" }, "receivedAt": "2023-12-18T19:32:01.326Z", "sentAt": null, "timestamp": "2023-12-18T19:31:58.653Z", "type": "track", "userId": "70171a84-7e52-46d7-8866-5b4ab88cxxx", "writeKey": "REDACTED" } ``` ## Datadog Learn more about how to connect Knock with your Datadog account. --- title: Connecting Knock to your Datadog account description: Learn more about how to connect Knock with your Datadog account. layout: integrations tags: ["datadog", "extensions"] section: Integrations > Extensions --- You can use the Knock + Datadog integration to stream workflow, channel, and message metrics from your Knock account to your Datadog account. With this you can: - Set up custom Datadog monitors and dashboards to track your Knock workflows & channels - Get up-to-the-minute data on workflows triggered and messages delivered - Monitor events ingested and actions triggered from event platforms like [Segment](/integrations/sources/segment) and [RudderStack](/integrations/sources/rudderstack) The Knock Datadog extension is only available on our{" "} Enterprise plan . } /> ## What this integration does This integration will send a stream of metrics as they happen from your Knock account to your Datadog account. Metrics are prefixed `knock.*` and include success and failure codes. Metrics are tagged (where applicable) by: - Environment - Workflow key - Workflow category - Workflow exec mode - Channel or workflow step type - Channel provider - Integration source type - Error reason Please refer to your Datadog pricing agreement for information on how custom metrics sent to Datadog are priced for your account. At this time there is no way to selectively enable specific metrics, however metrics will only be emitted to Datadog for features that you are actively using in Knock. A workflow can have one or more [categories](/concepts/workflows#workflow-categories). For applicable metrics, each category will have a unique tag on the emitted metrics; a workflow with categories `transactional` and `updates` will have the tags `workflow_category:transactional` and `workflow_category:updates`. ## Installing the integration 1. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 2. Click "Configure Datadog" 3. Enter a Datadog API Key from Datadog's API Keys page (we recommend creating a dedicated key just for Knock) 4. Pick the correct site for your Datadog account (visit Datadog's docs for more information) 5. Click "Connect" ## Dashboard starter kit Get started with our Datadog dashboard starter kit to start monitoring Knock metrics with just a few clicks: 1. Visit Datadog's dashboard list and click "New Dashboard" 2. Give it a name and click "New Dashboard" 3. Click the gear icon in the corner of the dashboard and choose "Import dashboard JSON..." - You may need to close the "Add Widgets" tray to see the gear icon 4. Click the button below to copy the dashboard JSON, and paste it into the Datadog dashboard page when prompted. ## Reported metrics - **knock.message_delivered.total** (`count`) - How many messages have been delivered, segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.message_delivered_retryable_error.total** (`count`) - How many deliveries ended in a retryable failure, segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.message_delivered_error.total** (`count`) - How many deliveries ended in failure (not retryable), segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.message_bounced_error.total** (`count`) - How many deliveries ended in non-retryable errors from downstream providers, segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.workflow_recipient_run.total** (`count`) - How many workflow recipient runs have been started, segmented by `workflow` key, `exec_mode`, and `workflow_category`. - **knock.workflow_recipient_run_error.total** (`count`) - How many errors were experienced during a workflow recipient run, segmented by `workflow` key, `exec_mode`, `step_type`, `workflow_category`, and the error `reason`. A workflow recipient run can report more than one error. - **knock.integration_event_received.total** (`count`) - The raw number of events received by Knock from an integration source, segmented by `source_type`. - **knock.integration_action_run.total** (`count`) - How many actions were triggered by received events, segmented by `source_type` and `action`. - **knock.integration_action_run_error.total** (`count`) - How many actions failed to run, segmented by `source_type` and `action`. - All metrics are segmented by `environment` name (e.g. `production`, `development`) - For each of the error cases, [the Knock dashboard](https://dashboard.knock.app) can provide more insights into specific failures (e.g. misconfigured workflow, channel, or integration action) ## Uninstalling the integration 1. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 2. Click the "Disconnect" button for the Datadog extension, and then click "Confirm" 3. If you created a dedicated Datadog API key for Knock, you can now delete the key from Datadog's API Keys page ## New Relic Learn more about how to connect Knock with your New Relic account. --- title: Connecting Knock to your New Relic account description: Learn more about how to connect Knock with your New Relic account. layout: integrations tags: ["new relic", "extensions"] section: Integrations > Extensions --- You can use the Knock + New Relic integration to stream workflow, channel, and message metrics from your Knock account to your New Relic account. With this you can: - Set up custom New Relic monitors and dashboards to track your Knock workflows & channels - Get up-to-the-minute data on workflows triggered and messages delivered - Monitor events ingested and actions triggered from event platforms like [Segment](/integrations/sources/segment) and [RudderStack](/integrations/sources/rudderstack) The Knock New Relic extension is only available on our{" "} Enterprise plan . } /> ## What this integration does This integration will stream metrics from your Knock account to your New Relic account. Metrics are prefixed `knock.*` and include success and failure codes. Metrics are tagged (where applicable) by: - Environment - Workflow key - Workflow category - Workflow exec mode - Channel or workflow step type - Channel provider - Integration source type - Error reason Please refer to your New Relic pricing agreement for information on how custom metrics sent to New Relic are priced for your account. At this time there is no way to selectively enable specific metrics, but metrics will only be emitted to New Relic for features that you are actively using in Knock. A workflow can have one or more [categories](/concepts/workflows#workflow-categories). For applicable metrics, each category will have a unique tag on the emitted metrics; a workflow with categories `transactional` and `updates` will have the tags `workflow_category:transactional` and `workflow_category:updates`. ## Installing the integration 1. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 2. Click "Configure New Relic" 3. Enter a New Relic API Key from New Relic's API Keys page (we recommend creating a dedicated key just for Knock) 4. Pick the correct site for your New Relic data hosting (visit New Relic's docs for more information) 5. Click "Connect" When creating a New Relic API key, make sure "Key Type" is marked as{" "} Ingest - License } /> ## Dashboard starter kit Get started with our New Relic dashboard starter kit to start monitoring Knock metrics with just a few clicks: 1. Visit New Relic's all capabilities page and click on "Dashboards" 2. In the top-right corner, click "Import dashboard" 3. Click the button below to copy the dashboard JSON 4. Paste the updated JSON into the New Relic modal 5. Replace all instances of `"accountIds":[0]` in the JSON with `"accountIds":[YOUR_ACCOUNT_ID]`, substituting your actual New Relic account ID. Your New Relic account ID can be found by either locating it in the URL after `/accounts/` or by opening the user menu in the bottom-left corner and navigating to **Administration** > **Access management** > **Accounts** to view IDs for all accounts you have access to. 6. Click "Import dashboard" ## Reported metrics - **knock.message_delivered.total** (`count`) - How many messages have been delivered, segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.message_delivered_retryable_error.total** (`count`) - How many deliveries ended in a retryable failure, segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.message_delivered_error.total** (`count`) - How many deliveries ended in failure (not retryable), segmented by `channel`, `provider`, `workflow` key, and `workflow_category`. - **knock.workflow_recipient_run.total** (`count`) - How many workflow recipient runs have been started, segmented by `workflow` key, `exec_mode`, and `workflow_category`. - **knock.workflow_recipient_run_error.total** (`count`) - How many errors were experienced during a workflow recipient run, segmented by `workflow` key, `exec_mode`, `step_type`, `workflow_category`, and the error `reason`. A workflow recipient run can report more than one error. - **knock.integration_event_received.total** (`count`) - Hhe raw number of events received by Knock from an [integration source](https://docs.knock.app/integrations/sources/overview), segmented by `source_type`. - **knock.integration_action_run.total** (`count`) - How many actions were triggered by received events, segmented by `source_type` and `action`. - **knock.integration_action_run_error.total** (`count`) - How many actions failed to run, segmented by `source_type` and `action`. - All metrics are segmented by `environment` name (e.g. `production`, `development`) - For each of the error cases, [the Knock dashboard](https://dashboard.knock.app) can provide more insights into specific failures (e.g. misconfigured workflow, channel, or integration action) ## Uninstalling the integration 1. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 2. Click the "Disconnect" button for the New Relic extension, and then click "Confirm" 3. If you created a dedicated New Relic API key for Knock, you can now delete the key from New Relics's API Keys page ## Heap Learn more about how to connect Knock with your Heap account. --- title: How to send Knock data to Heap description: Learn more about how to connect Knock with your Heap account. layout: integrations tags: ["heap", "extensions", "analytics"] section: Integrations > Extensions --- This documentation covers how to use our Heap extension to send Knock's normalized notification data into [Heap](https://heap.io) to forward on to your data warehouse or other tools where you run data analysis, such as Amplitude or Mixpanel. Once the extension is enabled, Knock automatically passes a stream of `track` events to Heap (e.g. `Notification delivered`, `Notification seen`, `Notification read`) which you can then use in your downstream tools. The Knock Heap extension is only available on our{" "} Enterprise plan . } /> ## Getting started Knock uses the Heap Bulk Track endpoint to send events to your Heap project environments. Each Heap project has a group of environments, and each of them has an `environment id` associated with it that Knock can use to push events into Heap. A best practice is to create a separate Heap environment for each Knock environment from which you plan to collect events. 1. Create a new Heap project with environments in your Heap account. You need to create one Heap environment for each Knock environment from which you plan to collect events 1. Log into your Heap dashboard, click **Account** > **Manage** > **Projects**, and then choose your main project 2. A list of environments will appear on your right side of the screen. By default Heap creates two environments (Production and development), but you can create more if you need it 3. Each environment has an `id` that will be used by Knock to send events to the given environment 2. Visit the **Extensions** page under the **Integrations** section of your Knock dashboard account settings 3. Find the Heap extension and click "Connect" 4. In the modal that appears, enter the Heap environment id for each environment you want to configure. Leave the field blank to disable writing events from that environment 5. Once you click save, events will begin flowing from that environment to Heap ## Label custom events Heap requires the labeling of custom received events from Knock before their use. 1. From your Heap dashboard, navigate to **Data** > **Labeled Events** and click on "Label event" or **Property** > **Event** 2. Select "Custom" under the source filter and choose one of the previously received events that you want to label 3. Add a name for the given event and click on "Label event" 4. Finally, click on "Verify event" 5. Once these steps are completed, the validated event will be ready for analysis and usage in your charts You can find more information about custom events and how to use them in the [Heap documentation](https://help.heap.io/hc/en-us/articles/18700111173532-How-to-use-custom-events-to-build-new-events) Heap has an expected latency for custom track events, which is typically less than 1 hour. Bear in mind that events may not immediately appear for labeling. } /> ## Events sent to Heap When connected, Knock will forward the following `Track` events to Heap.
## Event schema Knock uses the Heap Bulk track endpoint for sending event data to Heap. You can find an example payload of what you can expect from Knock events coming into Heap below. ```json title="An example Heap Track event payload" { "event": "Notification Sent", "identity": "70171a84-7e52-46d7-8866-5b4ab88cxxx", "properties": { "channelId": "26d3f6ad-eebc-4ce4-9125-bb856dad8xxx", "channelType": "in_app_feed", "environment": "Production", "messageId": "2XoNHQx7lPp0zsCtZh3BGgCJxxx", "provider": "in_app_feed_knock", "stepRef": "YJAxjVC-ya69fBhoSFxxx", "workflowKey": "account-invite-accepted" }, "timestamp": "2024-02-23T17:06:35.415647Z" } ``` ## Vercel Learn more about how to connect Knock with your Vercel account. --- title: Connecting Knock to your Vercel account description: Learn more about how to connect Knock with your Vercel account. layout: integrations tags: ["vercel", "extensions"] section: Integrations > Extensions --- You can use the Knock + Vercel integration to easily synchronize your Knock API keys to one or more Vercel projects. You'll find the Knock Vercel integration in the [Vercel marketplace](https://vercel.com/integrations/knock) for you to install. ## What this integration does The integration will set the following environment variables against your selected Vercel projects: - `KNOCK_API_KEY`: Set to your Knock secret key (starts with `sk_`) - `KNOCK_PUBLIC_API_KEY`: Set to your Knock public key (starts with `pk_`) Your environment variables will be set with the following Vercel project target mappings. You can read more about environment variables within Vercel [in the documentation](https://vercel.com/docs/concepts/projects/environment-variables#environments). | Knock environment | Vercel project target | | ----------------- | --------------------------- | | Development | `["development"]` | | Production | `["preview", "production"]` | ### Framework-specific environment variables For certain frameworks, we'll attempt to set the prefix of the `KNOCK_PUBLIC_API_KEY` on your behalf to ensure that the variable is then exposed in browser / client-side environments. Currently we offer support for: - `nextjs` - `blitzjs` - `create-react-app` - `nuxtjs` - `vue` - `gatsby` - `sveltekit` ## Installing the integration 1. Click "Add integration" on the [Vercel integrations page](https://vercel.com/integrations/knock) 2. Select the Vercel account you want to connect with 3. Sign into an existing Knock account, or create a new Knock account 4. Select the Vercel projects that you wish to connect to your Knock account 5. Click "Continue" 6. Back in your Vercel dashboard, confirm the environment variables were added by going to your **Vercel project** > **Settings** > **Environment variables** ## Uninstalling the integration You can manage the Knock Vercel integration in your Vercel dashboard under the **Integrations** tab. From there you can remove the specific integration installation from your Vercel account. **Please note**: removing an integration will delete the corresponding API keys set by Knock in your Vercel project(s). --- # In-app UI Use the Knock in-app experiences APIs and components to build rich notifications experiences inside of your product. ## Overview Learn about the in-product experiences you can build with our APIs and SDKs. --- title: Building in-app UI with Knock description: Learn about the in-product experiences you can build with our APIs and SDKs. section: Building in-app UI --- ## Overview In addition to delivering to out-of-app channels such as email, push, SMS, and chat apps like Slack, you can also use Knock to build great in-app messaging experiences. You can power any kind of in-app message with Knock, whether it’s transactional messages such as in-app feeds, notification centers, or toasts, or lifecycle-based messages such as banners, modals, or tags. Knock enables you to deliver these in-app messages to your users **in your own native product UI**, while the **content** of those messages is drafted from the Knock dashboard. Knock also provides an orchestration layer to determine **who** receives those messages, **when** they receive them, and **where** in your product those messages are rendered. Knock differs from other customer messaging platforms by separating the **content and presentation** of in-app messages, all while providing the orchestration engine and infrastructure to deliver them. [See a live demo](https://in-app-demo.knock.app/) ## Feeds and guides Knock supports two different types of in-app channel depending on the experience you're looking to build. - [Feeds](/in-app-ui/feeds/overview). A type of in-app channel that returns a per-user list of in-app messages to render in a feed-based UI. - [Guides](/in-app-ui/guides/overview). A type of in-app channel that renders a single in-app message to a user when they visit a specific page in your application. You can learn more about the difference between feeds and guides in our [feeds vs guides](/in-app-ui/feeds-vs-guides) overview. ## Why build in-app experiences on Knock? - **Real-time ready.** Our in-app API comes ready with websocket support, no infrastructure setup required. - **No data modeling required.** We handle all of the common cases for you: accurate badge counts, polymorphic notifications, read, seen and archive tracking, and much more. - **Build holistic experiences.** Use the Knock workflow builder to create cross-channel notification experiences to power cases like "send to the in-app feed, if they don't see the feed message within 5 minutes then fallback to sending an email." - **Complete APIs that are easy to use.** We take all of the heavy lifting out of building APIs to support common in-app notification experiences, like preferences and feeds. - **Drop-in components.** We have components ready to help you get started with building in-app notification experiences in React. - **Fully customizable.** It's easy to customize the experience by overriding styles, components, or building your own headless UI using our lower-level primitives. ## Client SDKs available We have the following SDKs available to use to build in-app notification experiences: - [React (Web)](/in-app-ui/react/overview) - [JS (Non-React Web)](/in-app-ui/javascript/overview) - [React Native](/in-app-ui/react-native/overview) - [Swift (iOS / macOS)](/in-app-ui/ios/overview) - [Kotlin (Android)](/in-app-ui/android/overview) - [Flutter (Android/iOS)](/in-app-ui/flutter/overview) Under each client SDK, you'll find documentation to help you get started. ## Going to production You'll need to follow our checklist on [going to production](/tutorials/implementation-guide#going-to-production) for any in-app notifications using Knock. Most importantly you'll need to [secure your requests to the Knock API for each user](/in-app-ui/security-and-authentication). ## API endpoints Learn more about the capabilities of Knock's in-app APIs and real-time services, and how these can power robust in-app notification experiences with little effort. --- title: In-app APIs and real-time service description: Learn more about the capabilities of Knock's in-app APIs and real-time services, and how these can power robust in-app notification experiences with little effort. section: Building in-app UI tags: [ "FeedItem", "MessageContent", "actionable notifications", "real-time delivery", "in-app notifications", "in-app messages", "in-app feed", "in-app channel", "in-app API", "in-app SDK", ] --- Knock provides a complete set of APIs to render your in-app notifications with the Knock-powered in-app channel. These APIs cover: - Fetching a reverse chronological list of in-app feed messages for a user. ([API reference](/api-reference/users/feeds/list_items)). - Retrieving badge count information on the number of seen and unread messages a user has. - Handling message engagement statuses to mark messages as seen, read, and archived. - Real-time delivery of in-app messages from Knock to your user. Our client-side SDKs wrap these APIs and provide a convenient way to interact with the Knock APIs. ## Security model Knock's in-app APIs are accessible via our client SDKs, which use your public API key to authenticate. In addition, we support an enhanced security mode that signs a request against the current user and provides an additional authorization mechanism. You can read more in [our security documentation](/in-app-ui/security-and-authentication). ## Real-time delivery Knock provides a real-time web socket connection for your users to subscribe to new messages being produced on an in-app channel. The events we currently send over the web socket are: | Event | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `new-message` | Emitted when a new message is produced over an in-app channel. Will not contain the full message contents, but will include metadata about the updated badge counts. Might be throttled. | ## Filtering in-app notifications It's possible to pass filters to the in-app messages endpoint. This allows you to: - Only return in-app messages for a particular tenant - Only return archived or unarchived messages - Filter messages by the `data` payload used to trigger the notification You can see the full set of available filters in the [endpoint documentation](/api-reference/users/feeds/list_items). by default all archived messages are excluded from the feed. } /> Please note, by default all `archived` messages are not displayed. ## In-app API response ### `FeedMetadata` - **total_count** (`number`) - The total number of messages the user has for this in-app channel. - **unread_count** (`number`) - The total number of unread items the user has on the in-app channel. - **unseen_count** (`number`) - The total number of unseen items the user has on the in-app channel. ### `FeedItem` Requests to the feed endpoint return an array of `FeedItem` objects. The shape of each item depends on the `mode` set when initializing the feed — the default is `compact`. For more information on setting the feed's mode, see [initialize the feed instance](/in-app-ui/react/headless/feed#initialize-the-feed-instance). - **id** (`string`) - A unique identifier for the item. - **actors** (`Recipient[]`) - One or more actors attributed to the workflow run. A maximum of one actor is returned in the default compact mode. Use rich mode to receive all actors. - **total_actors** (`number`) - The total count of unique actors across all workflow runs that generated this message. - **activities** (`Activity[]`) - One or more activities associated with this in-app message. Only includes multiple activities when the message was generated from a batch. Omitted in the default compact mode. - **total_activities** (`number`) - The total count of workflow runs that generated this message. Only greater than one for a batch. Omitted in the default compact mode. - **data** (`Record`) - The combined workflow run data associated with this message. Nested arrays and objects are omitted from `data`in the default compact mode. [See below](/in-app-ui/api-overview#passing-through-data) for more information on passing through data. - **blocks** (`ContentBlock[]`) - The rendered contents of each feed item. [See below](/in-app-ui/api-overview#message-content-blocks-contentblock) for more information. - **source** (`WorkflowSource`) - The key, version_id, and categories of the workflow that generated this in-app message. - **tenant** (`string`) - An optional tenant identifier that was set in the workflow run scope. - **archived_at** (`utc_datetime`) - An optional tenant identifier that was set in the workflow run scope. - **read_at** (`utc_datetime`) - When set, indicates the last time the message was marked as read. - **seen_at** (`utc_datetime`) - When set, indicates the last time the message was marked as seen. - **link_clicked_at** (`utc_datetime`) - When set, indicates the last time the message was clicked. - **inserted_at** (`utc_datetime`) - The time the message was generated. - **updated_at** (`utc_datetime`) - The time the message was last updated. ### Message content blocks (`ContentBlock`) Each in-app message will contain the contents of the message template that was used to generate the message, which you can use to display your in-app notifications. You'll find this content under the `blocks` attribute on each `FeedItem` returned. Each block includes: Each `ContentBlock` is one of: `MarkdownContentBlock`, `TextContentBlock`, `ButtonSetContentBlock` defined by the `type` attribute. #### `MarkdownContentBlock` Represents a markdown content block that will have rendered HTML content. - **key** (`string`) - A unique key for the block. - **rendered** (`string`) - The rendered liquid content as HTML. - **content** (`string`) - The markdown liquid template string. - **type** (`markdown`) - The type of block. #### `TextContentBlock` Represents a plaintext content block. - **key** (`string`) - A unique key for the block. - **rendered** (`string`) - The rendered liquid content as plaintext. - **content** (`string`) - The plaintext liquid template string. - **type** (`text`) - The type of block. #### `ButtonSetContentBlock` Represents a set of one or more buttons that can be rendered in the in-app message. - **key** (`string`) - A unique key for the block. - **buttons** (`ActionButton[]`) - A list of buttons to render in the block. - **type** (`button_set`) - The type of block. Each button set block will contain an array of `ActionButton` objects. Each button includes: - **name** (`string`) - One of primary or secondary. - **label** (`string`) - The label to show on the button. - **action** (`string`) - The URI for this action. ### Passing through data When triggering a workflow that generates an in-app message, all of the `data` in the workflow run scope will be included in the generated in-app message payload. This makes it possible to pass through identifiers from your system to your in-app notifications that you can then use to render custom UI elements. Here's an example: if you trigger a workflow with `{ "project_id": "proj_123" }` which generates an in-app message, you can expect the `data` in the `FeedItem` to include this `project_id`. ### Customizing in-app API responses It's also possible to override the response of the in-app API to hide sensitive data using an allow/deny list. You can [read more on doing so here](/integrations/in-app/knock#customizing-api-response-content). ## Message statuses All messages returned from the in-app channel adhere to our message status and engagement APIs. That means it's possible to programmatically mark the messages as seen, read, interacted with, or archived. You can read more in our [message status documentation](/send-notifications/message-statuses), or read more on how we use these message statuses [on the in-app channel](/integrations/in-app/knock#how-the-knock-in-app-feed-uses-status). ## Working with actionable notifications In-app notification messages (`FeedItem`) are "actionable" and can be one of three types: - **Standard**: indicates that the entire cell is potentially actionable (e.g., clicking anywhere on the cell will trigger an action). - **Single-action**: indicates that the notification cell has a single action button that can be clicked. - **Multi-action**: indicates that the notification cell has both a primary and secondary set of actions that can be clicked. Working with actionable notifications is straightforward. If you're using out-of-the-box Knock UI components, we'll handle the rendering of actionable notifications for you. If you're building a custom feed implementation, you can check the `blocks` attribute for the presence of a `ButtonSetContentBlock` and render the `buttons` contained within the block accordingly. ## Security + authentication Learn more about how to secure your client-side applications as they integrate with Knock. --- title: Security and authentication description: Learn more about how to secure your client-side applications as they integrate with Knock. section: Building in-app UI tags: [ "jwt", "signing keys", "missing_user_token", "user token", "enhanced security mode", ] --- This documentation references examples from our{" "} client-side JS SDK . You only need to add the authentication outlined here if you're integrating Knock on the client-side of your applications to use Knock feeds and guides, or the Knock preferences model. } /> Access to Knock's [API](/api-reference) is protected using a secret API key for your backend application, and a public API key for your client application. ## Public API key endpoints By default a public API key within Knock can call the following endpoints on the API (see table below). Production environments should enable **enhanced security mode**, which requires clients to send both the public API key and a signed user token that identifies the user that is performing the request. Enhanced security mode trades convenience for security, and we recommend that you enable it [when going to production](/tutorials/implementation-guide#going-to-production). Further, it's possible to [limit the endpoints](#limiting-access-to-specific-resources) that can be called with a public API key by setting access `grants` on the user token. This can also be used in [multi-tenant environments](/multi-tenancy/overview) to restrict access to specific tenants that the user has access to.
Retrieve a user by their ID, "GET", "/v1/users/:id", ], [ Update a user by their ID, "PUT", "/v1/users/:id", ], [ Retrieve the user's preferences by their ID , "GET", "/v1/users/:id/preferences", ], [ Retrieve a specific preference by the user's ID and preference set ID , "GET", "/v1/users/:id/preferences/:id", ], [ Update a specific preference by the user's ID and preference set ID , "PUT", "/v1/users/:id/preferences/:id", ], [ Retrieve the preference center configuration for a user , "GET", "/v1/users/:id/preference_center/config", ], [ Retrieve the channel data for a specific user on a specific channel , "GET", "/v1/users/:id/channel_data/:channel_id", ], [ Update the channel data for a specific user on a specific channel , "PUT", "/v1/users/:id/channel_data/:channel_id", ], [ Delete the channel data for a specific user on a specific channel , "DELETE", "/v1/users/:id/channel_data/:channel_id", ], [ Retrieve in-app feed messages for a specific user on a specific channel , "GET", "/v1/users/:id/feeds/:channel_id", ], [ Mark an in-app feed message as read, seen, or archived , "PUT", "/v1/messages/:message_id/:status", ], [ Unmark an in-app feed message as read, seen, or archived , "DELETE", "/v1/messages/:message_id/:status", ], [ Mark multiple in-app feed messages as read, seen, or archived , "POST", "/v1/messages/batch/:status", ], [ Perform a bulk action on multiple in-app feed messages for a specific channel , "POST", "/v1/channels/:channel_id/messages/bulk/:action", ], [ Retrieve guides for a specific user on a specific channel , "GET", "/v1/users/:id/guides/:channel_id", ], [ Mark a guide message as seen, interacted, or archived , "PUT", "/v1/users/:id/guides/:channel_id/messages/:message_id/:status", ], ]} /> ## Authentication (without enhanced security) In a non-production Knock environment, you can use your public key to authenticate all users. You do not need to implement any other security mechanisms. Knock will not reject requests that do not include a signed user token. This approach is convenient for development and testing, but should not be used in a production environment with real user data. } /> **Client SDK example** ```js import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); // Tell Knock to use the users id knockClient.authenticate({ id: currentUser.id }); ``` **React notification feed example** ```jsx ``` ## Authentication (with enhanced security) When enhanced security mode is enabled, Knock will reject requests from the client using your public API key that **do not include a signed user token**. This token must be generated by your backend application and is used to authenticate a user's requests to Knock using your public API key. Using our JWT-based authentication approach means using a shared secret to sign a new JWT on your backend. This means you can generate the authentication token out-of-band without an additional network request. ### 1. Generate the signing key You can find the signing key in the Knock dashboard under the **Platform** > **API keys** page. Save the private key shown to you here. Note: you won't be shown this key again, so you'll need to regenerate it if you lose access. The Knock dashboard will present the generated private key in two formats: 1. Base-64 encoded PEM format, which fits on a single line (convenient for setting environment variables) 2. PEM encoded format, which may be required by certain libraries or platforms (visible under the "Advanced" disclosure) By convention, we recommend storing the private key in the environment variable `KNOCK_SIGNING_KEY`. This is where the Knock SDK will look for the key by default. ### 2. Sign the JWT Within your backend application, you'll need to sign the JWT and make it available to your front-end client. Usually, you'll do this by passing it down as a serialized property on the user or passing in a cookie. Your JWT will need to be signed against your **private signing key** using an **RS256** algorithm. The JWT must contain the `sub` claim and we recommend also including `iat` and `exp` to enforce token expiration: ```json { // The user that you're signing the token for (required) "sub": "user_id", // When the token was issued (recommended) "iat": 1608600116, // Expiry timestamp (recommended) "exp": 1608603716 } ``` To sign your JWT as middleware in a NodeJS express like app: ```js import { signUserToken } from "@knocklabs/node/lib/tokenSigner"; app.use(async (req, res, next) => { if (!req.user) { return next(); } res.locals({ // `signUserToken` can take an options object as the second parameter knockToken: await signUserToken(req.user.id, { // Optionally override the signing key (defaults to KNOCK_SIGNING_KEY env var) signingKey: knockSigningKey, // Optionally set custom expiration (defaults to 3600 seconds) expiresInSeconds: 7200, }), }); next(); }); ``` You can see more details on the options for the `signUserToken` method in the SDK docs. ### 3. Send the JWT to the client In your client application, you can now use the JWT to authenticate with Knock: **Client SDK example** ```js import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); // Tell Knock to use the user id and the token knockClient.authenticate({ id: currentUser.id }, currentUser.knockToken); ``` **React notification feed example** ```jsx ``` ## Limiting access to specific resources You can limit access to the endpoints that can be called with a public API key by setting access `grants` on the user token. You can ensure that **no other actions** can be taken on any other resources by setting the `explicit_grants` option to `true` when generating the user token. Without this option, the token will be granted access to all resources by default.
Grants are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. A grant for a user should always be structured with the user's ID as the key (`https://api.knock.app/v1/users/`). This should always match the `sub` claim in the user token. ```json title="Example of a grant for a user" { "https://api.knock.app/v1/users/": { "user/read": [{}] } } ``` ```json title="Example of a user token with grants to make it read-only" { "sub": "", "explicit_grants": true, "grants": { "https://api.knock.app/v1/users/": { "user/read": [{}], "user/feed_read": [{}], "preferences/read": [{}], "channel_data/read": [{}] } } } ``` ### Scoping a token to a tenant Some per-tenant resources can further be scoped to a specific tenant by including a set of grants for one or more tenants in the user token. Doing so will restrict the updates that can be made to the resource to only the tenants that are included in the user token. Without a tenant-scope, any user actions will apply to all tenants. When at-least one tenant-scope is included, any user actions will apply to the tenants included in the user token. } /> Per-tenant resources can be applied to:
Grants for a tenant should always be structured with the tenant's ID as the key (`https://api.knock.app/v1/tenants/`). One or more tenant grants can be included in the user token. **Example policies** This policy will **only** allow the user to read in-app feeds for the tenant `acme_corp`. Feed reads to any other tenant will be denied, as will feed reads to a nil tenant scope. ```json title="Example of a user token with grants for a specific tenant" { "sub": "", "explicit_grants": true, "grants": { "https://api.knock.app/v1/tenants/acme_corp": { "user/feed_read": [{}] } } } ``` To allow the user to read a feed for the unscoped usecase (e.g. tenant = null), and also allow them to read feeds for the tenant `acme_corp`, you can include a grant the user's resource and a grant for the tenant. ```json title="Example of a user token with grants for a specific tenant" { "sub": "", "explicit_grants": true, "grants": { "https://api.knock.app/v1/users/": { "user/feed_read": [{}] }, "https://api.knock.app/v1/tenants/acme_corp": { "user/feed_read": [{}] } } } ``` ## Handling token expiration Generally, it's advisable to set the token expiration to be equal to your session token expiration. That way you can regenerate both of the tokens together from within your application. There may, however, be cases where this is not possible, in which case it's best practice to opt for a relatively short-lived expiration time for your user tokens and refresh them from your backend before the expiration window. For convenience, the Knock JavaScript client and React package expose an `onUserTokenExpiring` callback method which will be invoked before the user token expires. This provides an easy hook for your application to refresh the user token from your backend. At any time your application can call the `authenticate` method again with an updated user token and the connection to the Knock real-time service will be restarted. ## Avoiding authentication You can avoid authentication altogether by proxying requests to Knock via your backend, although we don't recommend this approach as it will add more latency for your users. ## Troubleshooting ### Errors using your Knock signing key If you are getting errors like `secretOrPrivateKey must be an asymmetric key when using RS256`, try using the base-64 encoded format of your signing key generated in the Knock Dashboard (under **Platform** > **API keys** > **Application Signing Keys**). ```bash // .env.local KNOCK_SIGNING_KEY="LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQoK..." ``` ## Feeds v. guides An overview of the differences between feeds and guides. --- title: Feeds v. guides description: An overview of the differences between feeds and guides. tags: [feed, guides, in-app] section: Building in-app UI --- Knock uses two different concepts to power in-app messaging: feeds and guides. Feeds power notification centers with messages generated in advance by workflow triggers, while guides power in-product UI with content fetched as needed based on targeting rules managed in the Knock dashboard. In this document we'll cover the key differences between the two, and when to use each. ## Feeds The [feeds API](/api-reference/users/feeds) enables you to build notification feeds, inboxes, and notification centers that display a chronological list of per-user notifications. Feed messages are generated ahead of render time by triggering workflows or scheduling broadcasts. When a user opens their feed, the API fetches pre-generated messages and displays them. ### Feed API characteristics - Returns a list of messages generated ahead of time by triggering workflows and scheduling broadcasts. - Returns messages in reverse chronological order. - Returns aggregate values for unread counts, read counts, and seen counts, for use in feed badge UIs. - Can be filtered by status (unread, read, seen, archived) or custom properties. ### Feed use cases - When you want to build a notification feed, inbox, or notification center. - When you want to build a notification experience that can be thought of as a list of items. ## Guides The [guides API](/api-reference/users/guides/) enables you to power in-product messaging using your own UI components. Use guides for announcements, upgrade flows, paywalls, banners, and other UI that doesn't fit the feed-based model. Guides are data-driven and evaluated at render time. When a user is eligible to see a guide based on targeting and activation rules configured in the Knock dashboard, the API returns the guide data to your application. Your engineering team controls the UI layer, while your content team manages content, targeting, and activation rules in the dashboard. ### Guides API characteristics - Returns guides based on eligibility determined by [targeting conditions](/in-app-ui/guides/create-guides#targeting) and [activation location rules](/in-app-ui/guides/create-guides#activation). - Can fetch a single guide by its unique `key` or a list of guides filtered by `type` (message type). - Does not include aggregate counts of unseen/unread items. - Generates messages when a guide is fetched and returned to a user. ### Guides use cases - When you need eligibility determined at runtime based on user attributes or application state. - When you want to control the UI layer in your codebase while non-technical teams manage content and targeting. - When you need location-based activation rules (showing UI on specific pages or routes). - For non-feed UI such as modals, banners, tooltips, badges, or inline components like paywalls. ## Feeds ## Overview Power feed-based notification experiences in your product, such as in-app feeds, notification centers, and inboxes. --- title: Feeds description: Power feed-based notification experiences in your product, such as in-app feeds, notification centers, and inboxes. tags: [] section: Building in-app UI > Feeds --- ## Overview The Knock [feeds API](/api-reference/users/feeds) is great for building notification experiences that rely on a _feed_ of per-user notifications. Here are a few characteristics of the feeds API. - Returns a list of messages generated ahead of time by triggering workflows and scheduling broadcasts. - Returns messages in reverse chronological order. - Returns aggregate values for unread counts, read counts, and seen counts, for use in feed badge UIs. - Can be filtered by status (unread, read, seen, archived) or custom properties. ## How Knock powers in-app feeds In-app feed messages in Knock are generated when a workflow runs for a recipient, or when a broadcast runs. Those in-app messages are available via our client API, which your application uses to fetch messages and update their state (seen, read, interacted with, archived). In-app messages encode **content**, which is created using the [Knock template editor](/template-editor/overview) as part of creating a workflow. You can read more on working with in-app message templates below. The Knock feed API also provides filtering capabilities to only return messages with a particular status (unread, read, seen etc) or custom property (sent as the data payload in the workflow trigger). You can also sort responses from the Knock feed API by priority, for cases where you only want to display one in-app message to a user at a time. This makes it possible to build highly customized in-app experiences within your product. Clients connect to the in-app real-time service, which receives notifications over a websocket when new messages are sent to an in-app feed channel. This is a managed service provided by Knock. ## Pre-built UI feed components Our client SDKs for React, React Native, iOS, and Android ship with pre-built components to power real-time, in-app notification feeds within your application. Additionally, our React SDK for the web also ships with prebuilt components to easily integrate Slack and Microsoft Teams authentication and channel selection to power Slack and Microsoft Teams notifications. ## Build your own feed UI (headless) In addition to the pre-built components that we ship, it's also possible to [use Knock in a headless way](/in-app-ui/feeds/custom-ui) by using our lower-level primitives. Doing so means you get to leverage the full power of Knock's in-app messaging infrastructure while having the flexibility to build your own UI components. ## Frequently asked questions Yes, you can. By default, Knock will create a single in-app channel for you, but you can easily add additional in-app channels should you need to support multiple feeds or different types of in-app message experiences. Currently you can't pin messages. The feed will only return messages in reverse chronological order with no way to change that order. You can, however, create multiple feed instances and use [feed filtering](/in-app-ui/api-overview#filtering-in-app-notifications) to build a pinned notifications UI. ## Styling Learn about how you can style the interface of your in-app feeds. --- title: Styling description: Learn about how you can style the interface of your in-app feeds. tags: ["styling", "ui", "css", "theme"] section: Building in-app UI > Feeds --- Knock provides extensive styling capabilities for feed-based components across all supported platforms (Web, iOS, Android, and React Native). This page covers the various ways you can customize the appearance of your in-app feeds to match your application's design system. ## Styling our pre-built components ### Web (React) For web applications using Knock's React SDK, you have several options for customizing the feed UI: 1. **CSS variables**: The feed uses CSS variables that you can override to align with your product's design. All variables are prefixed with `--rnf-`. You can find the complete list of available variables in the [theme.css source code](https://github.com/knocklabs/javascript/blob/main/packages/react/src/theme.css). 2. **Custom CSS**: You can completely replace Knock's CSS to tailor the feed's look. All feed classes are unique and start with `rnf-`. View the [component source code](https://github.com/knocklabs/javascript/tree/main/packages/react/src) for class details. 3. **Component customization**: You can customize individual components through props: - `NotificationCell`: Customize the rendering of notification cells using the `renderItem` prop - `Avatar`: Customize the avatar component within notification cells - `NotificationIconButton`: Customize the notification icon and badge count type - `colorMode`: Support for both light and dark modes For more detailed information about React component customization, see our [feed UI/UX override docs](/in-app-ui/react/feed#uiux-overrides). ### iOS For iOS applications, Knock provides the `InAppFeedTheme` class for comprehensive UI customization: 1. **Feed theme properties**: - `rowTheme`: Customize row items using `FeedNotificationRowTheme` - `titleString`: Customize or hide the feed title - `titleFont` and `titleColor`: Customize title appearance - `upperBackgroundColor` and `lowerBackgroundColor`: Customize feed sections 2. **Row theme properties**: - `showAvatarView`: Toggle avatar visibility - `avatarViewTheme`: Customize avatar appearance - `notificationContentCSS`: Customize notification body styling - `backgroundColor`: Set row background color - `markAsReadSwipeConfig` and `archiveSwipeConfig`: Customize swipe actions - `unreadNotificationCircleColor`: Customize unread indicators - Customizable action button styles for primary, secondary, and tertiary actions For more detailed information about iOS customization, see our [iOS customization documentation](/in-app-ui/ios/customization). ### Android For Android applications, Knock provides similar theming capabilities through the `InAppFeedTheme` class: 1. **Feed theme properties**: - `rowTheme`: Customize row items using `FeedNotificationRowTheme` - `filterTabTheme`: Customize filter tabs - `titleString`: Customize or hide the feed title - `textStyle`: Customize title text style - `upperBackgroundColor` and `lowerBackgroundColor`: Customize feed sections 2. **Row theme properties**: - `backgroundColor`: Set row background color - `bodyTextStyle`: Customize message body text - `unreadNotificationCircleColor`: Customize unread indicators - `showAvatarView`: Toggle avatar visibility - `avatarViewTheme`: Customize avatar appearance - Customizable action button styles - Swipe action configurations for mark as read/unread and archive/unarchive For more detailed information about Android customization, see our [Android customization documentation](/in-app-ui/android/customization). ### React Native For React Native applications, Knock provides style customization through several theme objects: 1. **Notification feed cell style**: - `unreadNotificationCircleColor`: Customize unread indicators - `showAvatarView`: Toggle avatar visibility - `avatarViewStyle`: Customize avatar appearance - Customizable action button styles for primary, secondary, and tertiary actions - `sentAtDateFormatter` and `sentAtDateTextStyle`: Customize timestamp appearance - `htmlStyles`: Customize HTML content styling 2. **Empty notification feed style**: - Customize empty state appearance with title, subtitle, and icon - Customizable text and icon styles 3. **Action button style**: - Customize button container, text, and icon styles 4. **Avatar view style**: - Customize container, image, and text styles for avatars For more detailed information about React Native customization, see our [React Native customization documentation](/in-app-ui/react-native/customization). ## Building your own UI components You can build your own UI components and use them with Knock in a headless way. This approach allows you to leverage Knock's powerful in-app messaging infrastructure while maintaining full control over your UI design. For more information, see our [custom UI documentation](/in-app-ui/feeds/custom-ui). ## Custom UI (headless) How to build custom feed UI using our low level primitives. --- title: "Custom feed UI" description: How to build custom feed UI using our low level primitives. section: Building in-app UI > Feeds tags: ["hooks", "headless", "useNotifications", "useAuthenticatedKnockClient"] --- While you can use our pre-built UI components to integrate an in-app notification feed into your application, you can also create your own feed UI by using Knock's SDKs and by bringing your own UI components. Knock's client SDKs for Javascript, React, React Native, Flutter, iOS, and Android ship with access to the underlying data stores and APIs to power your own feed UI. Using these primitives, it's possible to build a completely custom feed experience that leverages your design system and brand, without needing to build any of the backend infrastructure. ## Javascript You can use the `@knocklabs/client` package to build your own feed UI, where you'll have access to the underlying `FeedClient` instance and state store. For more information, see the [Javascript SDK documentation](/in-app-ui/javascript/sdk/reference). ## React If you're using the React SDK, you can use our hooks to build your own feed UI. You'll find more in the [React SDK documentation](/in-app-ui/react/headless/feed). ## React Native If you're using the React Native SDK, you can use our hooks to build your own feed UI. You'll find more in the [React Native SDK documentation](/in-app-ui/react-native/headless/feed). ## Flutter If you're using the Flutter SDK, use [`FeedClient`](/in-app-ui/flutter/sdk/reference#feedclient) to drive your feed UI. When a screen or scope goes away, call [`feedClient.dispose()`](/in-app-ui/flutter/sdk/reference#dispose-1) so sockets and streams tear down. As of [`knock_flutter` 1.0.0](https://pub.dev/packages/knock_flutter), public API types use a `Knock` prefix (for example `KnockApiClient`, `KnockApiResponse`, and `KnockApiException` instead of `ApiClient`, `ApiResponse`, and `ApiError`). `KnockApiException` implements [`Exception`](https://api.dart.dev/dart-core/Exception-class.html), not `Error`, so update any `catch` clauses that assumed `ApiError` extended `Error`. The SDK does not collect FCM or APNs tokens. Add something like [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) in your app, read the device token yourself, then pass it to [`UserClient.registerTokenForChannel`](/in-app-ui/flutter/sdk/reference#registertokenforchannel). The SDK [README migration table](https://github.com/knocklabs/knock-flutter/blob/main/README.md#migrating-from-01x-to-100) and [example README](https://github.com/knocklabs/knock-flutter/blob/main/example/README.md) cover renames and Firebase-oriented setup. You’ll find the full surface area in the [Flutter SDK reference](/in-app-ui/flutter/sdk/reference). ## iOS (Swift) If you're using the iOS SDK, you can use the FeedManager instance to build your own feed UI on top of. You'll find more in the [iOS SDK documentation](/in-app-ui/ios/sdk/reference). ## Android (Kotlin) If you're using the Android SDK, you can use the FeedManager instance to build your own feed UI on top of. You'll find more in the [Android SDK documentation](/in-app-ui/android/sdk/reference). ## Handling interactivity How to handle routing, clicking, and other interactions with your in-app feed. --- title: "Handling interactivity" description: How to handle routing, clicking, and other interactions with your in-app feed. section: Building in-app UI > Feeds tags: ["interactivity", "routing", "clicking", "interaction"] --- Knock enables you to build engaging in-app feed experiences with a rich set of interactivity features across all supported platforms (Web, iOS, Android, and React Native). This documentation covers the various ways you can handle user interactions with your feed components. ## Common interaction patterns ### Clicking and tapping All Knock feed components support handling user interactions through click/tap events: #### Web (React) ```jsx title="Handling notification interactions in React" { // Handle notification cell click }} onNotificationButtonClick={(item, button) => { // Handle button click within notification }} /> ``` #### iOS ```swift title="Handling notification interactions in iOS" KnockInAppFeedView() .onReceive(viewModel.didTapFeedItemButtonPublisher) { actionString in // Handle button tap } .onReceive(viewModel.didTapFeedItemRowPublisher) { item in // Handle row tap } ``` #### Android ```kotlin title="Handling notification interactions in Android" feedViewModel.didTapFeedItemRowPublisher .onEach { feedItem -> // Handle feed item row tap } .launchIn(this) feedViewModel.didTapFeedItemButtonPublisher .onEach { feedItemButtonEvent -> // Handle button tap } .launchIn(this) ``` #### React Native ```jsx title="Handling notification interactions in React Native" { // Handle row tap }} onCellActionButtonTap={({ button, item }) => { // Handle button tap }} /> ``` ### URL handling Knock supports automatic URL handling for notifications: 1. **Action URLs.** You can specify an `action_url` in your notification template, and Knock will automatically handle redirects when users interact with the notification. 2. **Custom URL handling.** You can implement custom URL handling logic in your click handlers to control navigation behavior. ### State management Knock provides built-in state management for notification interactions: 1. **Mark as read.** Notifications can be automatically marked as read when: - The feed is opened - A notification is clicked - A button within a notification is clicked 2. **Mark as seen.** Notifications are automatically marked as seen when they appear in the feed viewport. 3. **Archiving.** Users can archive notifications through swipe actions or dedicated buttons. ## Learn more For more detailed information about implementing interactivity in your specific platform, refer to the platform-specific docs: - [Web (React) feed docs](/in-app-ui/react/feed#handling-interactivity) - [iOS customization](/in-app-ui/ios/customization) - [Android customization](/in-app-ui/android/customization) - [React Native customization](/in-app-ui/react-native/customization) ## Filtering feeds Learn how to use Knock's feed filtering to scope in-app feeds to display information relevant to a particular tenant, resource, or individual workflow. --- title: Filtering feeds description: Learn how to use Knock's feed filtering to scope in-app feeds to display information relevant to a particular tenant, resource, or individual workflow. section: Building in-app UI > Feeds tags: ["filtering", "tenant", "source", "data"] --- When building an in-app notifications UI, you can filter notifications to increase their relevancy for your users. This is particularly useful for: - A multi-tenant SaaS application where users need to see tenant-specific notifications - Applications that need to show contextual notifications for specific resources - Separating notifications by engagement status (unread, read, seen, archived) - Separating notifications by a custom property (e.g. `priority: "high"`, `status: "incomplete"`) This page covers how to filter the feed API using `defaultFeedOptions`. These filters apply to all feed results, including real-time updates and badge counts for unseen or unread notifications. The feed client accepts default options that apply to both the initial feed fetch and real-time updates. This documentation demonstrates how to set these defaults. You can also override these values when calling the{" "} fetch function. } /> ## Filtering by tenant In a multi-tenant SaaS application, users often belong to one or more tenants, and their notifications should be scoped to the tenant they're currently viewing. For example, consider a user named `jane` in a document editing app. Jane's profile is connected to two workspaces: `acme-inc` and `superior-products`. When Jane is active in the `acme-inc` workspace, she should only see notifications relevant to that workspace. Knock implements this concept through [Tenancy](/concepts/tenants), where workflow triggers can be "tagged" with a tenant identifier. This identifier can be used to apply tenant overrides and scope in-app notifications. Here's how it works: ```js title="Triggering our workflow with a tenant" await knock.workflows.trigger("new-comment", { recipients: ["jane"], data: { pageId: page.id, commentId: comment.id, }, tenant: workspace.id, }); ``` To scope the in-app feed by the user's current tenant: ```javascript title="Rendering our feed with the tenant filter" // Passed to our component ; // Passed to the `useNotifications` hook as `FeedClientOptions` useNotifications(knockClient, process.env.KNOCK_FEED_CHANNEL_ID, { tenant: "acme-inc", }); ``` By default, filtering by tenant includes all notifications that either: - Include the specified tenant - Were sent without a tenant identifier If you need to show only notifications for a specific tenant, you can add `has_tenant: true` to your filter: ```javascript defaultFeedOptions={{ tenant: "acme-inc", has_tenant: true }} ``` ## Filtering by workflow To show notifications from a specific workflow in your in-app feed, use the `source` filter. For example, in a document collaboration application, you might want to show only notifications from the `new-comment` workflow: ```js title="Filtering our in-app feed by source" // Passed to our component ; // Passed to the `useNotifications` hook as `FeedClientOptions` useNotifications(knockClient, process.env.KNOCK_FEED_CHANNEL_ID, { source: "new-comment", }); ``` Note: Currently, filtering by multiple sources is not supported. ## Filtering by `data` You can filter the feed using properties from the `data` payload in your trigger step. This provides a flexible way to query feed data based on your system's entities. For example, to show comment notifications for a specific page in your document editing app: ```js title="Triggering a workflow with data" await knock.workflows.trigger("new-comment", { recipients: recipientIds, data: { pageId: page.id, commentId: comment.id, }, }); ``` To filter the feed by this data: ```js title="Filtering our in-app feed by trigger data" // Passed to our component ; // Passed to the `useNotifications` hook as `FeedClientOptions` useNotifications(knockClient, process.env.KNOCK_FEED_CHANNEL_ID, { trigger_data: { pageId }, }); ``` Important limitations to be aware of when filtering by data: 1. You can only filter by top-level keys in your data payload (e.g., `data.foo` is supported, but `data.foo.bar` is not) 2. Only exact value matching is supported (partial matches and comparison operators are not available) ## Learn more about feed filtering For a complete list of filtering options, refer to the [API reference](/api-reference/users/feeds/list_items). These options are available through the `FeedClientOptions` type, which you can use to: - Set default filters in the feed constructor - Override defaults for individual fetch operations ## Socket behavior overrides Learn about how you can customize the real-time socket behavior of your in-app feeds. --- title: Real-time socket behavior description: Learn about how you can customize the real-time socket behavior of your in-app feeds. tags: ["socket", "behavior", "override"] section: Building in-app UI > Feeds --- The Knock in-app real-time service is a managed service that provides a websocket connection to your application. This websocket connection is used to receive notifications from Knock when new messages are sent to an in-app feed channel. Our socket infrastructure is designed to be reliable and scalable, handling real-time updates for your in-app feeds. When new messages arrive in a feed channel, Knock automatically pushes these updates to your application through the websocket connection, ensuring your users receive notifications in real-time. The socket connection is automatically managed by our SDKs, handling connection lifecycle, reconnection logic, and message delivery. This allows you to focus on building your application without worrying about the complexities of real-time infrastructure. ## Customizing socket behavior You can customize the socket behavior of your in-app feeds by passing options to the feed component. These options allow you to control various aspects of the socket connection, including: - Auto-disconnect management for inactive tabs - Cross-browser feed synchronization You can learn more about customizing the socket behavior in our feed [socket behavior options](/in-app-ui/react/feed#socket-behavior-options). ## Guides ## Overview Power in-product messaging such as announcements, paywalls, and banners using your own components. --- title: Guides description: Power in-product messaging such as announcements, paywalls, and banners using your own components. tags: [ "guides", "announcements", "banners", "paywalls", "lifecycle", "marketing", "tours", ] section: Building in-app UI > Guides --- ## Overview A guide represents a piece of UI that you want to be **powered by Knock within your application**.
### Auto disconnect when inactive Optionally, you can configure the `Feed` to disconnect socket connections with inactive tabs after a brief delay. If the tab becomes active again, the socket will reconnect to continue receiving real-time updates. ```jsx title="Automatically manage socket connections" import { KnockFeedProvider } from "@knocklabs/react"; ; ``` ## Localization You can set custom translations for the components inside the feed by passing the `i18n` property to the `KnockProvider` component. You can provide a partial or full set of translations to be used [following the expected `I18nContent` type](/in-app-ui/react/sdk/reference#i18ncontent). **Note**: the default locale used in the components will be `en`. No other translations are provided out-of-the-box. ```jsx import { KnockProvider } from "@knocklabs/react"; const YourAppLayout = () => { return ( ); }; ``` ## Filtering/scoping a feed A feed can be scoped by any of the parameters that are accepted on the [feed endpoint](/api-reference/users/feeds/list_items) via the `FeedClientOptions` set in the `defaultFeedOptions` for the `KnockFeedProvider` component, or via the `useNotifications` hook. #### Status filtering The NotificationFeed component provides built-in support for filtering notifications by status (e.g., All, Unread). You can set the initial filter status using the `initialFilterStatus` prop: ```jsx import { NotificationFeed, FilterStatus } from "@knocklabs/react"; ; ``` The component manages its filter state internally and provides a default header with status filtering controls. If you need to customize the status filtering behavior, you can provide a custom header using the `renderHeader` prop. You can read more in this [documentation on feed filtering](/in-app-ui/react/filtering-in-app-feeds). ## Related links - [`@knocklabs/react` library reference](/in-app-ui/react/sdk/reference) - [`@knocklabs/client` library reference](/in-app-ui/javascript/sdk/reference) ## Toast How to build in-app toasts powered by Knock and React. --- title: "Toast" description: How to build in-app toasts powered by Knock and React. section: Building in-app UI --- While there are no out-of-the-box toast components in the `@knocklabs/react` library, it's easy to build toasts on top of the primitives exposed. This page covers how to do that using the `react-hot-toasts` library as our "toaster." See a live demo. ## Getting started To use this example, you'll need an account on Knock, as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) To find the channel ID for your in-app channel(s), navigate to{" "} Channels and sources under the account settings section of your Knock dashboard, click on your in-app feed channel, and copy the channel ID. } /> ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react react-hot-toast ``` ## Adding the Knock providers We'll need to wrap our toast producing component in a `KnockProvider` and `KnockFeedProvider` to set up a connection to Knock and connect to the authenticated user's feed. You can read more about the available props for the providers in [the reference](/in-app-ui/react/sdk/reference#knockprovider). ```jsx import { KnockProvider, KnockFeedProvider } from "@knocklabs/react"; // We'll write this next import NotificationToaster from "./NotificationToaster"; const NotificationToastProducer = () => { // An example of fetching the current authenticated user const { user } = useCurrentUser(); return ( ); }; ``` ## Rendering toasts when new notifications come in Our `KnockFeedProvider` exposes a `useKnockFeed` hook, which will return a `feedClient` we can use to bind to and receive real-time notifications being received on our feed. ```jsx import toast, { Toaster } from "react-hot-toast"; import { useEffect } from "react"; import { useKnockFeed } from "@knocklabs/react"; const NotificationToaster = () => { const { feedClient } = useKnockFeed(); const onNotificationsReceived = ({ items }) => { // Whenever we receive a new notification from our real-time stream, show a toast // (note here that we can receive > 1 items in a batch) items.forEach((notification) => { //Use toast.custom to render the HTML content of the notification toast.custom(
, { id: notification.id }, ); }); // Optionally, you may want to mark them as "seen" as well feedClient.markAsSeen(items); }; useEffect(() => { // Receive all real-time notifications on our feed feedClient.on("items.received.realtime", onNotificationsReceived); // Cleanup return () => feedClient.off("items.received.realtime", onNotificationsReceived); }, [feedClient]); return ; }; export default NotificationToaster; ``` ## Wrapping up We can then test our workflow using the built-in test runner in Knock to produce messages, which will be received and displayed as a toast in your application. ## Inbox How to build a notification inbox powered by Knock and React. --- title: "Inbox" metaTitle: "Inbox component for React" metaDescription: How to build a notification inbox component powered by Knock and React. description: How to build a notification inbox powered by Knock and React. section: Building in-app UI --- Our `@knocklabs/react` library can be used to build full-page inbox experiences, too. This page covers how to create a basic inbox for your users in just a few minutes. ## Getting started To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) To find the channel ID for your in-app channel(s), navigate to{" "} Channels and sources under the account settings section of your Knock dashboard, click on your in-app feed channel, and copy the channel ID. } /> ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Rendering the notification inbox Here we're using the `KnockProvider` to authenticate our current user and get access to the Knock JavaScript client. Then we use the `KnockFeedProvider` to connect them to a Knock In-app Feed Channel. We then render the `NotificationFeed` component to give us a simple, complete notification inbox page. ```jsx import { KnockFeedProvider, NotificationFeed } from "@knocklabs/react"; const NotificationInbox = () => { // An example of fetching the current authenticated user const { user } = useCurrentUser(); return ( {/* Optionally, use the KnockFeedProvider to connect an in-app feed */} ); }; ``` ## Next steps - [Common recipes for customizing the feed](/in-app-ui/react/feed#common-recipes) - [Using custom UI for a notification page](/in-app-ui/react/custom-notifications-ui) - [Complete reference for the React library](/in-app-ui/react/sdk/reference) ## Card How to ship an inline card using our pre-built guides component. --- title: "Card" metaTitle: "Card component for React" metaDescription: How to ship an inline card in React using our pre-built guides component. description: How to ship an inline card using our pre-built guides component. tags: ["card", "guides"] section: Building in-app UI --- Our `@knocklabs/react` library comes with a pre-built card component you can drop into your application. The card component enables you to display contextual information, tips, or interactive content inline within your application's content flow, providing users with relevant guidance without disrupting their current task. ## Getting started To use the card component, you'll need: - [An account on Knock](https://dashboard.knock.app) - A guide channel set up in your Knock dashboard - A guide created using the "Card" [message type](/in-app-ui/message-types) Note: You must be on @knocklabs/react version 0.7.31 or higher to use Knock guides. } /> ### Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Basic usage First, wrap your application with the `KnockProvider` and `KnockGuideProvider`. The `KnockGuideProvider` requires a `channelId`. You can find your guide channel ID on the integrations page in the dashboard under "Channels". ```tsx title="Setup the KnockGuideProvider within your product." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react"; import { useCurrentUser } from "@/lib/hooks"; const MyApplication = () => { // Get your authenticated current user const currentUser = useCurrentUser(); return ( {/* Rest of your app */} ); }; ``` Import Knock's pre-built `Card` component and place it inside the `KnockGuideProvider` wherever you want contextual guidance to appear. The `` component is most effective when positioned inline within your content where it can provide relevant information at the right moment. ```tsx title="Add the card within your page content." import { Card } from "@knocklabs/react"; const Sidebar = () => { return ( ); }; const MyPage = () => { return (

Welcome to Dashboard

{/* Your page content */}
); }; ```
The `Card` component will mount automatically when a user becomes eligible for a guide created using the `card` message type. Remember, for a user to be eligible, they must match the targeting rules of the guide, and they must be in a page in your application that matches the activation rules of the guide. You can learn more about guide targeting and activation rules in the [creating guides](/in-app-ui/guides/create-guides) page.
## Working with card variants The pre-built card message type supports three variants for different use cases: - **Default.** A simple card with content and optional dismiss functionality. - **Interactive.** A card with action buttons for user interaction. - **Compact.** A smaller card variant for less prominent guidance. ## Handling user engagement The card component handles user engagement tracking automatically. Here's an overview of what user behavior maps to which engagement statuses tracked in Knock. - **Seen.** The card has been rendered to (seen by) the user. - **Interacted.** The user has interacted with (clicked) the card or its action buttons. Dismissing the card does not count as an interaction. - **Archived.** The card has been archived (dismissed) by the user. ## Styling your card Knock provides multiple levels of customization for the card component, from simple theming to complete custom implementations. Choose the approach that best fits your needs: ### CSS variable theming The easiest way to customize the card's appearance is by overriding CSS variables. Knock provides CSS variables prefixed with `--knock-guide-` that are specifically designed for theming all of Knock's pre-built guide components. ```css title="Theme your card with CSS variables" :root { --knock-guide-accent: #your-brand-color; --knock-guide-background: #your-background-color; --knock-guide-text: #your-text-color; --knock-guide-border: #your-border-color; --knock-guide-border-radius: 8px; --knock-guide-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } ``` This approach enables you to quickly match your brand colors and basic styling without diving into complex CSS overrides. The `--knock-guide-accent` variable is particularly useful for theming buttons and interactive elements. ### CSS class overrides For more granular control, you can override the specific CSS classes used by the card component. All Knock guide components use classes that start with prefixes specific to the component type. Note: You may need to add{" "} !important to your CSS overrides to ensure they take precedence over the component's default styles. } /> ```css title="Override card CSS classes" /* Card container */ .knock-guide-card { background: white; border: 1px solid var(--knock-guide-border); border-radius: 12px; padding: 20px; margin: 16px 0; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; } /* Card header */ .knock-guide-card__header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 12px; } .knock-guide-card__headline { font-size: 18px; font-weight: 600; line-height: 1.4; color: var(--knock-guide-title-color); margin: 0; } /* Card message content */ .knock-guide-card__message { flex: 1; display: flex; flex-direction: column; align-items: flex-start; } .knock-guide-card__title { font-size: 16px; font-weight: 600; line-height: 1.4; color: var(--knock-guide-title-color); margin-bottom: 8px; } .knock-guide-card__body { font-size: 14px; line-height: 1.5; color: var(--knock-guide-body-color); margin-bottom: 16px; } /* Card image */ .knock-guide-card__img { display: block; max-width: 100%; height: auto; margin-bottom: 16px; border-radius: 8px; } /* Card actions */ .knock-guide-card__actions { display: flex; align-items: center; justify-content: flex-start; gap: 12px; margin-top: auto; } .knock-guide-card__action { padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; text-decoration: none; cursor: pointer; border: 1px solid transparent; background: var(--knock-guide-accent); color: white; } .knock-guide-card__action--secondary { background: transparent; color: var(--knock-guide-accent); border: 1px solid var(--knock-guide-accent); } /* Close button */ .knock-guide-card__close { background: transparent; border: none; cursor: pointer; padding: 8px; color: var(--knock-guide-text-muted); } ``` ### Individual subcomponents For maximum flexibility while still leveraging Knock's functionality, you can use the individual subcomponents to compose your own card. This approach gives you full control over the layout and styling while maintaining the guide behavior: ```tsx title="Compose your own card with subcomponents" import { useGuide, CardContainer, CardHeader, CardTitle, CardBody, CardFooter, CardButton, CardCloseButton, } from "@knocklabs/react"; import { useEffect } from "react"; const CustomCard = () => { const { step } = useGuide({ type: "card" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return ( {step.content.title} step.markAsArchived()} style={{ color: "rgba(255, 255, 255, 0.8)" }} />

{step.content.body}

{step.content.actions?.map((action, index) => ( { step.markAsInteracted(); // Handle action }} style={{ background: "rgba(255, 255, 255, 0.2)", color: "white", border: "1px solid rgba(255, 255, 255, 0.3)", borderRadius: "8px", padding: "10px 20px", }} > {action.label} ))}
); }; ``` This approach is ideal when you want to customize a piece of the pre-built component or change its structure. ## Building your own component For complete control over both functionality and appearance, you can build your own card component using the `useGuide` hook. This approach gives you the most flexibility but requires implementing all the card behavior yourself: ```tsx title="Building your own card component" import { useEffect } from "react"; import { useGuide } from "@knocklabs/react"; const CustomCard = () => { const { step } = useGuide({ type: "card" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return (

{step.content.title}

{step.content.body}

{step.content.primary_button && ( )}
); }; ``` Note: If your custom card component needs any fields not included in our pre-built card message type and its variants, you'll need to archive the pre-built card message type and create your own. } /> ## Related links - [Creating guides](/in-app-ui/guides/create-guides) - [Rendering guides](/in-app-ui/guides/render-guides) - [Message types](/in-app-ui/message-types) - [React SDK reference](/in-app-ui/react/sdk/reference) ## Banner How to ship an in-app notification banner using our pre-built guides component. --- title: "Banner" metaTitle: "Banner component for React" metaDescription: How to ship an in-app notification banner in React using our pre-built guides component. description: How to ship an in-app notification banner using our pre-built guides component. tags: ["banner", "guides", "alert"] section: Building in-app UI --- Our `@knocklabs/react` library comes with a pre-built banner component you can drop into your application. The banner component enables you to display important notifications, alerts, or announcements in a prominent position at the top of your application's content area, ensuring users see critical information without interrupting their workflow. ## Getting started To use the banner component, you'll need: - [An account on Knock](https://dashboard.knock.app) - A guide channel set up in your Knock dashboard - A guide created using the "Banner" [message type](/in-app-ui/message-types) Note: You must be on @knocklabs/react version 0.7.31 or higher to use Knock guides. } /> ### Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Basic usage First, wrap your application with the `KnockProvider` and `KnockGuideProvider`. The `KnockGuideProvider` requires a `channelId`. You can find your guide channel ID on the integrations page in the dashboard under "Channels". ```tsx title="Setup the KnockGuideProvider within your product." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react"; import { useCurrentUser } from "@/lib/hooks"; const MyApplication = () => { // Get your authenticated current user const currentUser = useCurrentUser(); return ( {/* Rest of your app */} ); }; ``` Import Knock's pre-built `Banner` component and place it inside the `KnockGuideProvider` at the top of your page or layout. The `` component is most effective when positioned prominently at the top of the content area where users will naturally notice it. ```tsx title="Add the banner at the top of your page or layout." import { Banner } from "@knocklabs/react"; const MyLayout = ({children}) => { return (
My App Header
{children}
); }; ```
The `Banner` component will mount automatically when a user becomes eligible for a guide created using the `banner` message type. Remember, for a user to be eligible, they must match the targeting rules of the guide, and they must be in a page in your application that matches the activation rules of the guide. You can learn more about guide targeting and activation rules in the [creating guides](/in-app-ui/guides/create-guides) page.
## Working with banner variants The pre-built banner message type supports three variants for different use cases: - **Default.** A banner with just text content. - **Single action.** A banner with a single action button. - **Multi action.** A banner with multiple action buttons. ## Handling user engagement The banner component handles user engagement tracking automatically. Here's an overview of what user behavior maps to which engagement statuses tracked in Knock. - **Seen.** The banner has been rendered to (seen by) the user. - **Interacted.** The user has interacted with (clicked) the banner or its action buttons. Dismissing the banner does not count as an interaction. - **Archived.** The banner has been archived (dismissed) by the user. ## Styling your banner Knock provides multiple levels of customization for the banner component, from simple theming to complete custom implementations. Choose the approach that best fits your needs. ### CSS variable theming The easiest way to customize the banner's appearance is by overriding CSS variables. Knock provides CSS variables prefixed with `--knock-guide-` that are specifically designed for theming all of Knock's pre-built guide components. ```css title="Theme your banner with CSS variables" :root { --knock-guide-accent: #your-brand-color; --knock-guide-background: #your-background-color; --knock-guide-text: #your-text-color; --knock-guide-border: #your-border-color; --knock-guide-border-radius: 8px; --knock-guide-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } ``` This approach enables you to quickly match your brand colors and basic styling without diving into complex CSS overrides. The `--knock-guide-accent` variable is particularly useful for theming buttons and interactive elements. ### CSS class overrides For more granular control, you can override the specific CSS classes used by the banner component. All Knock guide components use classes that start with prefixes specific to the component type. Note: You may need to add{" "} !important to your CSS overrides to ensure they take precedence over the component's default styles. } /> ```css title="Override banner CSS classes" /* Banner container */ .knock-guide-banner { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px 24px; border-radius: 8px; margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; } /* Banner message content */ .knock-guide-banner__message { flex: 1; min-width: 0; } .knock-guide-banner__title { font-size: 16px; font-weight: 600; line-height: 1.4; margin-bottom: 4px; color: var(--knock-guide-title-color); } .knock-guide-banner__body { font-size: 14px; line-height: 1.5; color: var(--knock-guide-body-color); } .knock-guide-banner__body p:first-child { margin-top: 0; } .knock-guide-banner__body p:last-child { margin-bottom: 0; } /* Banner actions */ .knock-guide-banner__actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .knock-guide-banner__action { padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; text-decoration: none; cursor: pointer; border: 1px solid transparent; background: var(--knock-guide-accent); color: white; } .knock-guide-banner__action--secondary { background: transparent; color: var(--knock-guide-accent); border: 1px solid var(--knock-guide-accent); } /* Close button */ .knock-guide-banner__close { background: transparent; border: none; cursor: pointer; padding: 8px; color: var(--knock-guide-text-muted); } ``` ### Individual subcomponents For maximum flexibility while still leveraging Knock's functionality, you can use the individual subcomponents to compose your own banner. This approach gives you full control over the layout and styling while maintaining the guide behavior: ```tsx title="Compose your own banner with subcomponents" import { useGuide, BannerContainer, BannerContent, BannerText, BannerActions, BannerButton, BannerCloseButton, } from "@knocklabs/react"; import { useEffect } from "react"; const CustomBanner = () => { const { step } = useGuide({ type: "banner" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return ( {step.content.title}

{step.content.body}

{step.content.actions?.map((action, index) => ( { step.markAsInteracted(); // Handle action }} style={{ background: "rgba(255, 255, 255, 0.2)", color: "white", padding: "6px 12px", borderRadius: "4px", border: "none", }} > {action.label} ))} step.markAsArchived()} style={{ background: "transparent", color: "rgba(255, 255, 255, 0.8)", border: "none", padding: "4px", }} > ×
); }; ``` This approach is ideal when you want to customize a piece of the pre-built component or change its structure. ## Building your own component For complete control over both functionality and appearance, you can build your own banner component using the `useGuide` hook. This approach gives you the most flexibility but requires implementing all the banner behavior yourself: ```tsx title="Building your own banner component" import { useEffect } from "react"; import { useGuide } from "@knocklabs/react"; const CustomBanner = () => { const { step } = useGuide({ type: "banner" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return (

{step.content.title}

{step.content.body}

{step.content.primary_button && ( )}
); }; ``` Note: If your custom banner component needs any fields not included in our pre-built banner message type and its variants, you'll need to archive the pre-built banner message type and create your own. } /> ## Related links - [Creating guides](/in-app-ui/guides/create-guides) - [Rendering guides](/in-app-ui/guides/render-guides) - [Message types](/in-app-ui/message-types) - [React SDK reference](/in-app-ui/react/sdk/reference) ## Modal How to ship an in-app notification modal using our pre-built guides component. --- title: "Modal" metaTitle: "Modal component for React" metaDescription: How to ship an in-app notification modal in React using our pre-built guides component. description: How to ship an in-app notification modal using our pre-built guides component. tags: ["modal", "guides"] section: Building in-app UI --- Our `@knocklabs/react` library comes with a pre-built modal component you can drop into your application. The modal component enables you to display important notifications, announcements, or interactive content in a focused overlay that appears above your application's main content. ## Getting started To use the modal component, you'll need: - [An account on Knock](https://dashboard.knock.app) - An in-app guides channel set up in your Knock dashboard - A guide created using the "Modal" [message type](/in-app-ui/message-types) Note: You must be on @knocklabs/react version 0.7.31 or higher to use Knock guides. } /> ### Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Basic usage First, wrap your application with the `KnockProvider` and `KnockGuideProvider`. The `KnockGuideProvider` requires a `channelId`. You can find your guide channel ID on the integrations page in the dashboard under "Channels". ```tsx title="Setup the KnockGuideProvider within your product." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react"; import { useCurrentUser } from "@/lib/hooks"; const MyApplication = () => { // Get your authenticated current user const currentUser = useCurrentUser(); return ( {/* Rest of your app */} ); }; ``` Import Knock's pre-built `Modal` component and place it inside the `KnockGuideProvider` near the top of your application's component tree. The `` component is most effective when placed in the root layout of your application as it ensures your modal is available to render on every page of your application. ```tsx title="Add the modal near the top of your application." import { Modal } from "@knocklabs/react"; const MyLayout = ({children}) => { return (
My App Header
{children}
); }; ```
The `Modal` component will mount automatically when a user becomes eligible for a guide created using the `modal` message type. Remember, for a user to be eligible, they must match the targeting rules of the guide, and they must be in a page in your application that matches the activation rules of the guide. You can learn more about guide targeting and activation rules in the [creating guides](/in-app-ui/guides/create-guides) page.
## Working with modal variants The pre-built modal message type supports three variants for different use cases: - **Default.** A dismissible modal with no action buttons. - **Single action.** A dismissible modal with a single action button. - **Multi-action.** A dismissible modal with two action buttons. ## Handling user engagement The modal component handles user engagement tracking automatically. Here's an overview of what user behavior maps to which engagement statuses tracked in Knock. - **Seen.** The modal has been rendered to (seen by) the user. - **Interacted.** The user has interacted with (clicked) the modal. Dismissing the modal does not count as an interaction. - **Archived.** The modal has been archived (dismissed) by the user. ## Styling your modal Knock provides multiple levels of customization for the modal component, from simple theming to complete custom implementations. Choose the approach that best fits your needs: ### CSS variable theming The easiest way to customize the modal's appearance is by overriding CSS variables. Knock provides CSS variables prefixed with `--knock-guide-` that are specifically designed for theming all of Knock's pre-built guide components. ```css title="Theme your modal with CSS variables" :root { --knock-guide-accent: #your-brand-color; --knock-guide-background: #your-background-color; --knock-guide-text: #your-text-color; --knock-guide-border: #your-border-color; --knock-guide-border-radius: 8px; --knock-guide-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } ``` This approach enables you to quickly match your brand colors and basic styling without diving into complex CSS overrides. The `--knock-guide-accent` variable is particularly useful for theming buttons and interactive elements. ### CSS class overrides For more granular control, you can override the specific CSS classes used by the modal component. All Knock guide components use classes that start with prefixes specific to the component type. Note: You may need to add{" "} !important to your CSS overrides to ensure they take precedence over the component's default styles. } /> ```css title="Override modal CSS classes" /* Modal overlay */ .knock-guide-modal__overlay { background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); } /* Modal container */ .knock-guide-modal { border-radius: 12px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); max-width: 600px; } /* Modal content areas */ .knock-guide-modal__header { border-bottom: 1px solid var(--knock-guide-border); padding-bottom: 16px; } .knock-guide-modal__title { font-size: 20px; font-weight: 600; color: var(--knock-guide-title-color); } .knock-guide-modal__body { padding: 24px 0; line-height: 1.6; color: var(--knock-guide-body-color); } .knock-guide-modal__actions { display: flex; gap: 12px; justify-content: flex-end; } /* Action buttons */ .knock-guide-modal__action { background: var(--knock-guide-accent); color: white; border: none; border-radius: 6px; padding: 12px 24px; font-weight: 600; cursor: pointer; } .knock-guide-modal__action--secondary { background: transparent; color: var(--knock-guide-accent); border: 2px solid var(--knock-guide-accent); } /* Close button */ .knock-guide-modal__close { background: transparent; border: none; cursor: pointer; padding: 8px; color: var(--knock-guide-text-muted); } /* Image styling */ .knock-guide-modal__img { width: 100%; height: auto; border-radius: 8px; } ``` ### Individual subcomponents For maximum flexibility while still leveraging Knock's functionality, you can use the individual subcomponents to compose your own modal. This approach gives you full control over the layout and styling while maintaining the guide behavior: ```tsx title="Compose your own modal with subcomponents" import { useGuide, ModalOverlay, ModalContainer, ModalHeader, ModalBody, ModalFooter, ModalCloseButton, } from "@knocklabs/react"; import { useEffect } from "react"; const CustomModal = () => { const { step } = useGuide({ type: "modal" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return ( step.markAsArchived()}> e.stopPropagation()} style={{ background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", color: "white", borderRadius: "16px", }} >

{step.content.title}

step.markAsArchived()} />

{step.content.body}

{step.content.actions?.map((action, index) => ( ))}
); }; ``` This approach is ideal when you want to customize a piece of the pre-built component or change its structure. ## Building your own component For complete control over both functionality and appearance, you can build your own modal component using the `useGuide` hook. This approach gives you the most flexibility but requires implementing all the modal behavior yourself: ```tsx title="Building your own modal component" import { useEffect } from "react"; import { useGuide } from "@knocklabs/react"; const CustomModal = () => { const { step } = useGuide({ type: "modal" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return (

{step.content.title}

{step.content.body}

{step.content.primary_button && ( )}
); }; ``` Note: If your custom modal component needs any fields not included in our pre-built modal message type and its variants, you'll need to archive the pre-built modal message type and create your own. } /> ## Related links - [Creating guides](/in-app-ui/guides/create-guides) - [Rendering guides](/in-app-ui/guides/render-guides) - [Message types](/in-app-ui/message-types) - [React SDK reference](/in-app-ui/react/sdk/reference) ## Preferences How to build a complete notification preference center, powered by Knock and React. --- title: "Preferences" metaTitle: "Build a preference center for React" description: How to build a complete notification preference center, powered by Knock and React. section: Building in-app UI --- This page covers how to build a `PreferenceCenter` React component with Knock's preferences API and `@knocklabs/client`. The example manages a user's `default` preference set and provides a starting point that you can customize or extend for your product. For a complete TypeScript implementation, see the Notion feed example. ## Getting started Before you get started, we recommend reading the [preferences overview docs](/preferences/overview) and creating a default `PreferenceSet`[for your environment](/preferences/overview#environment-level-default-preferences) for your environment. The [API reference for preferences](/api-reference/recipients/preferences) can also be helpful. Remember that if you have either a environment or tenant default{" "} PreferenceSet those preferences will be merged with changes a user makes in the UI, with the user-specified changes taking precedence. } /> ### What you'll need To use this example, you'll need [an account on Knock](https://dashboard.knock.app) and you'll need to have [identified a user](/concepts/users). You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - A signed user token from your backend (required in production with enhanced security mode enabled; see [Authentication with enhanced security](/in-app-ui/security-and-authentication#authentication-with-enhanced-security)) ### Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/client ``` ## Modeling our preferences In this example we'll assume the user has a default `PreferenceSet` that contains workflows and workflow categories, each with it's own channel type settings. We'll expose this to our users as a "cross-hatch" so that they can set a preference for each channel type. ```json title="Preference object" { "id": "default", "categories": { "collaboration": { "channel_types": { "email": true, "in_app_feed": true } }, "new-asset": { "channel_types": { "email": false, "in_app_feed": true } } }, "workflows": { "new-comment": { "channel_types": { "email": true } } }, "channel_types": {} } ``` ## Creating our preference center The next step here is to create our preference center component. Create a `PreferenceCenter.jsx` file in your project and add the following import statements to the top of the file. After that, you'll need to create a new instance of the `Knock` client and authenticate it against a user: ```jsx title="Import Knock client & React hooks" import Knock from "@knocklabs/client"; import { useEffect, useState } from "react"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knockClient.authenticate({ id: currentUser.id }, currentUser.knockUserToken); ``` In development environments without enhanced security mode enabled, you can omit the second argument (signed user token) and authenticate with the user id alone. Next we'll create an configuration object that will help us drive the view of our preference center. In some cases, you may want to store values in a `PreferenceSet` that you don't directly expose to users or want to provide more descriptive titles, labels, and descriptions. ```jsx title="Power the preference center view" const PreferenceViewConfig = { RowSettings: { "new-asset": { title: "New Asset", description: "New file uploads in workspaces you're a part of", }, "new-comment": { title: "Comments & mentions", description: "New comments and replies to threads.", }, collaboration: { title: "In-app messages", description: "Messages from other users on the platform", }, }, ChannelTypeLabels: { in_app_feed: "In-app Feed", email: "Email", push: "Push", }, }; ``` In this example, the `RowSettings` object contains entries that map directly to keys in the `PreferenceSet` we modeled in the previous step. Each entry here will surface those settings to the user and provide additional human readable details with `title` and `description`. If you want to modify this for your own project, you can swap the keys inside of `RowSettings` with a key from your default `PreferenceSet` and update the `title` and `description` properties. The `ChannelTypeLabels` object is similar in that its contents determine which channel type settings will be surfaced for each row. Adding additional entries to this object will present more checkboxes for the user, and you can modify the label value by updating the value of a particular key. Copy and paste this `PreferenceViewConfig` object in your component file and make any updates to correspond with the shape of your default `PreferenceSet` and channels. Instead of hardcoding row labels and structure, you can fetch the preference center configuration from Knock. See{" "} dashboard-managed configuration {" "} for more information. } /> Next, we'll create a `PreferenceSettingsRow` component that will display the `title`, `description`, and checkbox toggles for each `SettingsRow` entry: ```jsx title="Display a row for each desired preference setting" function PreferenceSettingsRow({ preferenceType, preferenceKey, channelTypeSettings, onChange, }) { return (

{PreferenceViewConfig.RowSettings[preferenceKey].title}

{PreferenceViewConfig.RowSettings[preferenceKey].description}

{Object.keys(PreferenceViewConfig.ChannelTypeLabels).map( (channelType) => { return (
{ onChange({ preferenceKey, preferenceType, channelTypeSettings: { ...channelTypeSettings, [channelType]: e.target.checked, }, }); }} />
); } )}
); } ``` This component has a lot of functionality built in, so let's unpack what it does. Using the `preferenceKey` parameter, this component renders a section of UI that displays the `title` and `description` properties stored in the `PreferenceViewConfig` under the matching key: ```jsx title="Displaying preference details"

{PreferenceViewConfig.RowSettings[preferenceKey].title}

{PreferenceViewConfig.RowSettings[preferenceKey].description}

``` Next, we'll generate an `input` element tied to each channel type setting for that preference. We do that by looping through the keys of `PreferenceViewConfig.ChannelTypeLabels` to generate a UI element tied to a particular channel and preference setting: ```jsx
{Object.keys(PreferenceViewConfig.ChannelTypeLabels).map((channelType) => { return (
{ onChange({ preferenceKey, preferenceType, channelTypeSettings: { ...channelTypeSettings, [channelType]: e.target.checked, }, }); }} />
); })}
``` This section of UI uses the `channelTypeSettings` passed into the function to drive the `disabled` and `checked` states of the `input` element. These `channelTypeSettings` are the user's existing preferences pulled directly from Knock. By disabling the checkbox if those channel type settings are `undefined` we remove the user's ability to modify that value if it doesn't appear in the default preference set. As the user toggles the state of this `input` it fires an `onChange` event handler that calls a function also passed as a parameter. This function is ultimately what updates the user's preferences in Knock, so we pass a modified value of `channelTypeSettings` that includes the current value of the event target's `checked` property: ```javascript title="Updating preferences onChange" onChange={(e) => { onChange({ preferenceKey, preferenceType, channelTypeSettings: { ...channelTypeSettings, [channelType]: e.target.checked, }, }); }} ```
Now that we have a `PreferencesViewConfig` object to help us drive the shape of our UI and a `PreferenceSettingsRow` to render a row's details an the necessary `inputs`, it's time to compose those elements into an actual `PreferenceCenter` component. This `PreferenceCenter` function should be exported: ```jsx title="Compose a preference center" export default function PreferenceCenter() { //Create some local state to store the user's preferences const [localPreferences, setLocalPreferences] = useState({ id: "default", categories: { collaboration: { channel_types: { email: true, in_app_feed: true, }, }, "new-asset": { channel_types: { email: false, in_app_feed: true, }, }, }, workflows: { "new-comment": { channel_types: { email: true, }, }, }, channel_types: {}, }); //We load the current user's preferences from Knock, and set them to local preferences useEffect(() => { async function fetchPreferences() { const preferences = await knockClient.user.getPreferences(); setLocalPreferences(preferences); } fetchPreferences(); }, [knockClient]); //When a preference setting is changed, we create a new PreferenceSet that //includes the change, update the preferences in Knock, and then update local state const onPreferenceChange = async ({ preferenceKey, preferenceType, channelTypeSettings, }) => { //create a new preference set with local preferences as starting point const preferenceUpdate = { ...localPreferences, }; // Here we'll make updates to the preference set based on the preferenceType // and override existing channelTypeSettings // since Workflow and Category preferences can also be a Boolean, // we'll check if the preferenceKey contains a channel_types object if ( preferenceType === "category" && typeof preferenceUpdate.categories[preferenceKey] === "object" ) { preferenceUpdate.categories[preferenceKey].channel_types = channelTypeSettings; } if ( preferenceType === "workflow" && typeof preferenceUpdate.workflows[preferenceKey] === "object" ) { preferenceUpdate.workflows[preferenceKey].channel_types = channelTypeSettings; } //Next, we upload the new PreferenceSet to Knock for that user const preferences = await knockClient.user.setPreferences(preferenceUpdate); // Set the updated preferences in local state setLocalPreferences(preferences); }; //If we haven't loaded preferences yet, maybe show a spinner if (!localPreferences) { return null; } return (
{Object.keys(localPreferences?.categories).map((category) => { return ( ); })} {Object.keys(localPreferences?.workflows).map((workflow) => { return ( ); })}
); } ``` Let's examine the code in the `PreferenceCenter` component step-by-step to explain what's happening. First, we need to load the current user's preferences from Knock and store them in local state so we can operate on them. We can call the `getPreferences` method on `knockClient.user` to load a user's preferences: ```jsx title="Store current preferences in local state" //Create some local state to store the user's preferences const [localPreferences, setLocalPreferences] = useState(); //We load the current user's preferences from Knock, and set them to local preferences useEffect(() => { async function fetchPreferences() { const preferences = await knockClient.user.getPreferences(); setLocalPreferences(preferences); } fetchPreferences(); }, [knockClient]); ``` Next, we create a function called `onPreferenceChange` that will get passed as the `onChange` parameter to our `PreferenceSettingsRow` component from the previous step. The `onPreferenceChange` function takes a `preferenceKey` argument and an updated `channelTypeSettings` argument: ```jsx title="Update preferences in Knock" //When a preference setting is changed, we create a new PreferenceSet that //includes the change, update the preferences in Knock, and then update local state const onPreferenceChange = async ({ preferenceKey, preferenceType, channelTypeSettings, }) => { //create a new preference set with local preferences as starting point const preferenceUpdate = { ...localPreferences, }; // Here we'll make updates to the preference set based on the preferenceType // and override existing channelTypeSettings // since Workflow and Category preferences can also be a Boolean, // we'll check if the preferenceKey contains a channel_types object if ( preferenceType === "category" && typeof preferenceUpdate.categories[preferenceKey] === "object" ) { preferenceUpdate.categories[preferenceKey].channel_types = channelTypeSettings; } if ( preferenceType === "workflow" && typeof preferenceUpdate.workflows[preferenceKey] === "object" ) { preferenceUpdate.workflows[preferenceKey].channel_types = channelTypeSettings; } //Next, we upload the new PreferenceSet to Knock for that user const preferences = await knockClient.user.setPreferences(preferenceUpdate); // Set the updated preferences in local state setLocalPreferences(preferences); }; ``` Based on the `preferenceType` of the update, we'll overwrite any existing preferences stored under that key and use the `setPreferences` method of `knockClient.user` to update those preferences in Knock. We then set the updated preferences back to local state using `setLocalPreferences` to keep our UI in sync with what is stored in Knock. Lastly, we actually render our `PreferenceSettingsRow` components: ```jsx
{Object.keys(localPreferences?.categories).map((category) => { return ( ); })} {Object.keys(localPreferences?.workflows).map((workflow) => { return ( ); })}
``` Here we loop through each workflow or category key stored in a user's preferences and pass in the existing `channelTypeSettings` which will power the state of that row's checkbox `inputs` and the `onPreferenceChange` callback to update a user's preferences in Knock. You should now have a working preference center 🎉
## Completed preference center Knock's preference model is very flexible, but you should find that the component below will satisfy most of your preference center needs and can easily be used as a starting point for your own preference center. ```jsx title="Completed preference center" import Knock from "@knocklabs/client"; import { useEffect, useState } from "react"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knockClient.authenticate({ id: currentUser.id }, currentUser.knockUserToken); // Here we create a view config object, this helps us customize the interface // and choose which preference options we want to display to the user const PreferenceViewConfig: Record = { RowSettings: { "new-asset": { title: "New Asset", description: "New file uploads in workspaces you're a part of", }, "new-comment": { title: "Comments & mentions", description: "New comments and replies to threads.", }, collaboration: { title: "In-app messages", description: "Messages from other users on the platform", }, }, ChannelTypeLabels: { in_app_feed: "In-app Feed", email: "Email", push: "Push", }, }; // The PreferenceSettingsRow component is what actually displays the UI to manipulate function PreferenceSettingsRow({ preferenceType, preferenceKey, channelTypeSettings, onChange, }) { return (

{PreferenceViewConfig.RowSettings[preferenceKey].title}

{PreferenceViewConfig.RowSettings[preferenceKey].description}

{Object.keys(PreferenceViewConfig.ChannelTypeLabels).map( (channelType) => { return (
{ onChange({ preferenceKey, preferenceType, channelTypeSettings: { ...channelTypeSettings, [channelType]: e.target.checked, }, }); }} />
); }, )}
); } export default function PreferenceCenter() { //Create some local state to store the user's preferences const [localPreferences, setLocalPreferences] = useState({ id: "default", categories: { collaboration: { channel_types: { email: true, in_app_feed: true, }, }, "new-asset": { channel_types: { email: false, in_app_feed: true, }, }, }, workflows: { "new-comment": { channel_types: { email: true, }, }, }, channel_types: {}, }); //We load the current user's preferences from Knock, and set them to local preferences useEffect(() => { async function fetchPreferences() { const preferences = await knockClient.user.getPreferences(); setLocalPreferences(preferences); } fetchPreferences(); }, [knockClient]); //When a preference setting is changed, we create a new PreferenceSet that //includes the change, update the preferences in Knock, and then update local state const onPreferenceChange = async ({ preferenceKey, preferenceType, channelTypeSettings, }) => { //create a new preference set with local preferences as starting point const preferenceUpdate = { ...localPreferences, }; // Here we'll make updates to the preference set based on the preferenceType // and override existing channelTypeSettings // since Workflow and Category preferences can also be a Boolean, // we'll check if the preferenceKey contains a channel_types object if ( preferenceType === "category" && typeof preferenceUpdate.categories[preferenceKey] === "object" ) { preferenceUpdate.categories[preferenceKey].channel_types = channelTypeSettings; } if ( preferenceType === "workflow" && typeof preferenceUpdate.workflows[preferenceKey] === "object" ) { preferenceUpdate.workflows[preferenceKey].channel_types = channelTypeSettings; } //Next, we upload the new PreferenceSet to Knock for that user const preferences = await knockClient.user.setPreferences(preferenceUpdate); // Set the updated preferences in local state setLocalPreferences(preferences); }; if (!localPreferences) { return null; } return (
{Object.keys(localPreferences?.categories).map((category) => { return ( ); })} {Object.keys(localPreferences?.workflows).map((workflow) => { return ( ); })}
); } ``` ## SlackKit How to let users authorize and select Slack channels in your app with Knock's SlackKit. --- title: "Building a Slack integration in React" description: How to let users authorize and select Slack channels in your app with Knock's SlackKit. section: SlackKit --- Our `@knocklabs/react` library comes with pre-built components for allowing your users to connect their Slack workspace to Knock and select the channels they want to be notified on. SlackKit manages your OAuth connection and tokens, helps your customers select which channels they want to receive notifications in, and integrates seamlessly with the rest of Knock. ## Getting started To get started you'll need a [Knock account](https://dashboard.knock.app), a [Slack channel connected to a Slack app](/in-app-ui/react/slack-kit), and a workflow with a Slack channel step. Follow step-by-step instructions for your use case: - [Sending messages to public and private](/integrations/chat/slack/sending-a-message-to-channels) channels in your customer's Slack workspace - [Sending direct messages](/integrations/chat/slack/sending-a-direct-message) to users in your customer's Slack workspace ## Using SlackKit components Once you've provided access to the necessary data, you can drop Knock's pre-built components into your React application to immediately set up Slack authorization and channel selection for your users. [See the reference](/in-app-ui/react/sdk/reference#slack-components) for full documentation of these components. ### Add the providers In order to give your components the data they need, they must be wrapped in the `KnockSlackProvider`. We recommend putting this high in your component tree so that any Slack components that you use will be rendered within it. The Slack provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockSlackProvider` gives your components access to the status of the connection to your Slack app, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Add the components #### SlackAuthButton & Container
The SlackAuthButton component with SlackAuthContainer
Your users will give your Slack app access to their own Slack workspaces via the `SlackAuthButton`. This button can be used on its own, or nested in the `SlackAuthContainer` for a bigger visual footprint. Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with SlackAuthButton" // Without container // With container } /> ``` The `SlackAuthButton` maps a tenant in your product to a customer's Slack workspace. This means in most cases you'll just need a single instance of the `SlackAuthButton`. Remember to consider which roles in your application can access the `SlackAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button/container in the settings area of your product. #### SlackChannelCombobox
The SlackChannelCombobox component showing connected channels
This combobox contains the list of channels in the connected Slack workspace. Users will use this combobox to search and select a channel (or more than one channel) to be notified when an event in your application occurs, for example a comment on a video. They can also use this combobox to deselect a connected channel. The combobox automatically shows which channels are already connected, and gives users an easy way to remove them as well. Add your combobox to your application where you'd like the user to select channels to notify: ```javascript title="The SlackChannelCombobox connects an object to one or more channels" ```
  • The combobox will only show channels for Slack workspaces with fewer than 1000 channels (public and private; not including archived). If there are more, the combobox will turn into a text input that accepts a channel ID to connect.
  • The combobox will only show private channels from your users' Slack workspaces if your Slack app has been invited to those channels.
  • The combobox does not show individual users for Slack direct messages.
} /> ### Complete sample code Here's an example of these components in a React application. ```javascript title="Store Knock and Slack credentials as .env vars" // .env KNOCK_PUBLIC_API_KEY = "pk_test_12345"; KNOCK_SLACK_CHANNEL_ID = "2e0e37c3-751b-4be5-a684-8296009e960e"; SLACK_APP_CLIENT_ID = "1354596639525.6166309709204"; ```
```javascript title="Providers wrap your UI components" ```
```javascript title="Render your SlackAuthButton inside of KnockSlackProvider" const NotificationSettings = () => { return ( } /> ); }; ```
```javascript title="Render a SlackChannelCombobox for each object" const VideoProjectsPage = ({videos}) => { return ( videos.map(video => { return }) ) } const VideoPage = ({video}) => { return (
{video.name}
{video.content}
{video.comments}
) } ```
## Using SlackKit headless If you need custom designs or want to display additional information around your Slack integration, you don't need to use Knock's pre-built components to take advantage of SlackKit. SlackKit exposes three levels of support: React hooks, client functions, and API endpoints. ### Hooks You can use the [Slack React hooks](/in-app-ui/react/sdk/reference#slack-hooks) under the hood to access and set Slack integration data with your own components. All of them are available from the `@knocklabs/react-core` package. All of them must still be nested under the `KnockSlackProvider` to work. To use a hook in your component, all you need to do is import it and pass it the necessary params, and then you can use the data and functions returned in each to pass to your own component UI. For example, you may want to provide your users a list of the connected channels outside of the `SlackChannelCombobox`. Let's look at how you can combine two hooks to accomplish that. #### Building a list of connected channels First, make sure your component that you're using the hooks in is nested somewhere under the `KnockSlackProvider`. Then, import both the `useSlackChannels` and `useConnectedSlackChannels` hooks, as we'll be combining data from each to build our list. We'll combine our data to create a list of channels that has a name and an `isPrivate` attribute so we can conditionally show a lock icon if the channel is marked private. Here's some sample code for our final list: ```javascript title="Fetch Slack channels without the SlackChannelCombobox" import { useConnectedSlackChannels, useSlackChannels, } from "@knocklabs/react-core"; const ConnectedChannelsList = ({ slackChannelsRecipientObject }) => { const { data: slackChannels } = useSlackChannels(); const { data: connectedChannels } = useConnectedSlackChannels({ slackChannelsRecipientObject, }); const slackChannelsMap = new Map( slackChannels.map((channel) => [channel.id, channel]), ); const hydratedConnectedChannels = connectedChannels.map( (connectedChannel) => { const channel_id = connectedChannel.channel_id; return { id: channel_id, name: slackChannelsMap[channel_id].name, isPrivate: slackChannelsMap[channel_id].is_private, }; }, ); return (
    {hydratedConnectedChannels.map((channel) => { return (
  • {channel.name} {channel.isPrivate && }
  • ); })}
); }; ``` ### Client functions If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the @knocklabs/client library as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: - `knock.slack.authCheck`: Get the status of Slack authorization - `knock.slack.getChannels`: Get a list of Slack channels for the given tenant - `knock.slack.revokeAccessToken`: Disables an access token with Slack and removes it from the tenant - `knock.objects.getChannelData`: Use this to get the connected channels stored as channel data on the recipient object - `knock.objects.setChannelData`: Use this to set the connected channels for a recipient object or an access token for a tenant ### API endpoints Lastly, you can interact directly with the API endpoints for all of the above functionality. Here are the endpoints used in SlackKit that you would need to support an implementation of the managed UI: - [Slack auth check](/api-reference/providers/slack/check_auth): status of Slack authorization - [Slack channels](/api-reference/providers/slack/list_channels): list of Slack channels for the given workspace - [Slack revoke token](/api-reference/providers/slack/revoke_access): revoke Slack app token access and remove from tenant - [Get channel data](/api-reference/objects/get_channel_data): get channel data for your recipient object, which gives you access to the connected slack channels - [Set channel data](/api-reference/objects/set_channel_data): set channel data for your recipient object, which allows you to set connected slack channels ## Resources access grants The only access you'll need to manage when using SlackKit are grants for your users to interact with their [Tenants](/concepts/tenants) and [Objects](/concepts/objects) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. ### With the Node SDK You'll need to generate a token for your user that includes access to the tenant storing the Slack access token as well as any recipient objects storing Slack channel data described in [SlackKit ](/in-app-ui/react/slack-kit#channel-data-requirements). If you need to enable access to multiple recipient objects, you can include multiple grants in the user token. Example: - Tenant ID: `jurassic-park` - Recipient object collection: `videos` - Recipient object ID: `dinosaurs-loose` Using the above example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "jurassic-park" }, [ Grants.SlackChannelsRead, ]), buildUserTokenGrant( { type: "object", id: "dinosaurs-loose", collection: "videos" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), buildUserTokenGrant( { type: "object", id: "raptor-feeding-info", collection: "videos" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockSlackProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Other languages If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an example of using Joken for the Elixir library. You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: ```json { "sub": "user123", "grants": { "https://api.knock.app/v1/objects/$tenants/jurassic-park": { "slack/channels_read": [{}] }, "https://api.knock.app/v1/objects/videos/dinosaurs-loose": { "channel_data/read": [{}], "channel_data/write": [{}] } } } ``` Continue reading for a deeper dive on access and how these grants are structured. ### Grants in the user token You may already be familiar with generating a user token to be used with your public API key when making client side calls if you've used [authentication with enhanced security](/in-app-ui/security-and-authentication#authentication-with-enhanced-security). You'll use the same process to generate the user token as described here, including signing it with an RS256 algorithm using your private signing key, but you'll also be sending a list of grants that the user needs to work with SlackKit. The two resources you'll be granting access to are: - **The tenant**: the user needs access to this because this is where Knock stores the access token to Slack that will be used when communicating with the Slack API - **The recipient object**: the user needs access to this because this is where Knock is storing the connected Slack channels as channel data on the object These resources need different permissions. Here are the permissions needed for each: - **Tenant**: reading slack channels - **Recipient Object**: reading channel data; writing channel data ### How the grants are structured Resource access grants in Knock are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. How to read a resource grant: ```json { "Knock endpoint of the resource": { "Name of access type being granted/Specific permission being granted": [ "List of exceptions" ] } } ``` So to grant access for a user to read the channel data of object `dinosaurs-loose` in the `videos` collection, your grant would look like this: ```json { "https://api.knock.app/v1/objects/videos/dinosaurs-loose": { "channel_data/read": [{}] } } ``` ### Availability of resource access grants Currently these grants are only implemented for use by SlackKit as described in this doc, and since exceptions are not used for these they will not be respected. ## TeamsKit How to let users connect to your Microsoft Teams integration with Knock's TeamsKit. --- title: "Building a Microsoft Teams integration in React" description: How to let users connect to your Microsoft Teams integration with Knock's TeamsKit. section: TeamsKit --- Our `@knocklabs/react` library comes with pre-built components for allowing your users to connect their Microsoft Teams instances to Knock and select the channels they want to be notified on. TeamsKit manages your OAuth connection and tokens, helps your customers select which channels they want to receive notifications in, and integrates seamlessly with the rest of Knock. ## Getting started To get started you'll need a [Knock account](https://dashboard.knock.app), a [Microsoft Teams channel connected to a Microsoft Teams bot](/integrations/chat/microsoft-teams/overview), a [Graph API-enabled application](/integrations/chat/microsoft-teams/overview#configure-graph-api-in-microsoft-entra), and a workflow with a Microsoft Teams channel step. Follow step-by-step instructions for your use case: - [Sending messages to public channels](/integrations/chat/microsoft-teams/sending-a-message-to-channels) in your customers' Microsoft Teams instances - [Sending direct messages](/integrations/chat/microsoft-teams/sending-a-direct-message) to users in your customers' Microsoft Teams instances ## Using TeamsKit components Once you've provided access to the necessary data, you can drop Knock's pre-built components into your React application to immediately set up Microsoft Teams authorization and channel selection for your users. [See the reference](/in-app-ui/react/sdk/reference#microsoft-teams-components) for full documentation of these components. ### Add the providers In order to give your components the data they need, they must be wrapped in the `KnockMsTeamsProvider`. We recommend putting this high in your component tree so that any TeamsKit components that you use will be rendered within it. The Microsoft Teams provider goes inside of the `KnockProvider`. Your hierarchy will look like this: ```javascript title="Wrap your UI components in data providers" {child components} ``` The `KnockMsTeamsProvider` gives your components access to the status of the connection to your Microsoft Teams bot, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error. ### Add the components #### MsTeamsAuthButton & Container
The MsTeamsAuthButton component with MsTeamsAuthContainer
Your users will connect your Microsoft Teams bot to their own Microsoft Entra tenants via the `MsTeamsAuthButton`. This button can be used on its own, or nested in the `MsTeamsAuthContainer` for a bigger visual footprint. Here's an example of how to use them: ```javascript title="Initiate OAuth and display auth state with MsTeamsAuthButton" // Without container // With container } /> ``` The `MsTeamsAuthButton` maps a tenant in your product to a customer's Microsoft Entra tenant. This means in most cases you'll just need a single instance of the `MsTeamsAuthButton`. Remember to consider which roles in your application can access the `MsTeamsAuthButton` component. Knock does not control access to the component. In most cases, you'll add this connect button/container in the settings area of your product. The MsTeamsAuthButton component does not automatically install your Microsoft Teams bot into a team or users' personal scope. Your users will need to{" "} manually install your bot {" "} before you can send messages to users and channels. Alternatively, provide instructions to your app's admins to{" "} preinstall your bot for all Microsoft Teams users in their organization , install your bot into existing teams , and preinstall your bot when new teams are created . } /> #### MsTeamsChannelCombobox
The MsTeamsChannelCombobox component showing connected channels
This combobox contains the list of teams and channels belonging to the connected Microsoft Entra tenant. Users will use this combobox to search and select a channel (or more than one channel) to be notified when your application triggers a workflow with a Teams channel step. They can also use this combobox to deselect a connected channel. The combobox automatically shows which channels are already connected, and gives users an easy way to remove them as well. Add your combobox to your application where you'd like the user to select channels to notify: ```javascript title="The MsTeamsChannelCombobox connects an object to one or more channels" ```
  • The combobox will only show public channels. Microsoft Teams bots do not support sending messages to private channels.
  • The combobox does not show individual users for Microsoft Teams direct messages.
} /> ### Complete sample code Here's an example of these components in a React application. ```javascript title="Store Knock and Microsoft Teams credentials as .env vars" // .env KNOCK_PUBLIC_API_KEY = "pk_test_12345"; KNOCK_MS_TEAMS_CHANNEL_ID = "2e0e37c3-751b-4be5-a684-8296009e960e"; GRAPH_API_CLIENT_ID = "f1b85cf4-58e1-4cef-8d3f-ce6ccf60734d"; ```
```javascript title="Providers wrap your UI components" ```
```javascript title="Render your MsTeamsAuthButton inside of KnockMsTeamsProvider" const NotificationSettings = () => { return ( } /> ); }; ```
```javascript title="Render an MsTeamsChannelCombobox for each object" const VideoProjectsPage = ({videos}) => { return ( videos.map(video => { return }) ) } const VideoPage = ({video}) => { return (
{video.name}
{video.content}
{video.comments}
) } ```
## Using TeamsKit headless If you need custom designs or want to display additional information around your Microsoft Teams integration, you don't need to use Knock's pre-built components to take advantage of TeamsKit. TeamsKit exposes three levels of support: React hooks, client functions, and API endpoints. ### Hooks You can use the [Microsoft Teams React hooks](/in-app-ui/react/sdk/reference#microsoft-teams-hooks) under the hood to access and set Microsoft Teams integration data with your own components. All of them are available from the `@knocklabs/react-core` package. All of them must still be nested under the `KnockMsTeamsProvider` to work. To use a hook in your component, all you need to do is import it and pass it the necessary params, and then you can use the data and functions returned in each to pass to your own component UI. ### Client functions If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the @knocklabs/client library as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: - `knock.msTeams.authCheck`: Get the status of Microsoft Teams authorization - `knock.msTeams.getTeams`: Get a list of teams in the connected Microsoft Entra tenant - `knock.msTeams.getChannels`: Get a list of Microsoft Teams channels within a single team - `knock.msTeams.revokeAccessToken`: Removes the Microsoft Entra tenant ID from the tenant - `knock.objects.getChannelData`: Use this to get the connected channels stored as channel data on the recipient object - `knock.objects.setChannelData`: Use this to set the connected channels for a recipient object or an access token for a tenant ### API endpoints Lastly, you can interact directly with the API endpoints for all of the above functionality. Here are the endpoints used in TeamsKit that you would need to support an implementation of the managed UI: - [Microsoft Teams auth check](/api-reference/providers/ms_teams/check_auth): status of Microsoft Teams authorization - [Microsoft Teams teams](/api-reference/providers/ms_teams/list_teams): list of teams in the connected Microsoft Entra tenant - [Microsoft Teams channels](/api-reference/providers/ms_teams/list_channels): list of Microsoft Teams channels within a single team - [Microsoft Teams revoke access](/api-reference/providers/ms_teams/revoke_access): remove a Microsoft Entra tenant ID from a Knock tenant - [Get channel data](/api-reference/objects/get_channel_data): get channel data for your recipient object, which gives you access to the connected Microsoft Teams channels - [Set channel data](/api-reference/objects/set_channel_data): set channel data for your recipient object, which allows you to set connected Microsoft Teams channels ## Resource access grants The only access you'll need to manage when using TeamsKit are grants for your users to interact with their [Tenants](/concepts/tenants) and [Objects](/concepts/objects) in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a [member of the account](/manage-your-account/managing-members). Therefore, these grants provide them elevated privileges to operate on specific resources using the API. We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages. ### With the Node SDK You'll need to generate a token for your user that includes access to the tenant storing the Microsoft Entra tenant ID as well as any recipient objects storing Teams channel data as described in [Microsoft Teams notifications with Knock](/integrations/chat/microsoft-teams/overview#channel-data-requirements). If you need to enable access to multiple recipient objects, you can include multiple grants in the user token. Example: - Tenant ID: `jurassic-park` - Recipient object collection: `videos` - Recipient object ID: `dinosaurs-loose` Using the above example, you can quickly generate a token with the Node SDK. ```javascript import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "jurassic-park" }, [ Grants.MsTeamsChannelsRead, ]), buildUserTokenGrant( { type: "object", id: "dinosaurs-loose", collection: "videos" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), buildUserTokenGrant( { type: "object", id: "raptor-feeding-info", collection: "videos" }, [Grants.ChannelDataRead, Grants.ChannelDataWrite], ), ], }); ``` You'll need to pass this token along with the public API key to the `KnockProvider` that wraps `KnockMsTeamsProvider` and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it. ### Other languages If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an example of using Joken for the Elixir library. You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: ```json { "sub": "user123", "grants": { "https://api.knock.app/v1/objects/$tenants/jurassic-park": { "ms_teams/channels_read": [{}] }, "https://api.knock.app/v1/objects/videos/dinosaurs-loose": { "channel_data/read": [{}], "channel_data/write": [{}] } } } ``` Continue reading for a deeper dive on access and how these grants are structured. ### Grants in the user token You may already be familiar with generating a user token to be used with your public API key when making client side calls if you've used [authentication with enhanced security](/in-app-ui/security-and-authentication#authentication-with-enhanced-security). You'll use the same process to generate the user token as described here, including signing it with an RS256 algorithm using your private signing key, but you'll also be sending a list of grants that the user needs to work with TeamsKit. The two resources you'll be granting access to are: - **The tenant**: the user needs access to this because this is where Knock stores the Microsoft Entra tenant ID (`ms_teams_tenant_id`) that will be used to send a notification as your Microsoft Teams bot - **The recipient object**: the user needs access to this because this is where Knock is storing the connected Microsoft Teams channels as channel data on the object These resources need different permissions. Here are the permissions needed for each: - **Tenant**: reading Microsoft Teams channels - **Recipient Object**: reading channel data; writing channel data ### How the grants are structured Resource access grants in Knock are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. How to read a resource grant: ```json { "Knock endpoint of the resource": { "Name of access type being granted/Specific permission being granted": [ "List of exceptions" ] } } ``` So to grant access for a user to read the channel data of object `dinosaurs-loose` in the `videos` collection, your grant would look like this: ```json { "https://api.knock.app/v1/objects/videos/dinosaurs-loose": { "channel_data/read": [{}] } } ``` ### Availability of resource access grants Currently these grants are only implemented for use by TeamsKit as described in this doc, and since exceptions are not used for these they will not be respected. # React Headless UI ## Feed How to build custom feed UI using our React hooks and client library. --- title: "Build your own feed UI (headless)" description: How to build custom feed UI using our React hooks and client library. section: Building in-app UI > Feeds tags: ["hooks", "headless", "useNotifications", "useAuthenticatedKnockClient"] --- Using our `@knocklabs/react` and `@knocklabs/client` libraries, you can create fully custom notification UIs that are backed by the Knock Feed API and real-time service. In this documentation, we'll take a look at creating a completely custom notifications UI in our application in a headless way using Knock's hooks. ## Getting started To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) To find the channel ID for your in-app channel(s), navigate to{" "} Channels and sources under the account settings section of your Knock dashboard, click on your in-app feed channel, and copy the channel ID. } /> ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Implement `KnockProvider` First, we'll need to implement the `KnockProvider` component somewhere in your component tree and authenticate against the Knock API using a user id and API key. ```jsx title="Implement KnockProvider in your app" import { KnockProvider } from "@knocklabs/react"; const App = ({ user }) => ( ); ``` ## Initialize the Knock client Next, we'll need to access the instance of the Knock client created by the `KnockProvider` using the `useKnockClient` hook. ```jsx title="Access the configured knockClient using useKnockClient" import { useKnockClient } from "@knocklabs/react"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); return null; }; ``` ## Initialize the feed instance Next, we'll want to set up an instance of a Knock Feed, which will handle the state management and provide a way for us to interact with the messages on the feed. ```jsx title="Create a feed store with Zustand" import { useKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react"; import { useEffect } from "react"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); const feedClient = useNotifications( knockClient, process.env.KNOCK_FEED_CHANNEL_ID, ); const { items, metadata } = useNotificationStore(feedClient); useEffect(() => { feedClient.fetch(); }, [feedClient]); return null; }; ``` ### Feed mode By default, the feed is initialized in `compact` mode, which returns a leaner payload with some lesser-used fields omitted. See [FeedItem](/in-app-ui/api-overview#feeditem) for more details on the payload schema. | Mode | Behavior | | --------- | -------------------------------------------------------------------------------------------------------------------- | | `compact` | Default. Omits `activities`, `total_activities`, and all but one actor. Omits nested arrays and objects from `data`. | | `rich` | Returns the full payload of each `FeedItem`. | If your application needs the full payload, pass `{ mode: "rich" }` as the third argument to `useNotifications`: ```jsx title="Initialize the feed in rich mode" const feedClient = useNotifications( knockClient, process.env.KNOCK_FEED_CHANNEL_ID, { mode: "rich" }, ); ``` ## Creating a custom notifications UI The last step is to render our notifications UI using the data that's exposed via the state store (`items` and `metadata`). ```jsx title="Render items and metadata in the feed" import { useKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react"; import { useEffect } from "react"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); const feedClient = useNotifications( knockClient, process.env.KNOCK_FEED_CHANNEL_ID, ); const { items, metadata } = useNotificationStore(feedClient); useEffect(() => { feedClient.fetch(); }, [feedClient]); return (
You have {metadata.unread_count} unread items {items.map((item) => (
))}
); }; ``` ## Wrapping up There's a lot more we can do with our notifications UI, but we'll leave that as an exercise to the reader. Here are some examples: - Adding mark as read, and archiving behavior to the notification cell - Displaying a count of the total number of notifications ## Guide How to build custom guide component using our React hooks and client library. --- title: "Build your own guide component (headless)" description: How to build custom guide component using our React hooks and client library. section: Building in-app UI > Guides --- Using our `@knocklabs/react` and `@knocklabs/client` libraries, you can create fully custom guide components that are backed by the Knock Guide API and real-time service. Note: You must be on @knocklabs/react version 0.7.31 or higher in order to use our new Guides related features. Older SDK versions do not have this capability. } /> ```bash npm install @knocklabs/react ``` You'll need to ensure the guide provider is a child component of the `KnockProvider`. You can find the `channelId` for your guide on the guide details page in the dashboard. ```tsx title="Setup the KnockGuideProvider within your product." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react"; import { useCurrentUser } from "@/lib/hooks"; const MyApplication = () => { // Get your authenticated current user const currentUser = useCurrentUser(); return (
{/* Rest of your app */} ); }; ``` consider using the{" "} KnockGuideLocationSensor {` `} helper component inside KnockGuideProvider to facilitate evaluating activation rules based on route changes. } /> > If you're not using one of our pre-built message types, you'll need to set up a custom message type. You can learn more about how to do this in our [message types](/in-app-ui/message-types/create-message-types) documentation. You can learn more about how to create a guide in our [creating guides](/in-app-ui/guides/create-guides) documentation. Next, you'll need to build a component to render your guide. You can use the `useGuide` hook to get the current guide step and render it. You'll either want to find a guide by its message type or by the guide key. ```tsx import { useEffect } from "react"; import { useGuide } from "@knocklabs/react"; const ChangelogCard = () => { const { step } = useGuide({ type: "changelog-card" }); useEffect(() => { if (step) step.markAsSeen(); }, [step]); if (!step) return null; return (
step.markAsInteracted()}>

{step.content.title}

{step.content.body}

); }; export ChangelogCard; ```
Once mounted in your application tree, if your user becomes eligible for the guide, then the component will be rendered and displayed to your users. ```tsx import { ChangelogCard } from "@/components/ChangelogCard"; const Sidebar = () => { return (
{/* Rest of my sidebar */}
); }; ```
## Other cases ### Rendering multiple guides If you want to fetch multiple guides of the same message type, you can use the `useGuides` hook for use in your component. ```tsx import { useGuides } from "@knocklabs/react"; const MyComponent = () => { const { guides } = useGuides({ type: "changelog-card" }); return (
{guides.map((guide) => (
{guide.step.content.title}
))}
); }; ``` ### Typing the guide step content If you're using a custom message type, you can type the guide step content to ensure type safety. ```tsx import { useGuide } from "@knocklabs/react"; type MyMessageTypeAttrs = { title: string; body: string; }; const MyComponent = () => { const { step } = useGuide({ type: "my-message-type" }); return (

{step.content.title}

{step.content.body}

); }; export MyComponent; ``` ## Advanced: working with the `GuideClient` In certain cases, you may need to drop down to operate on the guide client, which is the state management layer automatically created when you mount a `GuideProvider`. You can always access the guide client by using the `useGuideContext` hook: ```tsx const { client: guideClient } = useGuideContext(); ``` One use case for accessing the guide client is to force a refetch of the users eligible guides, which you can do through the `guideClient.fetch()` method. ## Preferences How to build custom notification preference interfaces using our React usePreferences hook. --- title: "Build your own preferences interface (headless)" description: How to build custom notification preference interfaces using our React usePreferences hook. section: Building in-app UI > Preferences tags: ["hooks", "headless", "usePreferences", "preferences"] --- Using our `@knocklabs/react` library, you can create custom notification preference interfaces backed by Knock's preferences API. The `usePreferences` hook fetches and updates preference data, manages cached data, and revalidates it after updates. This page covers the React-specific steps for authenticating a user and building a custom preference interface with `usePreferences`. For the underlying API flow and configuration options, see [Custom preference center](/preferences/custom-preference-center). ## Getting started To use this example, you'll need [an account on Knock](https://dashboard.knock.app) and an [identified user](/concepts/users). You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - A signed user token from your backend (required in production with enhanced security mode enabled; see [Authentication with enhanced security](/in-app-ui/security-and-authentication#authentication-with-enhanced-security)) - A default preference set configured in your Knock dashboard Before building custom preference interfaces, we recommend reading the{" "} preferences overview to understand how Knock's preference system works, including preference sets, workflows, and channel types. } /> ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react ``` ## Implement `KnockProvider` First, we'll need to implement the `KnockProvider` component somewhere in your component tree and authenticate against the Knock API using a user id, public API key, and signed user token. ```jsx title="Implement KnockProvider in your app" import { KnockProvider } from "@knocklabs/react"; const App = ({ user }) => ( ); ``` In development environments without enhanced security mode enabled, you can omit the `userToken` prop and pass only the user id. ## Setup the usePreferences hook Next, we'll use the `usePreferences` hook to fetch and manage user preferences. The hook provides preferences data along with functions to update preferences and loading states. ```jsx title="Access preferences using usePreferences hook" import { usePreferences } from "@knocklabs/react"; const PreferencesPage = () => { const { preferences, setPreferences, isLoading } = usePreferences(); if (isLoading) { return
Loading preferences...
; } return (

Notification preferences

{/* We'll build the interface here */}
); }; ``` ## Working with preference sets You can specify which preference set to fetch by passing options to the hook. This is useful when you have multiple preference sets for different contexts or user types. When updating preferences, you can use the spread operator to merge changes with existing preferences. This approach is cleaner than manually specifying each preference type (workflows, categories, channel_types). } /> ```jsx title="Fetch preferences for a specific preference set" import { usePreferences } from "@knocklabs/react"; const MarketingPreferences = () => { const { preferences, setPreferences, isLoading } = usePreferences({ preferenceSet: "marketing", }); // Component implementation }; const ProductPreferences = () => { const { preferences, setPreferences, isLoading } = usePreferences({ preferenceSet: "product-updates", }); // Component implementation }; ``` ## Building workflow preferences Workflow preferences control whether users receive notifications for specific workflows. Here's how to build an interface for workflow preferences: ```jsx title="Build workflow preferences interface" import { usePreferences } from "@knocklabs/react"; const WorkflowPreferences = ({ preferences: externalPreferences, setPreferences: externalSetPreferences, isLoading: externalIsLoading, } = {}) => { const { preferences: hookPreferences, setPreferences: hookSetPreferences, isLoading: hookIsLoading, } = usePreferences(); // Prefer externally provided props to avoid redundant API calls const preferences = externalPreferences ?? hookPreferences; const setPreferences = externalSetPreferences ?? hookSetPreferences; const isLoading = externalIsLoading ?? hookIsLoading; const handleWorkflowToggle = (workflowKey, enabled) => { setPreferences({ ...preferences, workflows: { ...preferences?.workflows, [workflowKey]: enabled, }, }); }; if (isLoading) return
Loading...
; return (

Workflow notifications

{Object.entries(preferences?.workflows || {}).map( ([workflowKey, setting]) => { // Handle simple boolean preferences if (typeof setting === "boolean") { return (
); } // Handle workflow preferences with channel type and/or channel settings if ( typeof setting === "object" && (setting?.channel_types || setting?.channels) ) { return (

{workflowKey.replace(/-/g, " ")}

{/* Channel type preferences */} {setting.channel_types && (

Channel types

{Object.entries(setting.channel_types).map( ([channelType, channelEnabled]) => (
), )}
)} {/* Specific channel preferences */} {setting.channels && (

Specific channels

{Object.entries(setting.channels).map( ([channelId, channelEnabled]) => (
), )}
)}
); } return null; }, )}
); }; ``` ## Building category preferences Category preferences enable users to control notifications for groups of related workflows. Here's how to build a category preferences interface: ```jsx title="Build category preferences interface" import { usePreferences } from "@knocklabs/react"; const CategoryPreferences = ({ preferences: externalPreferences, setPreferences: externalSetPreferences, isLoading: externalIsLoading, } = {}) => { const { preferences: hookPreferences, setPreferences: hookSetPreferences, isLoading: hookIsLoading, } = usePreferences(); // Prefer externally provided props to avoid redundant API calls const preferences = externalPreferences ?? hookPreferences; const setPreferences = externalSetPreferences ?? hookSetPreferences; const isLoading = externalIsLoading ?? hookIsLoading; // Helper function for cleaner channel type preference updates const updateCategoryChannelPreference = ( categoryKey, channelType, enabled, ) => { setPreferences({ ...preferences, categories: { ...preferences?.categories, [categoryKey]: { ...preferences?.categories?.[categoryKey], channel_types: { ...preferences?.categories?.[categoryKey]?.channel_types, [channelType]: enabled, }, }, }, }); }; // Helper function for specific channel preference updates const updateCategorySpecificChannelPreference = ( categoryKey, channelId, enabled, ) => { setPreferences({ ...preferences, categories: { ...preferences?.categories, [categoryKey]: { ...preferences?.categories?.[categoryKey], channels: { ...preferences?.categories?.[categoryKey]?.channels, [channelId]: enabled, }, }, }, }); }; if (isLoading) return
Loading...
; return (

Category preferences

{Object.entries(preferences?.categories || {}).map( ([categoryKey, category]) => (

{categoryKey.replace(/-/g, " ")}

{/* Channel type preferences */} {category.channel_types && (

Channel types

{Object.entries(category.channel_types).map( ([channelType, enabled]) => (
), )}
)} {/* Specific channel preferences */} {category.channels && (

Specific channels

{Object.entries(category.channels).map( ([channelId, enabled]) => (
), )}
)}
), )}
); }; ``` ## Building a complete preference center Here's how to combine different preference types into a complete preference center: ```jsx title="Complete preference center implementation" import { usePreferences } from "@knocklabs/react"; import { useState } from "react"; const PreferenceCenter = () => { const { preferences, setPreferences, isLoading, isValidating } = usePreferences(); const [activeTab, setActiveTab] = useState("workflows"); const handleBulkChannelTypeToggle = (channelType, enabled) => { setPreferences({ ...preferences, channel_types: { ...preferences?.channel_types, [channelType]: enabled, }, }); }; const handleSpecificChannelToggle = (channelId, enabled) => { setPreferences({ ...preferences, channels: { ...preferences?.channels, [channelId]: enabled, }, }); }; if (isLoading) { return (
Loading your preferences...
); } return (

Notification preferences

{isValidating &&
Saving...
}
{activeTab === "workflows" && ( )} {activeTab === "categories" && ( )} {activeTab === "channels" && (

Channel preferences

Choose which types of channels you receive notifications through.

Channel types

Control broad categories of channels (email, SMS, push, etc.)

{Object.entries(preferences?.channel_types || {}).map( ([channelType, enabled]) => (
), )}

Specific channels

Control individual channel instances using their UUID identifiers (takes precedence over channel types)

{Object.entries(preferences?.channels || {}).map( ([channelId, enabled]) => (
), )}
)}
); }; ``` ## Working with tenants If you're using Knock's multi-tenancy features, you can fetch and update preferences for specific tenants: ```jsx title="Working with tenant-specific preferences" import { usePreferences } from "@knocklabs/react"; const TenantPreferences = ({ tenantId }) => { const { preferences, setPreferences, isLoading } = usePreferences({ tenant: tenantId, }); // Component implementation for tenant-specific preferences }; ``` ## Fetching all preference sets The hook also provides access to all preference sets for advanced use cases: ```jsx title="Fetching all preference sets" import { usePreferences } from "@knocklabs/react"; const AdvancedPreferences = () => { const { getAllPreferences } = usePreferences(); const handleExportPreferences = async () => { try { const allPreferences = await getAllPreferences(); console.log("All user preferences:", allPreferences); // Handle the full preferences data } catch (error) { console.error("Failed to fetch all preferences:", error); } }; return (
); }; ``` ## Error handling and loading states The hook provides loading and validation states to help you build better user experiences: ```jsx title="Handling loading and error states" import { usePreferences } from "@knocklabs/react"; const PreferencesWithStates = () => { const { preferences, setPreferences, isLoading, isValidating } = usePreferences(); // Show loading spinner on initial load if (isLoading) { return (

Loading your preferences...

); } return (
{/* Show saving indicator when updating */} {isValidating && (
Saving your preferences...
)} {/* Preferences interface */}
{/* Your preference controls here */}
); }; ``` ## Next steps You now have the building blocks to create sophisticated preference interfaces with Knock. Consider: - Adding search and filtering functionality for large preference sets - Implementing bulk actions for managing multiple preferences at once - Creating preset configurations that users can apply - Adding preference history and audit trails - Building role-based preference management for team accounts ## Related links - [Preferences overview](/preferences/overview) - [Custom preference center](/preferences/custom-preference-center) - [`usePreferences` reference](/in-app-ui/react/sdk/hooks/use-preferences) - [Preferences API reference](/api-reference/recipients/preferences) # React SDK ## Overview Learn more about integrating Knock into your web applications through our React SDK. --- title: "Knock React SDK" description: Learn more about integrating Knock into your web applications through our React SDK. section: SDKs --- Our [`@knocklabs/react`](https://github.com/knocklabs/javascript/tree/main/packages/react) library lets you create notification experiences using Knock's APIs. It comes with pre-built UI components that you can use to easily get up-and-running with a fully functional notification feed experience in your product. If you're currently using @knocklabs/react-notification-feed, check out our{" "} migration documentation {" "} to learn how to use our new React library. } /> The React library is built on-top of the `@knocklabs/client` JS SDK and includes that library as an implicit dependency. See a live in-app demo. **You can also use the library to build:** - [Floating notification feeds](/in-app-ui/react/feed) - [Real-time notification toasts](/in-app-ui/react/toasts) - [Notification inboxes](/in-app-ui/react/inbox) - [Custom notification UI](/in-app-ui/react/custom-notifications-ui) - [Notification preferences](/in-app-ui/react/preferences) **Quick links:** - [`@knocklabs/react` on npm](https://www.npmjs.com/package/@knocklabs/react) - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Package on GitHub](https://github.com/knocklabs/javascript/tree/main/packages/react) - [React SDK reference](/in-app-ui/react/sdk/reference) - [JS SDK reference](/in-app-ui/javascript/sdk/reference) ## Need help? Our React library is worked on full-time by the Knock JavaScript team. ### Join the community Ask questions and find answers on those following platforms: - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/javascript/issues/new) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Reference Complete API reference for the Knock React SDK. --- title: "React SDK API reference" description: Complete API reference for the Knock React SDK. tags: ["mark as read"] section: SDKs --- In this section, you'll find the complete documentation for the components exposed in `@knocklabs/react`, including the props available. **Note**: You can see a reference for the methods available for the `Knock` class, as well as a `Feed` instance under the [client JS docs](/in-app-ui/javascript/sdk/reference). ## Components ## KnockProvider --- title: KnockProvider description: section: SDKs --- A React context provider that initializes and provides the Knock client instance to child components throughout your application. ## Usage ```jsx import { KnockProvider } from "@knocklabs/react-core"; function App() { return ( ); } ``` ## Props - **apiKey** (`string`) *required* - Your public API key from the Knock dashboard. - **user** (`object`) - A user object containing information to identify the user on Knock. - **identificationStrategy** (`string`) - Defaults to `inline`. Can be set to `skip` to skip inline identification. - **userToken** (`string`) - The JWT token for authenticating the user (required when using enhanced security mode). - **enabled** (`boolean`) - Defaults to `true`. When `false`, children still render but the Knock client stays idle: no identify call, no API requests, and no websocket. Flipping it to `true` authenticates and connects the client; flipping it back to `false` disconnects it and clears its data. Use it to defer activity until you have a complete identity (see below). - **host** (`string`) - The Knock API host URL. Defaults to the production Knock API. - **logLevel** (`string`) - When set to debug, will output additional logging to aid with debugging. - **children** (`ReactNode`) - Child components that will have access to the Knock client. ## Deferring activity with `enabled` `KnockProvider` takes an `enabled` prop that defaults to `true`. When it's `false`, the provider still renders its children, but the Knock client sits idle: it doesn't identify the user, make any API requests, or open a websocket. Set it back to `true` and the client authenticates and connects; set it to `false` again and it disconnects and clears its data. This is the recommended way to gate the provider on a complete identity — for example, an enhanced-security user token that isn't ready on the first render — rather than mounting and unmounting `KnockProvider` as that identity changes: ```jsx {/* ... */} ``` Reach for `enabled` whenever you render the provider before authentication or consent is ready, such as feature-flagged rollouts, gated workspaces, or cookie consent flows. ### Behavior when `enabled` changes - Feed components remount and reload their data when `enabled` becomes `true`. - Slack and Microsoft Teams connection status re-checks when the authenticated user changes. - Guides begin targeting once the client is authenticated. The `enabled` prop gates the whole client, while the [`readyToTarget` prop on `KnockGuideProvider`](/in-app-ui/react/sdk/components/knock-guide-provider) gates guide targeting on its own. To react to these transitions in your own components, subscribe to the client's authentication state with the [`useKnockAuthState`](/in-app-ui/react/sdk/hooks/use-knock-auth-state) hook. ## KnockFeedProvider --- title: KnockFeedProvider description: section: SDKs --- A React context provider that manages the notification feed state and provides feed-related functionality to child components. ## Usage ```jsx import { KnockProvider, KnockFeedProvider } from "@knocklabs/react-core"; function App() { return ( ); } ``` ## Props - **feedId** (`string`) *required* - The channel ID of the Knock in-app feed. - **colorMode** (`string`) - Sets the theme as either light or dark mode (defaults to light). - **defaultFeedOptions** (`FeedClientOptions`) - Default options for configuring the feed behavior. - **children** (`ReactNode`) - Child components that will have access to the feed context. ## KnockGuideProvider --- title: KnockGuideProvider description: section: SDKs --- A React context provider that manages in-app guide state and provides guide-related functionality to child components. ## Usage ```jsx import { KnockProvider, KnockGuideProvider } from "@knocklabs/react-core"; function App() { return ( ); } ``` ## Props - **channelId** (`string`) *required* - The channel ID for the guide channel. - **readyToTarget** (`boolean`) - Signals when the provider should initialize the guide client and fetch eligible guides. To prevent guides from being fetched with incomplete or undefined targeting data, set to true only when all targetParams data is available. - **listenForUpdates** (`boolean`) - Whether to listen for real-time guide updates. Defaults to true. - **colorMode** (`ColorMode`) - The color mode for guide styling (light or dark). - **targetParams** (`KnockGuideTargetParams`) - Additional targeting parameters for guide display conditions. - **trackLocationFromWindow** (`boolean`) - Whether to automatically track location changes from the browser window. - **orderResolutionDuration** (`number`) - Duration in milliseconds for resolving guide order conflicts. - **throttleCheckInterval** (`number`) - Interval in milliseconds for throttling guide checks. - **children** (`ReactNode`) - Child components that will have access to the guide context. ## KnockGuideLocationSensor --- title: KnockGuideLocationSensor description: section: SDKs --- A React helper component intended for use with select supported frameworks, such as [Next.js](https://nextjs.org/) and [Tanstack Router](https://tanstack.com/router/), for detecting route changes. Activation rules for guides are evaluated as a user's location changes in your application and, by default, [KnockGuideProvider](/in-app-ui/react/sdk/components/knock-guide-provider) listens for location change events from the global `window` object (enabled via the `trackLocationFromWindow` prop). However, modern frameworks often provide their own first class router APIs for detecting and reacting to route changes, which often works better and more reliably. Two important notes to keep in mind when implementing `KnockGuideLocationSensor`: 1. The import path is specific to the corresponding framework (e.g., `@knocklabs/react/next` for Next.js). 2. `KnockGuideLocationSensor` must be placed inside `KnockGuideProvider`. ## Usage ### Next.js ```jsx title="Next.js implementation of KnockGuideLocationSensor." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react-core"; import { KnockGuideLocationSensor } from "@knocklabs/react/next"; function App() { return ( // Must be placed inside KnockGuideProvider. // If you are using the Pages Router: // ); } ``` ### Tanstack Router ```jsx title="Tanstack Router implementation of KnockGuideLocationSensor." import { KnockProvider, KnockGuideProvider } from "@knocklabs/react-core"; import { KnockGuideLocationSensor } from "@knocklabs/react/tanstack"; function App() { return ( // Must be placed inside KnockGuideProvider. ); } ``` ## KnockSlackProvider --- title: KnockSlackProvider description: section: SDKs --- A React context provider that manages Slack integration state and provides Slack-related functionality to child components. ## Usage ```jsx import { KnockProvider, KnockSlackProvider } from "@knocklabs/react-core"; function App() { return ( ); } ``` ## Props - **knockSlackClientId** (`string`) *required* - The Slack client ID for your Knock Slack integration. - **redirectUrl** (`string`) *required* - The URL to redirect to after Slack authentication. - **children** (`ReactNode`) - Child components that will have access to the Slack context. ## KnockMsTeamsProvider --- title: KnockMsTeamsProvider description: section: SDKs --- A React context provider that manages Microsoft Teams integration state and provides Teams-related functionality to child components. ## Usage ```jsx import { KnockProvider, KnockMsTeamsProvider } from "@knocklabs/react-core"; function App() { return ( ); } ``` ## Props - **knockMsTeamsClientId** (`string`) *required* - The Microsoft Teams client ID for your Knock Teams integration. - **redirectUrl** (`string`) *required* - The URL to redirect to after Microsoft Teams authentication. - **children** (`ReactNode`) - Child components that will have access to the Teams context. ## KnockI18nProvider --- title: KnockI18nProvider description: section: SDKs --- A React context provider that manages internationalization (i18n) state and provides translation functionality to child components. ## Usage ```jsx import { KnockProvider, KnockI18nProvider } from "@knocklabs/react-core"; const translations = { en: { "notification.new": "New notification", "notification.markAsRead": "Mark as read", }, fr: { "notification.new": "Nouvelle notification", "notification.markAsRead": "Marquer comme lu", }, }; function App() { return ( ); } ``` ## Props - **locale** (`string`) *required* - The current locale for translations (e.g., 'en', 'fr', 'es'). - **translations** (`Record`) - Translation dictionary containing localized strings. - **children** (`ReactNode`) - Child components that will have access to the i18n context. ## Button --- title: Button description: section: SDKs --- A generic Button component that can be used to add action buttons to items in a notification feed. ## Usage ```jsx import { Button } from "@knocklabs/react-core"; function MyComponent() { return ( ); } ``` ## Props - **variant** (`string`) - The variant of the button; either `primary` or `secondary`. Defaults to `primary`. - **loadingText** (`string`) - Text to display while the button is loading. - **isLoading** (`boolean`) - When true, will display a spinner next to the `loadingText` (if present). - **isDisabled** (`boolean`) - When true, will mark the button as disabled. - **isFullWidth** (`boolean`) - When true, will make the button occupy 100% of the parent container. - **onClick** (`function`) - The click handler to be invoked when the button is clicked. - **children** (`ReactNode`) - The text to display inside of the button. ## ButtonGroup --- title: ButtonGroup description: section: SDKs --- Used to group and space multiple `Button` components into a single line. ## Usage ```jsx import { Button, ButtonGroup } from "@knocklabs/react-core"; function MyComponent() { return ( ); } ``` ## Props - **children** (`ReactNode`) - One or more `Button` components. ## NotificationFeed --- title: NotificationFeed description: section: SDKs --- A React component that renders a complete notification feed interface with items, header, and filtering capabilities. ## Usage ```jsx import { NotificationFeed } from "@knocklabs/react-core"; function MyComponent() { return ( console.log("Clicked:", item)} onMarkAllAsReadClick={() => console.log("Mark all as read")} initialFilterStatus="unread" /> ); } ``` ## Props - **renderItem** (`function`) - A function invoked per `FeedItem` to be rendered that should return a cell to be rendered in the feed. Useful when you want to render a custom feed cell. Defaults to rendering a `NotificationCell`. - **renderHeader** (`function`) - A function invoked that returns a header to be rendered in the feed. Useful when you want to render a custom header. Defaults to rendering a `NotificationFeedHeader`. - **onNotificationClick** (`function`) - A custom function to be invoked when a notification cell is clicked. - **onNotificationButtonClick** (`function`) - A custom function to be invoked when an action button in a notification cell is clicked. - **onMarkAllAsReadClick** (`function`) - A custom function to be invoked when the `Mark all as read` button is clicked. - **initialFilterStatus** (`FilterStatus`) - The initial filter applied by the NotificationFeed (e.g., `FilterStatus.All` or `FilterStatus.Unread`). If unspecified, defaults to 'All'. - **EmptyComponent** (`ReactNode`) - The empty component to render, when not set defaults to . ## NotificationFeedPopover --- title: NotificationFeedPopover description: section: SDKs --- Renders a `NotificationFeed` in a floating popover, rendered by `popper-js`. ## Usage ```jsx import { NotificationFeedPopover } from "@knocklabs/react-core"; import { useRef, useState } from "react"; function MyComponent() { const buttonRef = useRef(null); const [isVisible, setIsVisible] = useState(false); return ( <> setIsVisible(false)} buttonRef={buttonRef} closeOnClickOutside={true} /> ); } ``` ## Props Accepts the same base props as `NotificationFeed`, and overrides with the following: - **isVisible** (`boolean`) *required* - Whether or not to show the popover. - **onClose** (`function`) - The function to be invoked when the popover is closed. - **onOpen** (`function`) - A function that's invoked whenever the feed popover is opened, useful for updating any items in view and marking them as read, seen, or archived. - **buttonRef** (`RefObject`) *required* - A ref of the button to position the popover adjacent to. - **closeOnClickOutside** (`boolean`) - When true, will close the popover whenever any area outside of the popover is clicked. - **placement** (`Placement`) - Determines the popper-js position of the popover (defaults to `bottom-end`). ## NotificationCell --- title: NotificationCell description: section: SDKs --- A React component that renders an individual notification item within a feed. ## Usage ```jsx import { NotificationCell } from "@knocklabs/react-core"; function MyComponent({ item }) { return ( console.log("Item clicked:", item)} onButtonClick={(button, item) => console.log("Button clicked:", button)} avatar={User avatar} > ); } ``` ## Props - **item** (`FeedItem`) - The feed item (notification) to render. - **onItemClick** (`function`) - The function to be invoked when the notification cell is clicked. - **onButtonClick** (`function`) - The function to be invoked when a button rendered in the notification cell is clicked. - **avatar** (`ReactNode`) - Render a custom avatar for the feed item. - **archiveButton** (`ReactNode`) - Render a custom archive button for the feed item. - **children** (`ReactNode`) - A set of children to render inside of the cell, will be rendered under the main content in a `rnf-notification-cell__child-content` div. Useful for rendering action buttons. ## NotificationIconButton --- title: NotificationIconButton description: section: SDKs --- Renders a notification bell icon, with a badge showing the number of unseen items present in the notification feed. ## Usage ```jsx import { NotificationIconButton } from "@knocklabs/react-core"; function MyComponent() { return ( console.log("Notification icon clicked")} badgeCountType="unseen" /> ); } ``` ## Props - **onClick** (`function`) - The function to be invoked when the IconButton is clicked. - **badgeCountType** (`enum`) - One of `unseen` | `unread` | `all` to determine which count to display. ## SlackAuthButton --- title: SlackAuthButton description: section: SDKs --- A React component that renders a button for authenticating with Slack. ## Usage ```jsx import { SlackAuthButton } from "@knocklabs/react-core"; function MyComponent() { return ( { if (result === "authComplete") { console.log("Slack authentication successful"); } else { console.log("Slack authentication failed"); } }} /> ); } ``` ## Props - **slackClientId** (`string`) - The client ID of your Slack application. - **redirectUrl** (`string`) - The URL of your application to return to once Slack authorization is complete. - **onAuthenticationComplete** (`(authenticationResult: 'authComplete' | 'authFailed') => void;`) - An optional callback function you can pass to this component that will execute upon completion of the authentication flow. Takes one argument of the authentication result for you to handle in your callback. ## SlackAuthContainer --- title: SlackAuthContainer description: section: SDKs --- A React component that provides a container for Slack authentication with custom action buttons. ## Usage ```jsx import { SlackAuthContainer, SlackAuthButton } from "@knocklabs/react-core"; function MyComponent() { return ( } /> ); } ``` ## Props - **actionButton** (`ReactNode`) - Render a button, either a custom one or the SlackAuthButton, to connect to Slack. ## SlackChannelCombobox --- title: SlackChannelCombobox description: section: SDKs --- A React component that renders a combobox for selecting Slack channels. ## Usage ```jsx import { SlackChannelCombobox } from "@knocklabs/react-core"; function MyComponent() { return ( ); } ``` ## Props - **slackChannelsRecipientObject** (`RecipientObject`) - Object ID and collection of the Knock object that will store the channel data of the connected Slack channels. - **queryOptions** (`SlackChannelQueryOptions`) - An optional map of params to control the query to the Slack API. ## MsTeamsAuthButton --- title: MsTeamsAuthButton description: section: SDKs --- A React component that renders a button for authenticating with Microsoft Teams. ## Usage ```jsx import { MsTeamsAuthButton } from "@knocklabs/react-core"; function MyComponent() { return ( { if (result === "authComplete") { console.log("Microsoft Teams authentication successful"); } else { console.log("Microsoft Teams authentication failed"); } }} /> ); } ``` ## Props - **graphApiClientId** (`string`) - The client ID of your Microsoft Graph API-enabled application registered with Microsoft Entra. This should match the "Graph API client ID" setting of your Microsoft Teams channel in the Knock dashboard. - **redirectUrl** (`string`) - The URL of your application to return to once Microsoft Teams authorization is complete. - **onAuthenticationComplete** (`(authenticationResult: 'authComplete' | 'authFailed') => void;`) - An optional callback function you can pass to this component that will execute upon completion of the authentication flow. Takes one argument of the authentication result for you to handle in your callback. ## MsTeamsAuthContainer --- title: MsTeamsAuthContainer description: section: SDKs --- A React component that provides a container for Microsoft Teams authentication with custom action buttons. ## Usage ```jsx import { MsTeamsAuthContainer, MsTeamsAuthButton } from "@knocklabs/react-core"; function MyComponent() { return ( } /> ); } ``` ## Props - **actionButton** (`ReactNode`) - Render a button, either a custom one or the MsTeamsAuthButton, to connect to Microsoft Teams. ## MsTeamsChannelCombobox --- title: MsTeamsChannelCombobox description: section: SDKs --- A React component that renders a combobox for selecting Microsoft Teams channels. ## Usage ```jsx import { MsTeamsChannelCombobox } from "@knocklabs/react-core"; function MyComponent() { return ( ); } ``` ## Props - **msTeamsChannelsRecipientObject** (`RecipientObject`) - Object ID and collection of the Knock object that will store the channel data of the connected Microsoft Teams channels. ## Hooks ## useAuthenticatedKnockClient --- title: useAuthenticatedKnockClient description: section: SDKs --- The `useAuthenticatedKnockClient` hook is used to create and manage an authenticated Knock client instance for making API calls on behalf of a specific user. ## Parameters This hook accepts positional parameters: - **apiKey** (`string`) *required* - The public API key for the environment. - **userIdOrUserWithProperties** (`UserIdOrUserWithProperties`) *required* - User identification data. Can be a string user ID or an object with id and optional properties. - **userToken** (`string`) - Optional user token for the authenticated user. - **options** (`AuthenticatedKnockClientOptions`) - Optional configuration for the authenticated Knock client. ## Returns Returns a `Knock` client instance directly (not wrapped in an object). ## Example ### Basic usage The following example demonstrates how to use the `useAuthenticatedKnockClient` hook to create an authenticated Knock client. ```tsx import { useAuthenticatedKnockClient } from "@knocklabs/react"; const MyComponent = () => { const knock = useAuthenticatedKnockClient( "pk_test_12345", { id: "user-123", name: "John Doe" }, "user-token-abc", ); const triggerWorkflow = async () => { await knock.workflows.trigger("my-workflow", { recipients: ["user-456"], data: { message: "Hello world!" }, }); }; return ; }; ``` ### With options You can pass additional options for the Knock client: ```tsx const knock = useAuthenticatedKnockClient( "pk_test_12345", { id: "user-123" }, "user-token-abc", { host: "https://api.knock.app", logLevel: "debug", }, ); ``` ### Deferring activity with the `enabled` option The `options` object accepts an `enabled` flag that defaults to `true`. When it's `false`, the hook creates the client but keeps it idle — no identify call, no API requests, and no websocket — until you have a complete identity. Set it to `true` to authenticate and connect the client, and back to `false` to disconnect it and clear its data. ```tsx const knock = useAuthenticatedKnockClient( "pk_test_12345", { id: userId }, userToken, { enabled: Boolean(userId && userToken), }, ); ``` This is the same lifecycle the [`enabled` prop on `KnockProvider`](/in-app-ui/react/sdk/components/knock-provider) manages for you. Prefer the prop when you're using `KnockProvider`, and reach for this option when you build a headless client with the hook. ### Using string user ID (deprecated) While you can pass a string user ID directly, it's recommended to use an object: ```tsx // Deprecated approach const knock = useAuthenticatedKnockClient( "pk_test_12345", "user-123", "user-token-abc", ); // Recommended approach const knock = useAuthenticatedKnockClient( "pk_test_12345", { id: "user-123" }, "user-token-abc", ); ``` ## useKnockAuthState --- title: useKnockAuthState description: section: SDKs --- The `useKnockAuthState` hook subscribes to a Knock client's authentication state, re-rendering your component when the authenticated user changes. It's backed by the subscribable `authStore` on the client, so it stays correct even when the client is re-authenticated in place — for example, when you toggle the [`enabled` prop on `KnockProvider`](/in-app-ui/react/sdk/components/knock-provider). ## Parameters This hook accepts a single argument: - **knock** (`Knock`) *required* - An authenticated Knock client, such as the instance returned by the `useKnockClient` hook. ## Returns Returns a `KnockAuthState` object describing the client's current authentication. - **status** (`'authenticated' | 'unauthenticated'`) - Whether a user is authenticated to the client. - **userId** (`string | undefined | null`) - The ID of the authenticated user, or `undefined` when no user is authenticated. - **userToken** (`string | undefined`) - The user token in use for the authenticated user, when one was provided. ## Example The following example reads the authentication state to render different UI while a user authenticates. It pairs well with the `enabled` prop on `KnockProvider`, which flips the client between unauthenticated and authenticated as the identity loads. ```tsx import { useKnockClient, useKnockAuthState } from "@knocklabs/react"; const AuthStatus = () => { const knock = useKnockClient(); const { status, userId } = useKnockAuthState(knock); if (status === "unauthenticated") { return Authenticating…; } return Connected as {userId}; }; ``` ## useTranslations --- title: useTranslations description: section: SDKs --- The `useTranslations` hook is used to retrieve translations for the current user's locale. It provides access to localized strings for the Knock in-app UI components. ## Parameters This hook does not accept any parameters. ## Returns A `UseTranslationsReturn` object with the following properties: - **t** (`(key: string, defaultValue?: string) => string`) - Translation function that returns the translated string for the given key. - **locale** (`string`) - The current locale being used. ## Example ### Basic usage The following example demonstrates how to use the `useTranslations` hook to get translated strings. ```tsx import { useTranslations } from "@knocklabs/react"; const MyComponent = () => { const { t, locale } = useTranslations(); return (

{t("notification_feed.title", "Notifications")}

{t("notification_feed.empty", "No notifications")}

Current locale: {locale}
); }; ``` ## useNotifications --- title: useNotifications description: section: SDKs --- The `useNotifications` hook initializes and manages a feed client instance for a user's notification feed. This hook handles the lifecycle of the feed client, including initialization, real-time updates, and cleanup. ## Parameters - **knock** (`Knock`) *required* - An authenticated Knock client instance. - **feedChannelId** (`string`) *required* - The UUID of the in-app feed channel from your Knock dashboard. - **options** (`FeedClientOptions`) - Optional configuration for the feed client (e.g., archived, status, page_size). ## Returns Returns a [`Feed`](/typedocs/client/feed) client instance. The Feed client provides methods for fetching items, marking items as read/seen/archived, and subscribing to real-time updates. See the [Feed client reference](/typedocs/client/feed) for the full API. ## Example ### Basic usage The following example demonstrates how to use the `useNotifications` hook with `useNotificationStore` to display notifications. ```tsx import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react"; const MyComponent = () => { const knock = useAuthenticatedKnockClient(); const feedClient = useNotifications(knock, "feed-channel-id", { status: "all", archived: "exclude", }); const { items, metadata } = useNotificationStore(feedClient); return (

Notifications ({metadata.unread_count} unread)

    {items.map((notification) => (
  • feedClient.markAsRead(notification)} > {notification.blocks?.[0]?.rendered}
  • ))}
); }; ``` ### Using with KnockProvider and KnockFeedProvider When using the provider pattern, you can access the feed client through context: ```tsx import { KnockProvider, KnockFeedProvider, useKnockFeed, } from "@knocklabs/react"; const NotificationList = () => { const { feedClient, items, metadata } = useKnockFeed(); return (

Notifications ({metadata.unread_count} unread)

    {items.map((item) => (
  • {item.blocks?.[0]?.rendered}
  • ))}
); }; const App = () => ( ); ``` ## useNotificationStore --- title: useNotificationStore description: section: SDKs --- The `useNotificationStore` hook provides direct access to the feed store state. This is a lower-level hook that subscribes to state changes from a feed client and returns the current state. It supports optional selectors for optimized re-renders. ## Parameters - **feedClient** (`Feed`) *required* - A Feed client instance returned from useNotifications. - **selector** (`(state: FeedStoreState) => T`) - Optional selector function to extract a subset of the state. When provided, only re-renders when the selected value changes. ## Returns Returns `FeedStoreState` (or `T` if a selector is provided) with the following properties: - **items** (`FeedItem[]`) - Array of notification feed items. - **metadata** (`FeedMetadata`) - Feed metadata including unread_count, unseen_count, and total_count. - **pageInfo** (`PageInfo`) - Pagination information with before, after, and page_size. - **loading** (`boolean`) - Whether the feed is currently loading. - **networkStatus** (`NetworkStatus`) - Current network request status. ## Example ### Basic usage The following example demonstrates how to use the `useNotificationStore` hook to access feed state. ```tsx import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react"; const MyComponent = () => { const knock = useAuthenticatedKnockClient(); const feedClient = useNotifications(knock, "feed-channel-id"); const { items, metadata, loading } = useNotificationStore(feedClient); if (loading) return
Loading...
; return (

Total notifications: {items.length}

Unread count: {metadata.unread_count}

    {items.map((item) => (
  • {item.blocks?.[0]?.rendered}
  • ))}
); }; ``` ### Using with a selector Use a selector to optimize re-renders by only subscribing to specific state changes: ```tsx import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react"; const UnreadBadge = () => { const knock = useAuthenticatedKnockClient(); const feedClient = useNotifications(knock, "feed-channel-id"); // Only re-renders when unread_count changes const unreadCount = useNotificationStore( feedClient, (state) => state.metadata.unread_count, ); return {unreadCount}; }; ``` ### Accessing multiple state properties You can select multiple properties at once: ```tsx const { items, metadata } = useNotificationStore(feedClient, (state) => ({ items: state.items, metadata: state.metadata, })); ``` ## useGuide --- title: useGuide description: section: SDKs --- The `useGuide` hook is used to retrieve guides by either a message type key or a specific guide key. It supports typing the guide step content to ensure type safety. ## Parameters Accepts an object (`KnockGuideFilterParams`) with the following properties: - **key** (`string`) - Match a specific guide by its key. - **type** (`string`) - Match any guide by its type (message type). ## Returns A `UseGuideReturn` object with the following properties: - **guide** (`KnockGuide | undefined`) - The matching guide. - **step** (`KnockGuideStep | undefined`) - The matching guide step. Will always be defined if the guide is defined. ## Example ### Specific message type The following example demonstrates how to use the `useGuide` hook to get the guide for a specific message type. ```tsx import { useGuide } from "@knocklabs/react"; const MyComponent = () => { const { guide, step } = useGuide({ type: "my-message-type" }); if (!step) return null; return (

{step.content.title}

{step.content.body}

); }; ``` ### Typing the guide and step The `useGuide` hook returns the guide and step as `KnockGuide` and `KnockGuideStep` respectively. ```tsx import { useGuide } from "@knocklabs/react"; type MyMessageTypeAttrs = { title: string; body: string; }; const MyComponent = () => { const { guide, step } = useGuide({ type: "my-message-type", }); if (!step) return null; return (

{step.content.title}

{step.content.body}

); }; ``` ## useGuides --- title: useGuides description: section: SDKs --- The `useGuides` hook returns an array of guides matching the provided filter criteria. It supports typing the guide step content to ensure type safety. Starting in version 0.10.0 of `@knocklabs/react`, `useGuides` respects [throttling rules](/in-app-ui/guides/order-guides#guide-throttling) by default, similar to `useGuide`. ## Parameters The hook accepts two parameters: a filter object and an optional options object. ### Filter object - **type** (`string`) *required* - Match any guide by its type, which corresponds to the key of a message type. ### Options object - **includeThrottled** (`boolean`) - When set to true, returns all eligible guides regardless of throttling rules. Defaults to false. ## Returns A `UseGuidesReturn` object with the following properties: - **guides** (`KnockGuide[]`) - Zero or more matching guides. ## Example ### Getting guides for a specific message type ```tsx import { useGuides } from "@knocklabs/react"; const MyComponent = () => { const { guides } = useGuides({ type: "my-message-type" }); if (guides.length === 0) return null; return (
{guides.map((guide) => (
{guide.name}
))}
); }; ``` ### Typing the guides ```tsx import { useGuides } from "@knocklabs/react"; type MyMessageTypeAttrs = { title: string; body: string; }; const MyComponent = () => { const { guides } = useGuides({ type: "my-message-type" }); if (guides.length === 0) return null; return (
{guides.map((guide) => (
{guide.name}

{guide.step.content.body}

{guide.step.content.title}

))}
); }; ``` ### Including throttled guides By default, `useGuides` respects throttling rules. To return all eligible guides regardless of throttling, pass `includeThrottled: true`: ```tsx import { useGuides } from "@knocklabs/react"; const MyComponent = () => { const { guides } = useGuides( { type: "changelog" }, { includeThrottled: true }, ); return (
{guides.map((guide) => (
{guide.name}
))}
); }; ``` ## useGuideContext --- title: useGuideContext description: section: SDKs --- The `useGuideContext` hook is used to access the guide context and client instance within a guide provider. This hook provides access to the guide client and theme information. ## Parameters This hook does not accept any parameters. ## Returns - **client** (`KnockGuideClient`) - The guide client instance for managing guides. - **colorMode** (`'light' | 'dark'`) - The current theme color mode. ## Example ### Basic usage The following example demonstrates how to use the `useGuideContext` hook to access the guide client and theme. ```tsx import { useGuideContext } from "@knocklabs/react"; const MyGuideComponent = () => { const { client, colorMode } = useGuideContext(); const dismissGuide = async (guideId: string) => { await client.dismiss(guideId); }; return (

Current theme: {colorMode}

); }; ``` ## usePreferences --- title: usePreferences description: section: SDKs --- The `usePreferences` hook is used to fetch and manage user notification preferences. This hook provides access to preference sets and allows you to update user preferences. ## Parameters Accepts an optional object with the following properties: - **preferenceSet** (`string`) - The preference set ID to fetch. If not provided, the default preference set will be fetched. - **tenant** (`string`) - Optional tenant ID for multi-tenant applications. ## Returns Returns an object with the following properties: - **preferences** (`PreferenceSet | undefined`) - The preference set data including workflows and channels. - **setPreferences** (`(properties: SetPreferencesProperties) => void`) - Function to update user preferences. - **getAllPreferences** (`() => Promise`) - Function to retrieve all preference sets for the user. - **isLoading** (`boolean`) - Whether preferences are being loaded. - **isValidating** (`boolean`) - Whether preferences are being revalidated. ## Example ### Basic usage The following example demonstrates how to use the `usePreferences` hook to manage user preferences. ```tsx import { usePreferences } from "@knocklabs/react"; const MyComponent = () => { const { preferences, isLoading, setPreferences } = usePreferences({ preferenceSet: "default", }); if (isLoading) return
Loading preferences...
; if (!preferences) return
No preferences found
; const handleToggleWorkflow = (workflowKey: string, enabled: boolean) => { setPreferences({ workflows: { [workflowKey]: { enabled }, }, }); }; return (

Notification preferences

{preferences.workflows.map((workflow) => (
))}
); }; ``` ### With tenant For multi-tenant applications, you can specify a tenant: ```tsx const { preferences, setPreferences } = usePreferences({ preferenceSet: "default", tenant: "tenant-123", }); ``` ### Getting all preference sets You can retrieve all preference sets for the user: ```tsx const { getAllPreferences } = usePreferences(); const handleGetAll = async () => { const allPreferences = await getAllPreferences(); console.log(allPreferences); }; ``` ## useSlackAuth --- title: useSlackAuth description: section: SDKs --- The `useSlackAuth` hook is used to manage Slack authentication for connecting user accounts to Slack workspaces. This hook must be used within a `KnockSlackProvider`. ## Parameters - **slackClientId** (`string`) *required* - The Slack OAuth client ID for your application. - **redirectUrl** (`string`) - Optional URL to redirect to after authentication completes. - **options** (`UseSlackAuthOptions | string[]`) - Optional configuration for OAuth scopes. Can be an object with scopes/additionalScopes or an array of additional scopes. ## Returns Returns an object with the following properties: - **buildSlackAuthUrl** (`() => string`) - Function that builds the Slack OAuth authorization URL. - **disconnectFromSlack** (`() => Promise`) - Function to revoke Slack access for the current user. ## Example ### Basic usage The following example demonstrates how to use the `useSlackAuth` hook to manage Slack authentication. ```tsx import { KnockSlackProvider, useSlackAuth } from "@knocklabs/react"; const SlackAuthenticationButton = () => { const { buildSlackAuthUrl, disconnectFromSlack } = useSlackAuth( "slack-client-id", "https://example.com/callback", ); const handleConnect = () => { const authUrl = buildSlackAuthUrl(); window.open(authUrl, "slackAuth"); }; return (
); }; const App = () => ( ); ``` ### With custom scopes You can customize the OAuth scopes requested: ```tsx const { buildSlackAuthUrl } = useSlackAuth("slack-client-id", undefined, { additionalScopes: ["users:read", "channels:history"], }); ``` ## useSlackChannels --- title: useSlackChannels description: section: SDKs --- The `useSlackChannels` hook is used to retrieve available Slack channels for a connected workspace. This hook must be used within a `KnockSlackProvider`. ## Parameters Accepts an object with the following properties: - **queryOptions** (`SlackChannelQueryOptions`) - Optional query options for filtering and pagination. Includes limitPerPage, types, and cursor. ## Returns Returns an object with the following properties: - **data** (`SlackChannel[]`) - Array of available Slack channels. - **isLoading** (`boolean`) - Whether channels are being loaded or revalidating. - **refetch** (`() => void`) - Function to manually refetch the channels. ## Example ### Basic usage The following example demonstrates how to use the `useSlackChannels` hook to display available Slack channels. ```tsx import { KnockSlackProvider, useSlackChannels } from "@knocklabs/react"; const SlackChannelList = () => { const { data: channels, isLoading, refetch } = useSlackChannels({}); if (isLoading) return
Loading channels...
; return (

Available Channels

    {channels.map((channel) => (
  • {channel.name}
  • ))}
); }; const App = () => ( ); ``` ### With query options You can customize the query with pagination and filtering options: ```tsx const { data: channels } = useSlackChannels({ queryOptions: { limitPerPage: 50, types: ["public_channel", "private_channel"], }, }); ``` ## useConnectedSlackChannels --- title: useConnectedSlackChannels description: section: SDKs --- The `useConnectedSlackChannels` hook is used to retrieve and manage Slack channels that are currently connected for a specific recipient object. This hook must be used within a `KnockSlackProvider`. ## Parameters Accepts an object with the following properties: - **slackChannelsRecipientObject** (`RecipientObject`) *required* - The recipient object (with objectId and collection) to get connected channels for. ## Returns Returns an object with the following properties: - **data** (`SlackChannelConnection[] | null`) - Array of connected Slack channels for the recipient object. - **updateConnectedChannels** (`(channels: SlackChannelConnection[]) => Promise`) - Function to update the connected channels for the recipient object. - **loading** (`boolean`) - Whether connected channels are being loaded or revalidating. - **error** (`string | null`) - Error message if the request failed. - **updating** (`boolean`) - Whether an update operation is in progress. ## Example ### Basic usage The following example demonstrates how to use the `useConnectedSlackChannels` hook to display and manage connected Slack channels. ```tsx import { KnockSlackProvider, useConnectedSlackChannels, } from "@knocklabs/react"; const ConnectedChannelsList = () => { const { data: channels, loading, error, updateConnectedChannels, updating, } = useConnectedSlackChannels({ slackChannelsRecipientObject: { objectId: "project-123", collection: "projects", }, }); if (loading) return
Loading connected channels...
; if (error) return
Error: {error}
; const handleDisconnect = async (channelId: string) => { const updatedChannels = channels?.filter((ch) => ch.channel_id !== channelId) || []; await updateConnectedChannels(updatedChannels); }; return (

Connected channels

{channels && channels.length > 0 ? (
    {channels.map((channel) => (
  • {channel.channel_name}
  • ))}
) : (

No channels connected

)}
); }; const App = () => ( ); ``` ### Updating connected channels You can add or update connected channels by calling `updateConnectedChannels` with the new array: ```tsx const { data: channels, updateConnectedChannels } = useConnectedSlackChannels({ slackChannelsRecipientObject: { objectId: "project-123", collection: "projects", }, }); const handleConnect = async (newChannel: SlackChannelConnection) => { const updatedChannels = [...(channels || []), newChannel]; await updateConnectedChannels(updatedChannels); }; ``` ## useSlackConnectionStatus --- title: useSlackConnectionStatus description: section: SDKs --- The `useSlackConnectionStatus` hook is used to check and manage the current Slack connection status for a user. This hook is typically used internally by the `KnockSlackProvider` and is not commonly used directly in application code. ## Parameters - **knock** (`Knock`) *required* - The authenticated Knock client instance. - **knockSlackChannelId** (`string`) *required* - The Knock channel ID for the Slack integration. - **tenantId** (`string`) *required* - The tenant ID for multi-tenant applications. ## Returns Returns an object with the following properties: - **connectionStatus** (`ConnectionStatus`) - The current connection status: 'connecting' | 'connected' | 'disconnected' | 'error' | 'disconnecting'. - **setConnectionStatus** (`(status: ConnectionStatus) => void`) - Function to update the connection status. - **errorLabel** (`string | null`) - Error message if the connection check failed. - **setErrorLabel** (`(errorLabel: string) => void`) - Function to set an error message. - **actionLabel** (`string | null`) - Label for the current action being performed. - **setActionLabel** (`(actionLabel: string | null) => void`) - Function to set an action label. ## Example ### Basic usage This hook is typically used internally by the `KnockSlackProvider`. If you need to access the connection status in your components, use the `useKnockSlackClient` hook instead: ```tsx import { KnockSlackProvider, useKnockSlackClient } from "@knocklabs/react"; const SlackStatus = () => { const { connectionStatus } = useKnockSlackClient(); return (

Slack Status: {connectionStatus}

); }; const App = () => ( ); ``` ### Advanced usage If you need to use this hook directly: ```tsx import { useKnockClient, useSlackConnectionStatus } from "@knocklabs/react"; const MyComponent = () => { const knock = useKnockClient(); const { connectionStatus, errorLabel } = useSlackConnectionStatus( knock, "slack-channel-id", "tenant-id", ); if (connectionStatus === "connecting") return
Checking connection...
; if (errorLabel) return
Error: {errorLabel}
; return (

Status: {connectionStatus}

); }; ``` ## useMsTeamsAuth --- title: useMsTeamsAuth description: section: SDKs --- The `useMsTeamsAuth` hook is used to manage Microsoft Teams authentication for connecting user accounts to Teams workspaces. This hook must be used within a `KnockMsTeamsProvider`. ## Parameters - **graphApiClientId** (`string`) *required* - The Microsoft Graph API client ID for your application. - **redirectUrl** (`string`) - Optional URL to redirect to after authentication completes. ## Returns Returns an object with the following properties: - **buildMsTeamsAuthUrl** (`() => string`) - Function that builds the Microsoft Teams OAuth authorization URL. - **disconnectFromMsTeams** (`() => Promise`) - Function to revoke Microsoft Teams access for the current user. ## Example ### Basic usage The following example demonstrates how to use the `useMsTeamsAuth` hook to manage Microsoft Teams authentication. ```tsx import { KnockMsTeamsProvider, useMsTeamsAuth } from "@knocklabs/react"; const MsTeamsAuthenticationButton = () => { const { buildMsTeamsAuthUrl, disconnectFromMsTeams } = useMsTeamsAuth( "graph-api-client-id", "https://example.com/callback", ); const handleConnect = () => { const authUrl = buildMsTeamsAuthUrl(); window.open(authUrl, "msTeamsAuth"); }; return (
); }; const App = () => ( ); ``` ## useMsTeamsTeams --- title: useMsTeamsTeams description: section: SDKs --- The `useMsTeamsTeams` hook is used to retrieve available teams within a Microsoft Teams-enabled Microsoft Entra tenant. This hook must be used within a `KnockMsTeamsProvider`. ## Parameters Accepts an object with the following properties: - **queryOptions** (`MsTeamsTeamQueryOptions`) - Optional query options for filtering, pagination, and field selection. ## Returns Returns an object with the following properties: - **data** (`MsTeamsTeam[]`) - Array of available Microsoft Teams. - **isLoading** (`boolean`) - Whether teams are being loaded or revalidating. - **refetch** (`() => void`) - Function to manually refetch the teams. ## Example ### Basic usage The following example demonstrates how to use the `useMsTeamsTeams` hook to display available Microsoft Teams. ```tsx import { KnockMsTeamsProvider, useMsTeamsTeams } from "@knocklabs/react"; const MsTeamsTeamList = () => { const { data: teams, isLoading, refetch } = useMsTeamsTeams({}); if (isLoading) return
Loading teams...
; return (

Available teams

    {teams.map((team) => (
  • {team.displayName}
  • ))}
); }; const App = () => ( ); ``` ### With query options You can customize the query with pagination and filtering options: ```tsx const { data: teams } = useMsTeamsTeams({ queryOptions: { maxCount: 500, limitPerPage: 50, filter: "displayName eq 'Engineering'", select: "id,displayName,description", }, }); ``` ## useMsTeamsChannels --- title: useMsTeamsChannels description: section: SDKs --- The `useMsTeamsChannels` hook is used to retrieve available Microsoft Teams channels for a specific team. This hook must be used within a `KnockMsTeamsProvider`. ## Parameters Accepts an object with the following properties: - **teamId** (`string`) - Optional ID of the Microsoft Teams team to get channels for. If not provided, no channels will be fetched. - **queryOptions** (`MsTeamsChannelQueryOptions`) - Optional query options for filtering and selecting channel fields. ## Returns Returns an object with the following properties: - **data** (`MsTeamsChannel[]`) - Array of available Microsoft Teams channels. - **isLoading** (`boolean`) - Whether channels are being loaded or revalidating. - **refetch** (`() => void`) - Function to manually refetch the channels. ## Example ### Basic usage The following example demonstrates how to use the `useMsTeamsChannels` hook to display available Microsoft Teams channels. ```tsx import { KnockMsTeamsProvider, useMsTeamsChannels } from "@knocklabs/react"; const MsTeamsChannelList = ({ teamId }: { teamId: string }) => { const { data: channels, isLoading, refetch } = useMsTeamsChannels({ teamId }); if (isLoading) return
Loading channels...
; return (

Available channels

    {channels.map((channel) => (
  • {channel.displayName}
  • ))}
); }; const App = () => ( ); ``` ### With query options You can customize the query with filtering and field selection options: ```tsx const { data: channels } = useMsTeamsChannels({ teamId: "team-123", queryOptions: { filter: "isArchived eq false and membershipType eq 'standard'", select: "id,displayName,description", }, }); ``` ## useConnectedMsTeamsChannels --- title: useConnectedMsTeamsChannels description: section: SDKs --- The `useConnectedMsTeamsChannels` hook is used to retrieve and manage Microsoft Teams channels that are currently connected for a specific recipient object. This hook must be used within a `KnockMsTeamsProvider`. ## Parameters Accepts an object with the following properties: - **msTeamsChannelsRecipientObject** (`RecipientObject`) *required* - The recipient object (with objectId and collection) to get connected channels for. ## Returns Returns an object with the following properties: - **data** (`MsTeamsChannelConnection[] | null`) - Array of connected Microsoft Teams channels for the recipient object. - **updateConnectedChannels** (`(channels: MsTeamsChannelConnection[]) => Promise`) - Function to update the connected channels for the recipient object. - **loading** (`boolean`) - Whether connected channels are being loaded or revalidating. - **error** (`string | null`) - Error message if the request failed. - **updating** (`boolean`) - Whether an update operation is in progress. ## Example ### Basic usage The following example demonstrates how to use the `useConnectedMsTeamsChannels` hook to display and manage connected Microsoft Teams channels. ```tsx import { KnockMsTeamsProvider, useConnectedMsTeamsChannels, } from "@knocklabs/react"; const ConnectedChannelsList = () => { const { data: channels, loading, error, updateConnectedChannels, updating, } = useConnectedMsTeamsChannels({ msTeamsChannelsRecipientObject: { objectId: "project-123", collection: "projects", }, }); if (loading) return
Loading connected channels...
; if (error) return
Error: {error}
; const handleDisconnect = async (channelId: string) => { const updatedChannels = channels?.filter((ch) => ch.ms_teams_channel_id !== channelId) || []; await updateConnectedChannels(updatedChannels); }; return (

Connected channels

{channels && channels.length > 0 ? (
    {channels.map((channel) => (
  • {channel.ms_teams_channel_id}
  • ))}
) : (

No channels connected

)}
); }; const App = () => ( ); ``` ### Updating connected channels You can add or update connected channels by calling `updateConnectedChannels` with the new array: ```tsx const { data: channels, updateConnectedChannels } = useConnectedMsTeamsChannels( { msTeamsChannelsRecipientObject: { objectId: "project-123", collection: "projects", }, }, ); const handleConnect = async (newChannel: MsTeamsChannelConnection) => { const updatedChannels = [...(channels || []), newChannel]; await updateConnectedChannels(updatedChannels); }; ``` ## useMsTeamsConnectionStatus --- title: useMsTeamsConnectionStatus description: section: SDKs --- The `useMsTeamsConnectionStatus` hook is used to check and manage the current Microsoft Teams connection status for a user. This hook is typically used internally by the `KnockMsTeamsProvider` and is not commonly used directly in application code. ## Parameters - **knock** (`Knock`) *required* - The authenticated Knock client instance. - **knockMsTeamsChannelId** (`string`) *required* - The Knock channel ID for the Microsoft Teams integration. - **tenantId** (`string`) *required* - The tenant ID for multi-tenant applications. ## Returns Returns an object with the following properties: - **connectionStatus** (`ConnectionStatus`) - The current connection status: 'connecting' | 'connected' | 'disconnected' | 'error' | 'disconnecting'. - **setConnectionStatus** (`(status: ConnectionStatus) => void`) - Function to update the connection status. - **errorLabel** (`string | null`) - Error message if the connection check failed. - **setErrorLabel** (`(errorLabel: string) => void`) - Function to set an error message. - **actionLabel** (`string | null`) - Label for the current action being performed. - **setActionLabel** (`(actionLabel: string | null) => void`) - Function to set an action label. ## Example ### Basic usage This hook is typically used internally by the `KnockMsTeamsProvider`. If you need to access the connection status in your components, use the `useKnockMsTeamsClient` hook instead: ```tsx import { KnockMsTeamsProvider, useKnockMsTeamsClient } from "@knocklabs/react"; const MsTeamsStatus = () => { const { connectionStatus } = useKnockMsTeamsClient(); return (

Microsoft Teams status: {connectionStatus}

); }; const App = () => ( ); ``` ### Advanced usage If you need to use this hook directly: ```tsx import { useKnockClient, useMsTeamsConnectionStatus } from "@knocklabs/react"; const MyComponent = () => { const knock = useKnockClient(); const { connectionStatus, errorLabel } = useMsTeamsConnectionStatus( knock, "ms-teams-channel-id", "tenant-id", ); if (connectionStatus === "connecting") return
Checking connection...
; if (errorLabel) return
Error: {errorLabel}
; return (

Status: {connectionStatus}

); }; ``` ## Types ## KnockOptions --- title: KnockOptions description: section: SDKs --- Configuration options for initializing the Knock client instance. ## Properties - **host** (`string`) - Optional custom API host for Knock. Defaults to the standard Knock API endpoint. - **logLevel** (`string`) - Optional logging level for debugging. Can be 'debug', 'info', 'warn', or 'error'. - **onUserTokenExpiring** (`function`) - Optional callback function that fires before a user token expires. - **timeBeforeExpirationInMs** (`number`) - Optional time in milliseconds before token expiration to trigger the callback. Default: 30000 (30 seconds). ## Example ```typescript const knockOptions: KnockOptions = { host: "https://api.knock.app", logLevel: "debug", onUserTokenExpiring: async (oldToken) => { // Refresh token logic return await refreshUserToken(oldToken); }, timeBeforeExpirationInMs: 60000, // 1 minute }; ``` ## UserIdentificationOptions --- title: UserIdentificationOptions description: section: SDKs --- User identification data to pass through to the `authenticate` method. ## Properties - **id** (`string`) - The unique identifier for the user. - **[attribute_name]** (`any`) - Additional attributes to attach to the user on identification. ## Example ```typescript const userOptions: UserIdentificationOptions = { id: "user-123", name: "John Doe", email: "john@example.com", customAttribute: "value", }; ``` ## I18nContent --- title: I18nContent description: section: SDKs --- Configuration object for internationalization content used to set translations available in child components exposed under `KnockFeedProvider`, `KnockSlackProvider`, and `KnockMsTeamsProvider`. ## Properties - **translations** (`Partial`) - A partial object containing translation overrides for the default English strings. - **locale** (`string`) - A valid locale code (e.g., 'en', 'es', 'fr'). ## Translations Interface The `Translations` interface includes the following properties: - **emptyFeedTitle** (`string`) - Title shown when the feed is empty. - **emptyFeedBody** (`string`) - Body text shown when the feed is empty. - **notifications** (`string`) - Label for notifications. - **poweredBy** (`string`) - Powered by text. - **markAllAsRead** (`string`) - Mark all as read button text. - **archiveNotification** (`string`) - Archive notification button text. - **all** (`string`) - All filter option text. - **unread** (`string`) - Unread filter option text. - **read** (`string`) - Read filter option text. - **unseen** (`string`) - Unseen filter option text. ## Example ```typescript const i18nConfig: I18nContent = { locale: "es", translations: { emptyFeedTitle: "No hay notificaciones", markAllAsRead: "Marcar todo como leído", notifications: "Notificaciones", }, }; ``` ## ConnectionStatus --- title: ConnectionStatus description: section: SDKs --- Represents the current status of a connection to an external service like Slack or Microsoft Teams. ## Values - **connecting** (`string`) - Connection is being established. - **connected** (`string`) - Connection has been successfully established. - **disconnected** (`string`) - No connection exists. - **error** (`string`) - Connection failed or encountered an error. - **disconnecting** (`string`) - Connection is being terminated. ## Example ```typescript const [status, setStatus] = useState("disconnected"); // When starting connection setStatus("connecting"); // On successful connection setStatus("connected"); // On error setStatus("error"); ``` ## RecipientObject --- title: RecipientObject description: section: SDKs --- Represents a recipient object used to identify a specific object and collection in Knock. ## Properties - **objectId** (`string`) - The unique identifier of the object. - **collection** (`string`) - The collection name that the object belongs to. ## Example ```typescript const recipientObject: RecipientObject = { objectId: "team-123", collection: "teams", }; // Used with Slack channels const slackRecipient: RecipientObject = { objectId: "workspace-456", collection: "workspaces", }; ``` ## SlackChannel --- title: SlackChannel description: section: SDKs --- Represents a Slack channel with its basic properties. ## Properties - **name** (`string`) - The display name of the Slack channel. - **id** (`string`) - The unique identifier of the Slack channel. - **is_private** (`boolean`) - Whether the channel is private or public. - **is_im** (`boolean`) - Whether this is a direct message channel. - **context_team_id** (`boolean`) - The team ID context for the channel. ## Example ```typescript const channel: SlackChannel = { name: "general", id: "C1234567890", is_private: false, is_im: false, context_team_id: true, }; ``` ## SlackChannelConnection --- title: SlackChannelConnection description: section: SDKs --- Represents a connection configuration for a Slack channel integration. ## Properties - **access_token** (`string`) - Optional access token for the Slack integration. - **channel_id** (`string`) - Optional Slack channel ID. - **incoming_webhook** (`string`) - Optional incoming webhook URL for the Slack channel. - **user_id** (`null`) - User ID (typically null for channel connections). ## Example ```typescript const connection: SlackChannelConnection = { access_token: "xoxb-1234567890-abcdef...", channel_id: "C1234567890", incoming_webhook: "https://hooks.slack.com/services/...", user_id: null, }; ``` ## SlackChannelQueryOptions --- title: SlackChannelQueryOptions description: section: SDKs --- Configuration options for querying Slack channels. ## Properties - **maxCount** (`number`) - The maximum number of channels to return. Default: 1000. - **limitPerPage** (`number`) - How many Slack channels will be returned per request. Default: 200. - **excludeArchived** (`boolean`) - Whether to exclude archived channels. Default: true. - **types** (`string`) - Types of channels to return. Default: 'private_channel,public_channel'. - **teamId** (`string`) - Filters channels to a specific team ID. Default: null. ## Example ```typescript const queryOptions: SlackChannelQueryOptions = { maxCount: 500, limitPerPage: 100, excludeArchived: true, types: "public_channel", teamId: "T1234567890", }; ``` ## MsTeamsTeam --- title: MsTeamsTeam description: section: SDKs --- Represents a Microsoft Teams team with its basic properties. ## Properties - **id** (`string`) - The unique identifier of the Microsoft Teams team. - **displayName** (`string`) - The display name of the team. - **description** (`string`) - Optional description of the team. ## Example ```typescript const team: MsTeamsTeam = { id: "19:abc123def456...", displayName: "Engineering Team", description: "Software development team", }; ``` ## MsTeamsTeamQueryOptions --- title: MsTeamsTeamQueryOptions description: section: SDKs --- Configuration options for querying Microsoft Teams teams. ## Properties - **maxCount** (`number`) - The maximum number of teams to return. Default: 1000. - **limitPerPage** (`number`) - How many teams will be returned per request. Default: 100. - **filter** (`string`) - OData $filter query parameter to filter teams. Default: null. - **select** (`string`) - OData $select query parameter to select fields. Default: 'id,displayName'. ## Example ```typescript const queryOptions: MsTeamsTeamQueryOptions = { maxCount: 250, limitPerPage: 50, filter: "displayName eq 'Engineering'", select: "id,displayName,description", }; ``` ## MsTeamsChannel --- title: MsTeamsChannel description: section: SDKs --- Represents a Microsoft Teams channel with its properties. ## Properties - **id** (`string`) - The unique identifier of the Microsoft Teams channel. - **displayName** (`string`) - The display name of the channel. - **description** (`string`) - Optional description of the channel. - **membershipType** (`string`) - Optional membership type of the channel. - **isArchived** (`boolean`) - Optional flag indicating if the channel is archived. - **createdDateTime** (`string`) - Optional creation date and time of the channel. ## Example ```typescript const channel: MsTeamsChannel = { id: "19:abc123def456...", displayName: "General", description: "General discussion channel", membershipType: "standard", isArchived: false, createdDateTime: "2023-01-15T10:30:00Z", }; ``` ## MsTeamsChannelConnection --- title: MsTeamsChannelConnection description: section: SDKs --- Represents a connection configuration for a Microsoft Teams channel integration. ## Properties - **ms_teams_tenant_id** (`string`) - Optional Microsoft Entra tenant ID. - **ms_teams_team_id** (`string`) - Optional Microsoft Teams team ID. - **ms_teams_channel_id** (`string`) - Optional Microsoft Teams channel ID. - **ms_teams_user_id** (`null`) - User ID (typically null for channel connections). - **incoming_webhook** (`object`) - Optional incoming webhook configuration object. ## Incoming Webhook Object - **url** (`string`) - The webhook URL for the Microsoft Teams channel. ## Example ```typescript const connection: MsTeamsChannelConnection = { ms_teams_tenant_id: "12345678-1234-1234-1234-123456789012", ms_teams_team_id: "19:abc123def456...", ms_teams_channel_id: "19:def456ghi789...", ms_teams_user_id: null, incoming_webhook: { url: "https://outlook.office.com/webhook/...", }, }; ``` ## MsTeamsChannelQueryOptions --- title: MsTeamsChannelQueryOptions description: section: SDKs --- Configuration options for querying Microsoft Teams channels within a team. ## Properties - **filter** (`string`) - OData $filter query parameter to filter channels. Default: 'isArchived eq false and membershipType eq 'standard''. - **select** (`string`) - OData $select query parameter to select fields. Default: 'id,displayName'. ## Example ```typescript const queryOptions: MsTeamsChannelQueryOptions = { filter: "isArchived eq false and membershipType eq 'standard'", select: "id,displayName,description,membershipType", }; ``` ## Migrating from @knocklabs/react-notification-feed This documentation will help you migrate to the new Knock React library --- title: "Migrating from @knocklabs/react-notification-feed" description: This documentation will help you migrate to the new Knock React library section: Building in-app UI --- This documentation will walk you through the steps of replacing `@knocklabs/react-notification-feed` with `@knocklabs/react`. Please report any issues you encounter while upgrading. ## Installation Install the new React package ```bash title="Install with npm" npm install @knocklabs/react ``` Or if you're using Yarn: ```bash title="Install with Yarn" yarn add @knocklabs/react ``` ## Changes to Knock providers `@knocklabs/react` introduces updated providers: - `KnockProvider` authenticates the current user and provides access to the Knock client. It now accepts `userId`, `apiKey`, and `userToken` props. - `KnockFeedProvider` connects to an in-app feed channel using the `feedId` prop and no longer accepts `userId`, `apiKey`, and `userToken` props. ### Before using `@knocklabs/react-notification-feed` This code sample demonstrates a typical implementation of the `NotificationFeedPopover` and annotates points of change in the component APIs. ```jsx title="Set up Knock providers" // Before import { KnockFeedProvider, NotificationIconButton, NotificationFeedPopover, } from "@knocklabs/react-notification-feed"; // This import should be updated import "@knocklabs/react-notification-feed/dist/index.css"; const YourAppLayout = () => { const [isVisible, setIsVisible] = useState(false); const notifButtonRef = useRef(null); return ( <> setIsVisible(!isVisible)} /> setIsVisible(false)} /> ); }; ``` ### After using `@knocklabs/react` With the new React SDK, the `KnockProvider` component now wraps the `KnockFeedProvider` component and handles authenticating with Knock. ```jsx // After import { KnockProvider, KnockFeedProvider, NotificationIconButton, NotificationFeedPopover, } from "@knocklabs/react"; // Updated CSS import from new package import "@knocklabs/react/dist/index.css"; const YourAppLayout = () => { const [isVisible, setIsVisible] = useState(false); const notifButtonRef = useRef(null); return ( // Updated props on KnockProvider <> setIsVisible(!isVisible)} /> setIsVisible(false)} /> ); }; ``` ## `rootless` removed and `NotificationFeedContainer` added The `KnockFeedProvider` no longer wraps its children with default styles. If you were setting the `rootless` prop to true, this change has no effect. Otherwise, you should wrap your notification feed with the `NotificationFeedContainer` component to ensure the feed is properly styled. ### Before ```jsx // Before import { KnockFeedProvider, NotificationFeed, } from "@knocklabs/react-notification-feed"; ; ``` ### After ```jsx // After import { KnockProvider, KnockFeedProvider, NotificationFeedContainer, NotificationFeed, } from "@knocklabs/react"; {/* Optionally, use the KnockFeedProvider to connect an in-app feed */} ; ``` ## Use Knock hooks You can continue to use the `useKnockFeed` hook to build headless feed experiences. You can use the `useKnockClient` hook to access the Knock client. ## Quick links - [`@knocklabs/react` on npm](https://www.npmjs.com/package/@knocklabs/react) - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Package on GitHub](https://github.com/knocklabs/javascript/tree/main/packages/react) - [Full React SDK reference](/in-app-ui/react/sdk/reference) - [JS SDK reference](/in-app-ui/javascript/sdk/reference) # JavaScript UI components ## Overview Learn more about the in-app notification UI you can build in your web application with Knock. --- title: "Building in-app UI in Javascript (Web)" description: Learn more about the in-app notification UI you can build in your web application with Knock. section: Building in-app UI --- If you're looking to use pre-built in-app UI elements for a web application, you can look at{" "} React components. } /> The Knock Javascript client SDK is a low-level set of methods for interacting with the Knock APIs from client-side Javascript web applications. The SDK is designed to help you easily integrate Knock into your application and build in-app notification experiences powered by Knock. ## Features - API methods for interacting with the [Knock in-app API](/in-app-ui/api-overview). - Managed websocket connections to the Knock real-time service. - State management for powering in-app feeds, with optimistic client-side updates. ## Getting started ```bash title="Installing the package" npm install @knocklabs/client ``` ## Links - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Package on GitHub](https://github.com/knocklabs/javascript/tree/main/packages/client) - [Javascript SDK reference](/in-app-ui/javascript/sdk/reference) - [Javascript SDK quick start](/in-app-ui/javascript/sdk/quick-start) # JavaScript SDK ## Overview Learn more about integrating Knock into your web applications through our Javascript SDKs. --- title: Knock Javascript (Web) SDK description: Learn more about integrating Knock into your web applications through our Javascript SDKs. section: SDKs --- The `@knocklabs/client` library is a low-level JavaScript SDK for interacting with Knock from the client side of your JS application. If you're looking to use pre-built in-app UI elements for a web application, you can look at{" "} React components. } /> **Quick links** - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Package on GitHub](https://github.com/knocklabs/javascript/tree/main/packages/client) - [Full reference documentation](/in-app-ui/javascript/sdk/reference) ## Need help? Our `@knocklabs/client` library is worked on full-time by the Knock JavaScript team. ### Join the community Ask questions and find answers on the following platforms: - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/javascript/issues) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Quick start Get started with our Javascript SDK to build notification feed, toasts, and inbox experiences. --- title: "Getting started with the Javascript SDK" description: Get started with our Javascript SDK to build notification feed, toasts, and inbox experiences. section: SDKs --- The `@knocklabs/client` library is a low-level JavaScript SDK for interacting with Knock from the client side of your JS application. This documentation shows some of the ways you can interact with the SDK: **Quick links** - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Package on GitHub](https://github.com/knocklabs/javascript/tree/main/packages/client) - [Full reference documentation](/in-app-ui/javascript/sdk/reference) ## Getting started To use this example, you'll need an account on Knock, as well as an in-app feed channel, with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) ## Installing dependencies ```bash title="Installing the package" npm install @knocklabs/client ``` ## Authenticating the current user ```javascript title="Authenticating the current user" import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knockClient.authenticate({ id: currentUser.id }, currentUser.knockUserToken); ``` In production environments with [enhanced security mode](/in-app-ui/security-and-authentication#authentication-with-enhanced-security) enabled, pass a signed user token from your backend as the second argument when you authenticate the client. ## Initialize a feed connection for the user The `Knock` class exposes a `Feed` via the `initialize` method that can be used to connect the authenticated user to a Knock Feed Channel. Additionally, the `Feed` exposes a stateful store to build client-side feeds and other notification experiences. ```javascript title="Working with the Knock feed" const knockFeed = knockClient.feeds.initialize( process.env.KNOCK_FEED_CHANNEL_ID, ); // Setup a real-time connection knockFeed.listenForUpdates(); // Fetch items for the feed knockFeed.fetch(); ``` ## Marking feed item statuses A feed instance supports marking items as seen, unseen, read, unread, archived, and unarchived: ```javascript title="Handling feed item statuses" // Initialize the feed as in above examples const knockFeed = knockClient.feeds.initialize( process.env.KNOCK_FEED_CHANNEL_ID, ); // Mark one or more items as read knockFeed.markAsRead(feedItemOrItems); // Mark one or more items as seen knockFeed.markAsSeen(feedItemOrItems); // Mark one or more items as archived knockFeed.markAsArchived(feedItemOrItems); // Mark one or more items as unread knockFeed.markAsUnread(feedItemOrItems); // Mark one or more items as unseen knockFeed.markAsUnseen(feedItemOrItems); // Mark one or more items as unarchived knockFeed.markAsUnarchived(feedItemOrItems); ``` ## Retrieving preferences for the user Preference calls from the browser use your public API key and an authenticated user. In production, pass a signed user token when you authenticate the client. See [Security and authentication](/in-app-ui/security-and-authentication) for details. You can use the JS SDK to retrieve the preferences for the authenticated user, which is useful to build in-app preference UIs. ```javascript title="Getting user preferences" const preferences = await knockClient.user.getPreferences(); ``` ## Setting preferences for the user Similar to retrieving preferences, the `Knock` class also allows you to set preferences directly in the client for the authenticated user. ```javascript title="Setting user preferences" await knockClient.user.setPreferences({ channel_types: { email: true, sms: false }, workflows: { "dinosaurs-loose": { channel_types: { email: false, in_app_feed: true }, }, }, }); ``` ## Automatically disconnecting sockets from inactive tabs Optionally, you can configure the client to disconnect socket connections with inactive tabs after a brief delay. If the tab becomes active again, the socket will reconnect to continue receiving real-time updates. ```javascript title="Automatically manage socket connections" // Initialize the feed and configure the automatic disconnect settings const feedClient = knockClient.feeds.initialize( process.env.KNOCK_FEED_CHANNEL_ID, { // Turn on the automatic connection manager auto_manage_socket_connection: true, // Optionally, customize the delay amount in milliseconds. Defaults to 2000ms or 2s auto_manage_socket_connection_delay: 2500, }, ); ``` ## Reference Complete API reference for the Knock Javascript SDK. --- title: "Javascript SDK API Reference" description: Complete API reference for the Knock Javascript SDK. tags: ["mark as read"] section: SDKs --- In this section, you'll find the documentation for the classes and methods available in the [`@knocklabs/client`](https://github.com/knocklabs/javascript/tree/main/packages/client) library. ## Knock Complete reference for the Knock class in the Knock JavaScript SDK. --- title: "Knock class" description: Complete reference for the Knock class in the Knock JavaScript SDK. section: SDKs --- The top-level `Knock` class, used to interact with a client instance. ## Parameters - **apiKey** (`string*`) - The public API key for the Knock environment. - **options** (`KnockOptions`) - Additional options to pass through. ## Example ```javascript import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); ``` ## Properties ### `userId` Returns the user ID of the authenticated user. **Returns**: `string` ### `feeds` Returns a `FeedClient` instance that can be initialized to return a feed. Optionally a feed can be initialized with a default set of `FeedClientOptions` which will be applied to all subsequent requests. **Returns**: `Feed` instance **Example**: ```javascript import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // knockFeed is now a `Feed` instance. const knockFeed = knock.feeds.initialize(process.env.KNOCK_FEED_CHANNEL_ID, { archived: "exclude", page_size: 25, }); ``` ### `user` Returns a `UserClient` instance to interact with the users API for the current, authenticated user. **Returns**: `UserClient` instance **Example**: ```javascript import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const channelData = await knock.user.getChannelData({ channelId: process.env.KNOCK_CHANNEL_ID, }); ``` ### `client` Returns an instance of an authenticated `ApiClient` that can be used to make HTTP and Websocket requests to Knock. **Returns**: `ApiClient` instance ## Methods ### `authenticate` Authenticates the current user and creates a new Knock session. **Parameters**: - **user** (`UserIdentificationOptions`) - User identification data. - **userToken** (`string`) - JWT for the authenticated user. Not required in development environments. - **options** (`AuthenticateOptions`) - Additional options to authenticate your Knock user with. **Returns**: `void` **Example**: ```javascript import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id, name: "Knock" }, user.knockUserToken); ``` ### `teardown` Tears down a current session and disconnects any connected sockets. **Returns**: `void` ## ApiClient Complete reference for the ApiClient in the Knock JavaScript SDK. --- title: "ApiClient reference" description: Complete reference for the ApiClient in the Knock JavaScript SDK. section: SDKs --- The API client exposes direct functions for communicating with the Knock API over HTTP and websocket. ## Properties ### `socket` **Returns**: a `Socket` ## FeedClient Complete reference for the FeedClient in the Knock JavaScript SDK. --- title: "FeedClient reference" description: Complete reference for the FeedClient in the Knock JavaScript SDK. section: SDKs --- Represents the connection between a user and a feed, including methods for interacting with the items on that feed. Also includes a stateful store that can be used to build in-app notification experiences easily. See [`FeedStoreState`](#feedstorestate) for more on the shape of the store. ## Properties ### `store` **Returns**: `StoreApi` ## Methods ### `listenForUpdates` Connects the feed instance to the realtime socket so that any new items published to the feed are received over the websocket. **Returns**: `void` **Example**: ```javascript title="Connecting to a realtime stream" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const knockFeed = knock.feeds.initialize(process.env.KNOCK_FEED_CHANNEL_ID); // Listen for updates knockFeed.listenForUpdates(); // Stop listening knockFeed.teardown(); ``` ### `on` Binds an event handler to be invoked when the event is triggered. **Events**: - **items.received.realtime** (`event`) - Invoked whenever items are received in realtime from the socket. - **items.received.page** (`event`) - Invoked whenever items are received from performing a fetch. Will be invoked for the initial fetch as well. - **items.received.*** (`event`) - Invoked when any event with a name matching this pattern is emitted, e.g. `items.received.realtime` or `items.received.page`. - **items.archived** (`event`) - Invoked when one or more items are archived. - **items.unarchived** (`event`) - Invoked when one or more items are unarchived. - **items.seen** (`event`) - Invoked when one or more items are seen. - **items.unseen** (`event`) - Invoked when one or more items are unseen. - **items.read** (`event`) - Invoked when one or more items are read. - **items.unread** (`event`) - Invoked when one or more items are unread. - **items.all_archived** (`event`) - Invoked when all items in the current scope are marked as archived. - **items.all_seen** (`event`) - Invoked when all items in the current scope are marked as seen. - **items.all_read** (`event`) - Invoked when all items in the current scope are marked as read. - **items.*** (`event`) - Invoked when any event with a name matching this pattern is emitted, e.g. `items.read` or `items.archived`. **Parameters**: - **eventName** (enum of `messages.new` | `items.received.realtime` | `items.received.page` | `items.received.*`) - The type of event to bind to. - **callback** (`function`) - A function to be invoked when the event is triggered. **Returns**: `void`. **Example**: ```javascript title="Listening to items being received" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const knockFeed = knock.feeds.initialize(process.env.KNOCK_FEED_CHANNEL_ID); knockFeed.on("items.received.page", ({ items }) => { console.log(items); }); ``` ### `off` Unbinds an existing event handler previously bound with `on`. Use this method to cleanup bound event handlers. **Returns**: `void`. ### `getState` Programmatically access the current `FeedStoreState`. **Returns**: `FeedStoreState`. ### `markAllAsSeen` Marks all of the items in the store optimistically as seen and performs a server-side request to mark **all items on the feed in the current scope** as seen. Broadcasts an `items.all_seen` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. **Returns**: `Promise` ### `markAsSeen` Marks the given items as `seen`. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAsUnseen` Removes the `seen` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAllAsRead` Marks all of the items in the store optimistically as read and performs a server-side request to mark **all items on the feed in the current scope** as read. Broadcasts an `items.all_read` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. **Returns**: `Promise` ### `markAsRead` Sets the `read` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAsUnread` Removes the `read` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAllAsArchived` Marks all of the items in the store optimistically as archived and performs a server-side request to mark **all items on the feed in the current scope** as archived. Broadcasts an `items.all_archived` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. **Returns**: `Promise` ### `markAsArchived` Sets the `archived` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts an `items.archived` event. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAsUnarchived` Removes the `archived` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. **Returns**: `Promise` ### `markAsInteracted` Sets the `interacted` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts an `items.interacted` event. **Parameters**: - **itemOrItems** (`FeedItemOrItems`) - A single `FeedItem` or a list of `FeedItem` to perform the update on. - **metadata** (`Record`) - Additional metadata to be stored with the interaction event. **Returns**: `Promise` ### `fetch` Fetches items from the feed. Emits `items.received.page` events on a successful fetch. **Parameters**: - **options** (`FetchFeedOptions`) - Options to pass through to the feed request. **Returns**: `Promise` ### `fetchNextPage` Fetches the next page of the feed items (if there are any more to fetch). Emits `items.received.page` events on a successful fetch. Note: this will apply any current feed filters and append returned items to the end of the current set of items. ## GuideClient Complete reference for the GuideClient in the Knock JavaScript SDK. --- title: "GuideClient reference" description: Complete reference for the GuideClient in the Knock JavaScript SDK. section: SDKs --- Client for managing in-app guides, including real-time updates, location tracking, and engagement events. ## Constructor Initializes a new guide client instance. **Parameters**: - **knock** (`Knock`) - The Knock client instance to use for API requests. - **channelId** (`string`) - The channel ID for the guides integration. - **targetParams** (`TargetParams`) - Optional targeting parameters for guide selection (e.g., user properties, tenant). - **options** (`ConstructorOpts`) - Optional configuration options for the guide client. **Constructor Options**: - **trackLocationFromWindow** (`boolean`) - Whether to automatically track location changes from the browser window. Defaults to true. - **throttleCheckInterval** (`number`) - Interval in milliseconds for throttle checking. Defaults to 30000 (30 seconds). - **orderResolutionDuration** (`number`) - Duration in milliseconds to wait before resolving guide order. Defaults to 50ms. **Returns**: `KnockGuideClient` ## Properties ### `store` A Tanstack Store instance containing the current state of guides, location, and other client data. **Type**: `Store StoreState>` ## Core Methods ### `fetch` Fetches guides from the API based on the current targeting parameters and optional filters. **Parameters**: - **opts** (`object`) - Optional fetch options. - **opts.filters** (`QueryFilterParams`) - Optional filter parameters to apply to the guide query. **Returns**: `Promise` **Example**: ```javascript title="Fetching guides" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const guideClient = knock.guides.initialize("in-app-guide-channel"); // Fetch all eligible guides const status = await guideClient.fetch(); // Fetch with filters const filteredStatus = await guideClient.fetch({ filters: { type: "tooltip" }, }); ``` ### `subscribe` Subscribes to real-time guide updates via websocket connection. **Returns**: `void` **Example**: ```javascript title="Subscribing to real-time updates" const guideClient = knock.guides.initialize("in-app-guide-channel"); // Subscribe to real-time updates guideClient.subscribe(); // Remember to cleanup when component unmounts // guideClient.unsubscribe(); ``` ### `unsubscribe` Unsubscribes from real-time guide updates and cleans up websocket connections. **Returns**: `void` ### `cleanup` Performs complete cleanup of the guide client, including unsubscribing from websockets, removing event listeners, and clearing intervals. **Returns**: `void` **Example**: ```javascript title="Cleaning up guide client" // In React useEffect cleanup or component unmount useEffect(() => { const guideClient = knock.guides.initialize("in-app-guide-channel"); return () => { guideClient.cleanup(); }; }, []); ``` ## Selection Methods ### `selectGuides` Selects multiple guides based on the current state and optional filters. **Parameters**: - **state** (`StoreState`) - The current store state. - **filters** (`SelectFilterParams`) - Optional filters to apply during selection. **Filter Parameters**: - **type** (`string`) - Filter guides by type (e.g., 'tooltip', 'modal', 'banner'). - **key** (`string`) - Filter guides by specific guide key. **Returns**: `KnockGuide[]` **Example**: ```javascript title="Selecting multiple guides" const guideClient = knock.guides.initialize("in-app-guide-channel"); // Get all guides const allGuides = guideClient.selectGuides(guideClient.store.state); // Get guides of a specific type const tooltips = guideClient.selectGuides(guideClient.store.state, { type: "tooltip", }); ``` ### `selectGuide` Selects a single guide based on priority, throttling rules, and group staging logic. **Parameters**: - **state** (`StoreState`) - The current store state. - **filters** (`SelectFilterParams`) - Optional filters to apply during selection. **Returns**: `KnockGuide | undefined` **Example**: ```javascript title="Selecting a single guide" const guideClient = knock.guides.initialize("in-app-guide-channel"); // Get the highest priority guide const guide = guideClient.selectGuide(guideClient.store.state); if (guide) { console.log(`Selected guide: ${guide.key}`); // Get the next step to show const step = guide.getStep(); if (step) { console.log(`Show step: ${step.ref}`); } } ``` ## Location Tracking ### `setLocation` Manually sets the current location for guide activation rule evaluation. **Parameters**: - **href** (`string`) - The URL to set as the current location. - **additionalParams** (`Partial`) - Optional additional state parameters to update. **Returns**: `void` **Example**: ```javascript title="Setting location manually" const guideClient = knock.guides.initialize("in-app-guide-channel"); // Manually set location (useful for SPAs) guideClient.setLocation("/dashboard/analytics"); // Set location with additional debug params guideClient.setLocation("/dashboard", { debug: { forcedGuideKey: "onboarding-guide" }, }); ``` ## Engagement Methods ### `markAsSeen` Marks a guide step as seen by the user. **Parameters**: - **guide** (`GuideData`) - The guide containing the step. - **step** (`GuideStepData`) - The step to mark as seen. **Returns**: `Promise` ### `markAsInteracted` Marks a guide step as interacted with by the user. **Parameters**: - **guide** (`GuideData`) - The guide containing the step. - **step** (`GuideStepData`) - The step to mark as interacted. - **metadata** (`GenericData`) - Optional metadata about the interaction. **Returns**: `Promise` ### `markAsArchived` Marks a guide step as archived by the user. **Parameters**: - **guide** (`GuideData`) - The guide containing the step. - **step** (`GuideStepData`) - The step to mark as archived. **Returns**: `Promise` **Example**: ```javascript title="Using engagement methods" const guideClient = knock.guides.initialize("in-app-guide-channel"); const guide = guideClient.selectGuide(guideClient.store.state); if (guide) { const step = guide.getStep(); if (step) { // Mark as seen when guide is displayed await step.markAsSeen(); // Mark as interacted when user clicks await step.markAsInteracted({ action: "clicked", element: "cta-button", }); // Mark as archived when user dismisses await step.markAsArchived(); } } ``` ## Debug Support The guide client supports debug mode through URL parameters: - `knock_guide_key` - Forces a specific guide to show - `knock_preview_session_id` - Shows preview guides from a specific session **Example**: ``` https://yourapp.com/dashboard?knock_guide_key=onboarding-tooltip ``` ## MessageClient Complete reference for the MessageClient in the Knock JavaScript SDK. --- title: "MessageClient reference" description: Complete reference for the MessageClient in the Knock JavaScript SDK. section: SDKs --- Client for interacting with message operations in the Knock API. ## Constructor Initializes a new message client instance. **Parameters**: - **knock** (`Knock`) - The Knock client instance to use for API requests. **Returns**: `MessageClient` ## Methods ### `get` Retrieves a specific message by its ID. **Parameters**: - **messageId** (`string`) - The unique identifier of the message to retrieve. **Returns**: `Promise` **Example**: ```javascript title="Getting a message" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const message = await knock.messages.get("message-id-123"); ``` ### `updateStatus` Updates the engagement status of a message. For the "interacted" status, additional metadata can be provided. **Parameters**: - **messageId** (`string`) - The unique identifier of the message to update. - **status** (`MessageEngagementStatus`) - The engagement status to set (e.g., 'read', 'seen', 'archived', 'interacted'). - **options** (`UpdateMessageStatusOptions`) - Optional parameters. Required when status is 'interacted' to provide metadata. **Returns**: `Promise` **Example**: ```javascript title="Updating message status" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Mark as read const readMessage = await knock.messages.updateStatus("message-id-123", "read"); // Mark as interacted with metadata const interactedMessage = await knock.messages.updateStatus( "message-id-123", "interacted", { metadata: { action: "clicked", button: "cta" } }, ); ``` ### `removeStatus` Removes an engagement status from a message. Note: Cannot remove "interacted" status. **Parameters**: - **messageId** (`string`) - The unique identifier of the message to update. - **status** (`Exclude`) - The engagement status to remove (e.g., 'read', 'seen', 'archived'). Cannot be 'interacted'. **Returns**: `Promise` **Example**: ```javascript title="Removing message status" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Mark as unread const unreadMessage = await knock.messages.removeStatus( "message-id-123", "read", ); // Mark as unseen const unseenMessage = await knock.messages.removeStatus( "message-id-123", "seen", ); ``` ### `batchUpdateStatuses` Updates the engagement status of multiple messages in a single request. **Parameters**: - **messageIds** (`string[]`) - Array of message IDs to update. - **status** (`MessageEngagementStatus | 'unseen' | 'unread' | 'unarchived'`) - The engagement status to set on all messages. Can include removal statuses like 'unseen', 'unread', 'unarchived'. - **options** (`UpdateMessageStatusOptions`) - Optional parameters. Required when status is 'interacted' to provide metadata. **Returns**: `Promise` **Example**: ```javascript title="Batch updating message statuses" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Mark multiple messages as read const updatedMessages = await knock.messages.batchUpdateStatuses( ["msg-1", "msg-2", "msg-3"], "read", ); // Mark multiple messages as interacted const interactedMessages = await knock.messages.batchUpdateStatuses( ["msg-1", "msg-2"], "interacted", { metadata: { source: "bulk_action" } }, ); ``` ### `bulkUpdateAllStatusesInChannel` Updates the engagement status of all messages in a specific channel. **Parameters**: - **channelId** (`string`) - The ID of the channel containing the messages to update. - **status** (`MessageEngagementStatus`) - The engagement status to set on all messages in the channel. - **options** (`object`) - Additional options for the bulk update operation. **Returns**: `Promise` **Example**: ```javascript title="Bulk updating all messages in a channel" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Mark all messages in a channel as read const bulkOperation = await knock.messages.bulkUpdateAllStatusesInChannel({ channelId: "in-app-feed-channel", status: "read", options: {}, }); ``` ## UserClient Complete reference for the UserClient in the Knock JavaScript SDK. --- title: "UserClient reference" description: Complete reference for the UserClient in the Knock JavaScript SDK. section: SDKs --- Client for interacting with user-related operations in the Knock API. ## Methods ### `get` Retrieves the current, authenticated user by calling the [get user endpoint](/api-reference/users/get) directly from the client. **Returns**: `Promise` **Example**: ```javascript title="Getting the authenticated user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const user = await knock.user.get(); ``` ### `identify` Identifies a user by calling the [identify user endpoint](/api-reference/users/update) directly from the client. **Parameters**: - **properties** (`object`) - An object of key-value pairs for attributes you want to associate with the user. **Returns**: `Promise` **Example**: ```javascript title="Identifying a user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const user = await knock.user.identify({ id: "1", name: "John Hammond", email: "jhammond@ingen.net", }); ``` ### `getAllPreferences` Retrieves all preferences for the authenticated user by calling the [get preferences endpoint](/api-reference/users/get_preferences) directly from the client. **Returns**: `Promise` **Example**: ```javascript title="Getting preferences for the user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const preferences = await knock.user.getAllPreferences(); ``` ### `getPreferences` Retrieves a preference set for the authenticated user by calling the [get preferences endpoint](/api-reference/users/get_preferences) directly from the client. **Parameters**: - **preferenceSet** (`String`) - The preference set from Knock. - **tenant** (`String (optional)`) - The tenant from Knock. **Returns**: `Promise` **Example**: ```javascript title="Getting a preference set for the user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const preferenceSet = await knock.user.getPreferences({ preferenceSet: "default", }); ``` ### `setPreferences` Updates the authenticated user's preferences by calling the [set preferences endpoint](/api-reference/users/set_preferences) directly from the client. **Parameters**: - **preferenceSet** (`SetPreferencesProperties`) - The preferences to set for the current user. **Returns**: `Promise` **Example**: ```javascript title="Setting preferences for the user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const preferenceSet = await knock.user.setPreferences({ channel_types: { email: true, sms: false }, workflows: { "dinosaurs-loose": { channel_types: { email: false, in_app_feed: true }, }, }, }); ``` ### `getChannelData` Retrieves channel data for the authenticated user by calling the [get channel data endpoint](/api-reference/users/get_channel_data) directly from the client. **Parameters**: - **channelId** (`String`) - The channel ID from Knock. **Returns**: `Promise` **Example**: ```javascript title="Getting channel data for the user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const channelData = await knock.user.getChannelData({ channelId: process.env.KNOCK_CHANNEL_ID, }); ``` ### `setChannelData` Updates the channel data for the current user by calling the [set channel data endpoint](/api-reference/users/set_channel_data) directly from the client. **Parameters**: - **channelId** (`String`) - The channel ID to update the channel data for. - **channelData** (`Any`) - The data to update for the channel data. **Returns**: `Promise` **Example**: ```javascript title="Setting channel data for the user" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const channelData = await knock.user.setChannelData({ channelId: process.env.KNOCK_CHANNEL_ID, data: { tokens: ["some-fcm-token"], }, }); ``` ## ObjectClient Complete reference for the ObjectClient in the Knock JavaScript SDK. --- title: "ObjectClient reference" description: Complete reference for the ObjectClient in the Knock JavaScript SDK. section: SDKs --- Client for interacting with object channel data operations in the Knock API. ## Constructor Initializes a new object client instance. **Parameters**: - **instance** (`Knock`) - The Knock client instance to use for API requests. **Returns**: `ObjectClient` ## Methods ### `getChannelData` Retrieves channel data for a specific object in a collection. **Parameters**: - **objectId** (`string`) - The unique identifier of the object. - **collection** (`string`) - The collection that contains the object. - **channelId** (`string`) - The channel ID to retrieve data for. **Returns**: `Promise>` **Type Parameters**: - **T** (`GenericData`) - The expected shape of the channel data. Defaults to GenericData. **Example**: ```javascript title="Getting object channel data" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Get channel data for an object const channelData = await knock.objects.getChannelData({ objectId: "project-123", collection: "projects", channelId: "slack-channel-id", }); // Get channel data with typed response interface SlackChannelData { channel: string; webhook_url: string; } const typedChannelData = (await knock.objects.getChannelData) < SlackChannelData > { objectId: "project-123", collection: "projects", channelId: "slack-channel-id", }; ``` ### `setChannelData` Sets or updates channel data for a specific object in a collection. **Parameters**: - **objectId** (`string`) - The unique identifier of the object. - **collection** (`string`) - The collection that contains the object. - **channelId** (`string`) - The channel ID to set data for. - **data** (`GenericData`) - The channel data to store for the object. **Returns**: `Promise` **Example**: ```javascript title="Setting object channel data" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); // Set Slack channel data for a project const result = await knock.objects.setChannelData({ objectId: "project-123", collection: "projects", channelId: "slack-channel-id", data: { channel: "#project-notifications", webhook_url: "https://hooks.slack.com/services/...", notify_on_updates: true, }, }); // Set email channel data for a user const emailResult = await knock.objects.setChannelData({ objectId: "user-456", collection: "users", channelId: "email-channel-id", data: { email: "user@example.com", preferences: { marketing: false, notifications: true, }, }, }); ``` ## SlackClient Complete reference for the SlackClient in the Knock JavaScript SDK. --- title: "SlackClient reference" description: Complete reference for the SlackClient in the Knock JavaScript SDK. section: SDKs --- Client for interacting with Slack integration operations in the Knock API. ## Constructor Initializes a new Slack client instance. **Parameters**: - **instance** (`Knock`) - The Knock client instance to use for API requests. **Returns**: `SlackClient` ## Methods ### `authCheck` Checks the authentication status for a Slack channel integration. **Parameters**: - **tenant** (`string`) - The tenant identifier to check authentication for. - **knockChannelId** (`string`) - The Knock channel ID for the Slack integration. **Returns**: `Promise` **Example**: ```javascript title="Checking Slack authentication" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const authStatus = await knock.slack.authCheck({ tenant: "tenant-123", knockChannelId: "slack-channel-id", }); ``` ### `getChannels` Retrieves available Slack channels for the authenticated integration. **Parameters**: - **knockChannelId** (`string`) - The Knock channel ID for the Slack integration. - **tenant** (`string`) - The tenant identifier. - **queryOptions** (`SlackChannelQueryOptions`) - Optional query parameters for filtering and pagination. **Query Options**: - **cursor** (`string`) - Pagination cursor for retrieving the next set of results. - **limit** (`number`) - Maximum number of channels to return. - **excludeArchived** (`boolean`) - Whether to exclude archived channels from the results. - **teamId** (`string`) - Slack team ID to filter channels by. - **types** (`string[]`) - Array of channel types to include (e.g., ['public_channel', 'private_channel']). **Returns**: `Promise` **Example**: ```javascript title="Getting Slack channels" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const channels = await knock.slack.getChannels({ knockChannelId: "slack-channel-id", tenant: "tenant-123", queryOptions: { limit: 50, excludeArchived: true, types: ["public_channel", "private_channel"], }, }); ``` ### `revokeAccessToken` Revokes the access token for a Slack integration, effectively disconnecting it. **Parameters**: - **tenant** (`string`) - The tenant identifier. - **knockChannelId** (`string`) - The Knock channel ID for the Slack integration to revoke access for. **Returns**: `Promise` **Example**: ```javascript title="Revoking Slack access token" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const result = await knock.slack.revokeAccessToken({ tenant: "tenant-123", knockChannelId: "slack-channel-id", }); ``` ## MsTeamsClient Complete reference for the Microsoft Teams client in the Knock JavaScript SDK. --- title: "MsTeamsClient reference" description: Complete reference for the Microsoft Teams client in the Knock JavaScript SDK. section: SDKs --- Client for interacting with Microsoft Teams integration operations in the Knock API. ## Constructor Initializes a new Microsoft Teams client instance. **Parameters**: - **instance** (`Knock`) - The Knock client instance to use for API requests. **Returns**: `MsTeamsClient` ## Methods ### `authCheck` Checks the authentication status for a Microsoft Teams channel integration. **Parameters**: - **tenant** (`string`) - The tenant identifier to check authentication for. - **knockChannelId** (`string`) - The Knock channel ID for the Microsoft Teams integration. **Returns**: `Promise` **Example**: ```javascript title="Checking Microsoft Teams authentication" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const authStatus = await knock.msTeams.authCheck({ tenant: "tenant-123", knockChannelId: "ms-teams-channel-id", }); ``` ### `getTeams` Retrieves available Microsoft Teams teams for the authenticated integration. **Parameters**: - **knockChannelId** (`string`) - The Knock channel ID for the Microsoft Teams integration. - **tenant** (`string`) - The tenant identifier. - **queryOptions** (`MsTeamsTeamQueryOptions`) - Optional Microsoft Graph API query parameters for filtering and pagination. **Query Options**: - **$filter** (`string`) - OData filter expression to filter teams (e.g., 'displayName eq 'Marketing Team'). - **$select** (`string`) - Comma-separated list of properties to include in the response. - **$top** (`number`) - Maximum number of teams to return. - **$skiptoken** (`string`) - Pagination token for retrieving the next set of results. **Returns**: `Promise` **Example**: ```javascript title="Getting Microsoft Teams teams" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const teams = await knock.msTeams.getTeams({ knockChannelId: "ms-teams-channel-id", tenant: "tenant-123", queryOptions: { $top: 50, $filter: "archived eq false", $select: "id,displayName,description", }, }); ``` ### `getChannels` Retrieves available Microsoft Teams channels for a specific team. **Parameters**: - **knockChannelId** (`string`) - The Knock channel ID for the Microsoft Teams integration. - **teamId** (`string`) - The Microsoft Teams team ID to get channels for. - **tenant** (`string`) - The tenant identifier. - **queryOptions** (`MsTeamsChannelQueryOptions`) - Optional Microsoft Graph API query parameters for filtering. **Query Options**: - **$filter** (`string`) - OData filter expression to filter channels (e.g., 'membershipType eq 'standard'). - **$select** (`string`) - Comma-separated list of properties to include in the response. **Returns**: `Promise` **Example**: ```javascript title="Getting Microsoft Teams channels" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const channels = await knock.msTeams.getChannels({ knockChannelId: "ms-teams-channel-id", teamId: "team-abc-123", tenant: "tenant-123", queryOptions: { $filter: "membershipType eq 'standard'", $select: "id,displayName,description,membershipType", }, }); ``` ### `revokeAccessToken` Revokes the access token for a Microsoft Teams integration, effectively disconnecting it. **Parameters**: - **tenant** (`string`) - The tenant identifier. - **knockChannelId** (`string`) - The Knock channel ID for the Microsoft Teams integration to revoke access for. **Returns**: `Promise` **Example**: ```javascript title="Revoking Microsoft Teams access token" import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); const result = await knock.msTeams.revokeAccessToken({ tenant: "tenant-123", knockChannelId: "ms-teams-channel-id", }); ``` ## Types ## KnockOptions --- title: "KnockOptions" description: section: SDKs --- Configuration options for initializing the Knock client instance. ## Properties - **host** (`string`) - A base URL to use for all API requests to Knock. - **logLevel** (`'debug' | null`) - When set to debug, will output log events. ## AuthenticateOptions --- title: "AuthenticateOptions" description: section: SDKs --- Options to pass through to the `authenticate` method. ## Properties - **onUserTokenExpiring** (`(oldToken: string) => Promise`) - A callback to provide that will fire before a user Token expires. By default will fire 30s before the token is set to expire. If a string is returned from the resolved promise, that will be used as the new user token. - **timeBeforeExpirationInMs** (`number`) - Determines the amount of time in milliseconds before the token expires that the callback should be fired. ## FeedStoreState --- title: "FeedStoreState" description: section: SDKs --- A **[zustand](https://github.com/pmndrs/zustand) state store** that holds the current feed state, including item counts, for easy notification feed rendering within your application. The FeedStoreState is entirely managed by the `Feed` instance, and any calls to `fetch()`, `markAsX`, or `fetchNextPage` will update the state accordingly. ## Properties - **items** (`FeedItem[]`) - An ordered list of feed items to be rendered. - **pageInfo** (`PageInfo`) - The page info of the last successful fetch. - **metadata** (`FeedMetadata`) - The current feed metadata including unread, unseen, read, and total counts of items in the feed. - **loading** (`boolean`) - Whether or not the feed is currently loading. - **networkStatus** (`NetworkStatus`) - Represents the various network states the feed can be in, including differentiating between 'fetching more' and 'fetching'. # Angular UI components ## Overview Learn more about the in-app notifications experiences you can build in Angular with Knock. --- title: "Building in-app UI in Angular" description: Learn more about the in-app notifications experiences you can build in Angular with Knock. section: Building in-app UI --- Today we do not have Knock-built, out-of-the-box Angular components for in-app notifications UI. Our Knock community built this Angular in-app feed example you can use to get started with an Angular in-app feed in your own application. If you have any questions, or if you have an in-app use case that is blocking your adoption of Knock, please contact our sales team. # React Native UI components ## Overview Learn more about the in-app notification UI you can build in your React Native application with Knock. --- title: "Building in-app UI in React Native" description: Learn more about the in-app notification UI you can build in your React Native application with Knock. section: Building in-app UI --- The Knock React Native SDK provides pre-built UI components that you can use to easily get up and running with a fully functional notification feed experience in your product. You can also use a set of React hooks and API bindings for you to build custom UI on top of to power in-app notification experiences in your React Native applications. ## Features - API methods for interacting with the [Knock in-app API](/in-app-ui/api-overview). - Managed websocket connections to the Knock real-time service. - State management for powering in-app feeds, with optimistic client-side updates. ## Getting started Please reference our [React Native SDK documentation](/in-app-ui/react-native/sdk/quick-start) to set up the library. ```bash title="Install the Knock React Native SDK" npm install @knocklabs/react-native ``` When using React Native with Expo, install [our Expo SDK](/in-app-ui/react-native/sdk/overview) instead: ```bash title="Install the Knock Expo SDK" npm install @knocklabs/expo ``` ### In-app notifications - `NotificationFeed`: A full-page list of notifications. - `NotificationIconButton`: A button with a badge count for notifications, often used to open the `NotificationFeed`. ## Guides - [**Notification feed**](/in-app-ui/react-native/notification-feeds): Learn how to build an in-app feed powered by Knock in your React Native application. ## Links - [React Native SDK reference](/in-app-ui/react-native/sdk/reference) - [Expo SDK reference](/in-app-ui/expo/sdk/reference) - [JavaScript SDK reference](/in-app-ui/javascript/sdk/reference) - [`@knocklabs/react-native` on npm](https://www.npmjs.com/package/@knocklabs/react-native) - [`@knocklabs/expo` on npm](https://www.npmjs.com/package/@knocklabs/expo) ## Feed How to build notification feeds powered by Knock in your React Native applications. --- title: "Building notification feeds with React Native" description: How to build notification feeds powered by Knock in your React Native applications. tags: ["inbox", "feeds", "toasts"] section: Building in-app UI --- This page provides common recipes to help you build in-app feed experiences within your React Native applications. The SDK handles all aspects of managing the data surrounding notifications on your behalf, including managing unread badge counts. **Quick links**: - [`@knocklabs/react-native` library reference](/in-app-ui/react-native/sdk/reference) - [`@knocklabs/client` library reference](/in-app-ui/javascript/sdk/reference) ## Getting started To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react-native ``` ## Rendering a notification feed By default, Knock feeds are accessible to anyone who has the feed ID. This makes it easy to get started in development. To secure your feed for production, enable enhanced security mode in your Knock dashboard and pass a signed {" "}userToken as a prop to the KnockFeedProvider component. For more information, visit the security & authentication documentation for client-side applications. } /> ```jsx import { KnockFeedProvider, KnockProvider, NotificationIconButton, NotificationFeed, FilterStatus, } from "@knocklabs/react-native"; import React, { useCallback, useState } from "react"; import { StyleSheet, View, StatusBar } from "react-native"; const App: React.FC = () => { const [isNotificationFeedOpen, setIsNotificationFeedOpen] = useState(false); const onTopActionButtonTap = useCallback(() => { setIsNotificationFeedOpen(!isNotificationFeedOpen); }, [isNotificationFeedOpen]); return ( {!isNotificationFeedOpen && ( )} {isNotificationFeedOpen && ( { console.log("Action button tapped", button, item); }} onRowTap={(item) => { console.log("Row tapped", item); }} /> )} ); }; export default App; ``` ## Common feed recipes ### Filtering/scoping a feed A feed can be scoped by any of the parameters that are accepted on the [feed endpoint](/api-reference/users/feeds/list_items) via the `FeedClientOptions` set in the `defaultFeedOptions` for the `KnockFeedProvider` component, or via the `useNotifications` hook. You can read more in this [documentation on feed filtering](/in-app-ui/react/filtering-in-app-feeds). ## Components How to use Knock's UI components in your React Native application. --- title: "React Native SDK pre-built components" description: "How to use Knock's UI components in your React Native application." section: Building in-app UI --- The NotificationFeedContainer component is deprecated and has been replaced with NotificationFeed. The container component will be removed in a future release. } /> ## NotificationFeed ### Overview `NotificationFeed` is a React component that renders a list of notifications using data from Knock. It provides a customizable and interactive user interface for displaying notifications within your React Native application. ### Properties - **initialFilterStatus** (`FilterStatus`) - The initial filter applied to the notification feed. Defaults to 'All'. - **notificationRowStyle** (`NotificationFeedCellStyle`) - Customizes the style of the notification rows in the feed. - **headerConfig** (`NotificationFeedHeaderConfig`) - Configures the header of the notification feed. - **emptyFeedStyle** (`EmptyNotificationFeedStyle`) - Customizes the appearance of the empty state when there are no notifications. - **onCellActionButtonTap** (`(params: { button: ActionButton, item: FeedItem }) => void`) - Callback triggered when an action button in a notification row is tapped. - **onRowTap** (`(item: FeedItem) => void`) - Callback triggered when a notification row is tapped. ### Examples ```tsx import { KnockFeedProvider, KnockProvider, NotificationIconButton, } from "@knocklabs/react-native"; import React, { useCallback, useState } from "react"; import { StyleSheet, View, StatusBar } from "react-native"; // Your custom notification container component; see code example below import NotificationContainer from "./NotificationContainer"; const App: React.FC = () => { const [isNotificationFeedOpen, setIsNotificationFeedOpen] = useState(false); const onTopActionButtonTap = useCallback(() => { setIsNotificationFeedOpen(!isNotificationFeedOpen); }, [isNotificationFeedOpen]); return ( {!isNotificationFeedOpen && ( )} {isNotificationFeedOpen && ( setIsNotificationFeedOpen(!isNotificationFeedOpen) } /> )} ); }; export default App; import { NotificationFeed, FilterStatus } from "@knocklabs/react-native"; const MyNotificationFeed = () => { return ( { console.log("Action button tapped", button, item); }} onRowTap={(item) => { console.log("Row tapped", item); }} /> ); }; ```
```tsx import { ActionButton, FeedItem } from "@knocklabs/client"; import { NotificationFeed } from "@knocklabs/react-native"; import React, { useCallback } from "react"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; export interface NotificationContainerProps { handleClose: () => void; } const NotificationContainer: React.FC = ({ handleClose, }) => { const onCellActionButtonTap = useCallback( (params: { button: ActionButton; item: FeedItem }) => { // handle button tap }, [], ); const onRowTap = useCallback((item: FeedItem) => { // handle row tap }, []); return ( Notifications X ); }; export default NotificationContainer; ``` ## NotificationIconButton ### Overview `NotificationIconButton` is a React component that renders a button with a badge showing the count of unread or unseen notifications. This can be used to open the `NotificationFeed` when tapped. ### Properties - **onClick** (`() => void`) - Callback triggered when the button is pressed. - **badgeCountType** (`BadgeCountType`) - Specifies whether to display the count of 'unread', 'unseen', or 'all' notifications. - **styleOverride** (`ViewStyle`) - Customizes the overall style of the button. ### Example ```tsx import { NotificationIconButton } from "@knocklabs/react-native"; const MyApp = () => { return ( { console.log("Notification icon button clicked"); }} badgeCountType="unread" /> ); }; ``` ## Customization How to customize the UI of our pre-built components for React Native. --- title: "Customizing Knock UI components in React Native" description: "How to customize the UI of our pre-built components for React Native." section: Building in-app UI --- ## NotificationFeedCellStyle ### Overview `NotificationFeedCellStyle` allows for UI customization of the rows in the `NotificationFeed`. ### Properties - **unreadNotificationCircleColor** (`string`) - Color of the unread circle indicator in the top left of the row. - **showAvatarView** (`boolean`) - Determines whether to show the avatar/initials view in the upper left corner of the row. - **avatarViewStyle** (`AvatarViewStyle`) - Customizes the styling of the avatar view. - **primaryActionButtonStyle** (`ActionButtonStyle`) - Customizes the styling of primary action buttons. - **secondaryActionButtonStyle** (`ActionButtonStyle`) - Customizes the styling of secondary action buttons. - **tertiaryActionButtonStyle** (`ActionButtonStyle`) - Customizes the styling of tertiary action buttons. - **sentAtDateFormatter** (`Intl.DateTimeFormat`) - Formatter for the sent timestamp at the bottom of the row. - **sentAtDateTextStyle** (`TextStyle`) - Text style for the sent timestamp. - **htmlStyles** (`Record`) - Customizes the CSS styles of the HTML content in the notification body. ## EmptyNotificationFeedStyle ### Overview `EmptyNotificationFeedStyle` allows for UI customization of the empty state view when there are no notifications in the `NotificationFeed`. ### Properties - **titleString** (`string`) - The title text displayed when the feed is empty. - **subtitleString** (`string`) - The subtitle text displayed when the feed is empty. - **titleStyle** (`TextStyle`) - Customizes the style of the title text. - **subtitleStyle** (`TextStyle`) - Customizes the style of the subtitle text. - **iconStyle** (`ImageStyle`) - Customizes the style of the icon displayed in the empty state view. - **icon** (`string`) - The URL or local path to the icon image displayed in the empty state view. ## ActionButtonStyle ### Overview `ActionButtonStyle` allows for UI customization of the action buttons in the `NotificationFeed`. ### Properties - **buttonContainerStyle** (`ViewStyle`) - Customizes the container style of the action button. - **buttonTextStyle** (`TextStyle`) - Customizes the text style of the action button. - **buttonIconStyle** (`ImageStyle`) - Customizes the icon style of the action button. ## AvatarViewStyle ### Overview `AvatarViewStyle` allows for UI customization of the avatar view in the `NotificationFeed`. ### Properties - **container** (`ViewStyle`) - Customizes the overall container style of the avatar view. - **image** (`ImageStyle`) - Customizes the image style of the avatar view. - **text** (`TextStyle`) - Customizes the text style for initials when no avatar image is available. # React Native Headless UI ## Feed How to build custom feed UI using our React hooks and client library. --- title: "Build your own feed UI (headless)" description: How to build custom feed UI using our React hooks and client library. section: Building in-app UI > Feeds tags: ["hooks", "headless", "useNotifications", "useAuthenticatedKnockClient"] --- Using our `@knocklabs/react-native` and `@knocklabs/client` libraries, you can create fully custom notification UIs that are backed by the Knock Feed API and real-time service. This page shows how to create a completely custom notification UI in your application in a headless way using Knock's hooks. ## Getting started To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) To find the channel ID for your in-app channel(s), navigate to{" "} Channels and sources under the account settings section of your Knock dashboard, click on your in-app feed channel, and copy the channel ID. } /> ## Installing dependencies ```bash title="Installing dependencies" npm install @knocklabs/react-native ``` ## Implement `KnockProvider` First, we'll need to implement the `KnockProvider` component somewhere in your component tree and authenticate against the Knock API using a user id and API key. ```jsx title="Implement KnockProvider in your app" import { KnockProvider } from "@knocklabs/react-native"; const App = ({ user }) => ( ); ``` ## Setup the Knock client Next, we'll need to access the instance of the Knock client created by the `KnockProvider` using the `useKnockClient` hook. ```jsx title="Access the configured knockClient using useKnockClient" import { useKnockClient } from "@knocklabs/react-native"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); return null; }; ``` ## Setup the Knock feed instance Next, we'll want to set up an instance of a Knock Feed, which will handle the state management and provide a way for us to interact with the messages on the feed. ```jsx title="Create a feed store with Zustand" import { useKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react-native"; import { useEffect } from "react"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); const feedClient = useNotifications( knockClient, process.env.KNOCK_FEED_CHANNEL_ID, ); const { items, metadata } = useNotificationStore(feedClient); useEffect(() => { feedClient.fetch(); }, [feedClient]); return null; }; ``` ## Creating a custom notifications UI The last step is to render our notifications UI using the data that's exposed via the state store (`items` and `metadata`). ```jsx title="Render items and metadata in the feed" import { useKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react-native"; import { useEffect } from "react"; const NotificationFeed = ({ user }) => { const knockClient = useKnockClient(); const feedClient = useNotifications( knockClient, process.env.KNOCK_FEED_CHANNEL_ID, ); const { items, metadata } = useNotificationStore(feedClient); useEffect(() => { feedClient.fetch(); }, [feedClient]); return ( Total unread: {metadata.unread_count} {items.map(item => ( {/* Notification cell goes here */} )} ); }; ``` ## Common feed recipes ### Filtering/scoping a feed A feed can be scoped by any of the parameters that are accepted on the [feed endpoint](/api-reference/users/feeds/list_items) via the `FeedClientOptions` set in the `defaultFeedOptions` for the `KnockFeedProvider` component, or via the `useNotifications` hook. You can read more in this [documentation on feed filtering](/in-app-ui/react/filtering-in-app-feeds). # React Native SDK ## Overview Learn more about integrating Knock into your React Native applications through our React Native SDKs. --- title: "Knock React Native SDK" description: Learn more about integrating Knock into your React Native applications through our React Native SDKs. section: SDKs tags: ["expo", "rn", "react native"] --- Our [`@knocklabs/react-native`](https://www.npmjs.com/package/@knocklabs/react-native) library lets you create in-app notification experiences in React Native powered applications using Knock's client APIs. See our Expo SDK. Our React Native SDK is meant for use with React Native apps that are not built with a framework such as Expo. } /> The React Native library is built on top of the `@knocklabs/client` JS SDK and includes that library as an implicit dependency. Internally, our React Native SDK is tested with React Native v0.73.4, but it should work with all recent versions of React Native. If you encounter issues with React Native compatibility, please [reach out to our support team](mailto:support@knock.app). **Quick links:** - [`@knocklabs/react-native` on npm](https://www.npmjs.com/package/@knocklabs/react-native) - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [React Native SDK reference](/in-app-ui/react-native/sdk/reference) - [Javascript SDK reference](/in-app-ui/javascript/sdk/reference) Using the React Native SDK it's possible to build: - [Notification feeds](/in-app-ui/react-native/notification-feeds) that update in real time - Notification preference control centers - Push notification management ## Example app You can find a basic example application that uses the React Native SDK here. The app shows patterns for handling push token registration, building an in-app feed, and managing user notification preferences. ## Need help? Our React Native SDK is worked on full-time by the Knock JavaScript team. ### Join the community Ask questions and find answers on the following platforms: - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/javascript/issues/new) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Quick start Get started with the Knock React Native SDK to build in-app notification experiences. --- title: "Getting started with the Knock React Native SDK" description: Get started with the Knock React Native SDK to build in-app notification experiences. section: SDKs --- To get started, you will need the following: - [A Knock account](https://dashboard.knock.app/signup) - A public API key for the Knock environment (which you'll use in the `publishableKey`) - An in-app feed channel with a workflow that produces in-app feed messages (optional) ## Installation - Via NPM: `npm install @knocklabs/react-native` - Via Yarn: `yarn add @knocklabs/react-native` Install @knocklabs/expo instead to use our{" "} Expo SDK. } /> ### Configuration To configure the feed you will need: 1. A public API key (found in the Knock dashboard) 2. A user ID and an auth token {" "} Auth tokens are strongly recommended for production environments and are required when enhanced security mode is enabled. For more information, see our{" "} Security & Authentication documentation . } /> 3. If integrating an in-app feed, a feed channel ID (found in the Knock dashboard) ### Usage You can integrate the feed into your app as follows: ```typescript import { KnockProvider, KnockFeedProvider, NotificationFeed, } from "@knocklabs/react-native"; const YourAppLayout = () => { return ( {/* Optionally, use the KnockFeedProvider to connect an in-app feed */} { console.log("Notification tapped:", item); }} /> ); }; ``` ### Headless usage Alternatively, if you don't want to use our components you can render the feed in a headless mode using our hooks: ```typescript import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react-native"; const YourAppLayout = () => { const knockClient = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: currentUser.id }, ); const notificationFeed = useNotifications( knockClient, process.env.KNOCK_FEED_ID, ); const { metadata } = useNotificationStore(notificationFeed); useEffect(() => { notificationFeed.fetch(); }, [notificationFeed]); return Total unread: {metadata.unread_count}; }; ``` ## Push notifications Documentation on integrating FCM push notifications with the Knock SDK in your React Native application. --- title: "Handling Push Notifications with React Native and Firebase Cloud Messaging" description: Documentation on integrating FCM push notifications with the Knock SDK in your React Native application. section: SDKs --- See our{" "} documentation about handling push notifications using our Expo SDK . } /> If you haven't already, create a new Firebase Cloud Messaging channel by navigating to **Channels and sources** under the account settings section of your Knock dashboard. Follow our [Firebase Cloud Messaging push notification guide](/integrations/push/firebase) to configure FCM with Knock using your Service Account JSON file. The simplest way to get started with FCM in your React Native app is to use React Native Firebase, which simplifies the process of handling push notifications and device tokens. Follow their Getting Started guide to set up React Native Firebase in your project. Ensure your app is wrapped with both `KnockProvider` and `KnockPushNotificationProvider`. ```tsx import React from "react"; import { View } from "react-native"; import { KnockProvider, KnockPushNotificationProvider, } from "@knocklabs/react-native"; export default function App() { return ( {/* Your app content here */} ); } ``` Depending upon the platforms supported by your app, you may need to request permissions to receive push notifications on the user's device. See React Native Firebase's documentation on how to request permissions on Android and how to request permissions on iOS. Alternatively, consider using a third-party library such as react-native-permissions. When your app launches, retrieve the device token using React Native Firebase's `getToken` function. Pass this token to the `registerPushTokenToChannel` function provided by Knock's [`usePushNotifications` hook](/in-app-ui/react-native/sdk/reference#usepushnotifications) and include the channel ID of your FCM channel. Additionally, when your app is in the foreground, register a listener using `onTokenRefresh` and invoke `registerPushTokenToChannel` whenever the token is refreshed. ```tsx import { Text } from "react-native"; import { usePushNotifications } from "@knocklabs/react-native"; import messaging from "@react-native-firebase/messaging"; const MyComponent = () => { const { registerPushTokenToChannel } = usePushNotifications(); useEffect(() => { messaging() .getToken() .then((token) => registerPushTokenToChannel(token, "{YOUR_KNOCK_FCM_CHANNEL_ID}"), ) .catch(console.error); const unsubscribe = messaging().onTokenRefresh((token) => registerPushTokenToChannel(token, "{YOUR_KNOCK_FCM_CHANNEL_ID}").catch( console.error, ), ); return unsubscribe; }, [registerPushTokenToChannel]); return Hello, world!; }; ``` Listen for push notifications using React Native Firebase's `getInitialNotification` and `onNotificationOpenedApp` functions. You will need to manually update message engagement status when users interact with notifications. See React Native Firebase's Notifications guide for more details on these two functions. ```tsx useEffect(() => { messaging() .getInitialNotification() .then((remoteMessage) => { if (remoteMessage) { console.log("App opened via notification from quit state"); } }) .catch(console.error); const unsubscribe = messaging().onNotificationOpenedApp(() => { console.log("App opened via notification while in the background"); }); return unsubscribe; }, []); ``` Use the Knock dashboard or API to send a test notification to ensure your setup is correct. Verify that the notification appears on your device and that tapping on it triggers the expected behavior. ## Troubleshooting Check out our{" "} Expo example app {" "} to see a fully working example of how to integrate push notifications with Knock. } /> - **Not Receiving Notifications:** Ensure your FCM device token is correctly registered with Knock and that your device's notification settings allow push notifications from your app. - **Handling Silent Notifications:** If implementing silent notifications, ensure that your notification payload is correctly configured to not display an alert or sound. For further assistance, [reach out to our support team](mailto:support@knock.app). ## Reference Complete API reference for the Knock React Native SDK. --- title: "React Native API reference" description: Complete API reference for the Knock React Native SDK. tags: ["mark as read"] section: SDKs --- In this section, you'll find the complete documentation for the components exposed in `@knocklabs/react-native`, including the props available. **Note**: You can see a reference for the methods available for the `Knock` class, as well as a `Feed` instance under the [client JS docs](/in-app-ui/javascript/sdk/reference). ## Components ### `KnockProvider` The top-level provider that connects to Knock with the given API key and authenticates a user. #### Props Accepts `KnockProviderProps` - **apiKey*** (`string`) - The public API key for the environment. - **user** (`UserIdentificationOptions`) - User identification data. - **userToken** (`string`) - A JWT that identifies the authenticated user, signed with the private key provided in the Knock dashboard. Required to secure your production environment. [Learn more.](https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security) - **enabled** (`boolean`) - Defaults to `true`. When `false`, children still render but the Knock client stays idle: no identify call, no API requests, and no websocket. Flipping it to `true` authenticates and connects the client; flipping it back to `false` disconnects it and clears its data. Use it to defer activity until you have a complete identity (see below). - **host** (`string`) - A custom API host for Knock. - **i18n** (`I18nContent`) - An optional set of translations to override the default `en` translations used in the feed components. #### Deferring activity with `enabled` `KnockProvider` takes an `enabled` prop that defaults to `true`. When it's `false`, the provider still renders its children, but the Knock client sits idle: it doesn't identify the user, make any API requests, or open a websocket. Set it back to `true` and the client authenticates and connects; set it to `false` again and it disconnects and clears its data. This is the recommended way to gate the provider on a complete identity — for example, an enhanced-security user token that isn't ready on the first render — rather than mounting and unmounting `KnockProvider` as that identity changes: ```jsx {/* ... */} ``` When `enabled` becomes `true`, feed components remount and reload their data, and Slack and Microsoft Teams connection status re-checks for the authenticated user. To react to these transitions in your own components, use the [`useKnockAuthState`](#useknockauthstate) hook. ### `KnockFeedProvider` The feed-specific provider that connects to a feed for that user. Must be a child of the `KnockProvider`. #### Props Accepts `KnockFeedProviderProps`: - **feedId*** (`string`) - The channel ID of the in-app feed to be displayed. - **defaultFeedOptions** (`FeedClientOptions`) - Set defaults for `tenant`, `has_tenant`, `source`, `archived` to scope all subsequent feed queries. - **colorMode** (`ColorMode`) - Sets the theme as either light or dark mode (defaults to light). ### `KnockPushNotificationProvider` A context provider designed to streamline the integration of push notifications within your React Native application. It facilitates the registration of device push tokens with the Knock backend, enabling the delivery of notifications. It is recommended to use the [`usePushNotifications`](#usepushnotifications) hook to interact with this context provider. **Note:** Must be a child of the `KnockProvider`. #### Props None other than `children`. ## Hooks ### `useKnock` The `KnockProvider` exposes a `useKnock` hook for all child components. **Returns**: `Knock`, an instance of the Knock JS client. **Example**: ```jsx import { KnockProvider, useKnock } from "@knocklabs/react"; const App = ({ authenticatedUser }) => ( ); const MyComponent = () => { const knock = useKnock(); return null; }; ``` ### `useKnockFeed` The `KnockFeedProvider` exposes a `useKnockFeed` hook for all child components. **Returns**: `KnockFeedProviderState` - **knock** (`Knock`) - The instance of the Knock client. - **feedClient** (`Feed`) - The instance of the authenticated Feed. - **useFeedStore** (`UseStore`) - A zustand store containing the FeedStoreState. - **status** (`FilterStatus`) - Current value of the filter status for the Feed. - **setStatus** (`function`) - A function to set the current FilterStatus. - **colorMode** (`ColorMode`) - The current theme color. **Example**: ```jsx import { KnockProvider, KnockFeedProvider, useKnockFeed, } from "@knocklabs/react-native"; const App = ({ authenticatedUser }) => ( ); const MyFeedComponent = () => { const { useFeedStore } = useKnockFeed(); const items = useFeedStore((state) => state.items); return ( {items.map((item) => ( ))} ); }; ``` ### `useAuthenticatedKnockClient` Creates an authenticated Knock client. **Returns**: `Knock` instance, authenticated against the user **Example**: ```jsx import { useAuthenticatedKnockClient } from "@knocklabs/react-native"; const MyComponent = () => { const knock = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: user.id }, user.knockToken, ); return null; }; ``` ### `useKnockAuthState` Subscribes to a `Knock` client's authentication state, re-rendering when the authenticated user changes. Use it to react to the [`enabled` prop on `KnockProvider`](#knockprovider) flipping between states. **Returns**: `KnockAuthState` - **status** (`'authenticated' | 'unauthenticated'`) - Whether a user is authenticated to the client. - **userId** (`string | undefined | null`) - The ID of the authenticated user, or `undefined` when no user is authenticated. - **userToken** (`string | undefined`) - The user token in use for the authenticated user, when one was provided. **Example**: ```jsx import { useKnock, useKnockAuthState } from "@knocklabs/react-native"; const MyComponent = () => { const knock = useKnock(); const { status, userId } = useKnockAuthState(knock); return null; }; ``` ### `useNotifications` Creates a `Feed` instance for the provided `Knock` client which creates a stateful, real-time connection to Knock to build in-app experiences. **Returns**: `Feed` instance **Example**: ```js import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/react-native"; const MyComponent = () => { const knock = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: user.id }, user.knockToken, ); const notificationFeed = useNotifications( knock, process.env.KNOCK_FEED_CHANNEL_ID, ); const { metadata } = useNotificationStore(notificationFeed); useEffect(() => { notificationFeed.fetch(); }, [notificationFeed]); return ( Total unread: {metadata.unread_count} ); }; ``` ### `useTranslations` Exposed under `KnockI18nProvider` child components. **Returns**: - **locale** (`string`) - The current locale code (defaults to `en`). - **t** (`(key: string) => string`) - A helper function to get the value of a translation from the current `Translations`. ### `usePushNotifications` The `KnockPushNotificationProvider` exposes a `usePushNotifications` hook for all child components, enabling them to register and unregister a device's push token from a channel. **Returns**: `KnockPushNotificationContextType` - **registerPushTokenToChannel** (`(token: string, channelId: string) => Promise`) - Registers the device's push token with a specific channel in the Knock backend. - **unregisterPushTokenFromChannel** (`(token: string, channelId: string) => Promise`) - Removes the device's push token from a specific channel in the Knock backend. **Example**: ```jsx import React, { useEffect } from "react"; import { View, Text } from "react-native"; import { KnockPushNotificationProvider, usePushNotifications, } from "@knocklabs/react-native"; const App = () => ( ); const MyComponent = () => { // How the push token is retrieved depends upon the push notification service you are using const pushToken = getPushTokenForCurrentDevice(); const { registerPushTokenToChannel } = usePushNotifications(); useEffect(() => { registerPushTokenToChannel(pushToken, process.env.KNOCK_PUSH_CHANNEL_ID) .then(() => console.log("Push token registered")) .catch(console.error); }, [registerPushTokenToChannel, pushToken]); return ( Push Token: {pushToken} ); }; ``` ## Types ### UserIdentificationOptions User identification data to pass through to the `authenticate` method. - **id** (`string*`) - The `id` for the user. - **[attribute_name]** (`any`) - Attribute to attach to the user on identification. ### `I18nContent` Used to set translations available in the child components exposed under `KnockFeedProvider` and `KnockSlackProvider`. Used in the `useTranslations` hook. **Note:** `locale` must be a valid locale code. ```typescript interface Translations { readonly emptyFeedTitle: string; readonly emptyFeedBody: string; readonly notifications: string; readonly poweredBy: string; readonly markAllAsRead: string; readonly archiveNotification: string; readonly all: string; readonly unread: string; readonly read: string; readonly unseen: string; readonly slackConnectChannel: string; readonly slackChannelId: string; readonly slackConnecting: string; readonly slackDisconnecting: string; readonly slackConnect: string; readonly slackConnected: string; readonly slackConnectContainerDescription: string; readonly slackSearchbarDisconnected: string; readonly slackSearchbarNoChannelsConnected: string; readonly slackSearchbarNoChannelsFound: string; readonly slackSearchbarChannelsError: string; readonly slackSearchChannels: string; readonly slackConnectionErrorOccurred: string; readonly slackConnectionErrorExists: string; readonly slackChannelAlreadyConnected: string; readonly slackError: string; readonly slackDisconnect: string; readonly slackChannelSetError: string; readonly slackAccessTokenNotSet: string; readonly slackReconnect: string; } interface I18nContent { readonly translations: Partial; readonly locale: string; } ``` # Swift UI components ## Overview Learn more about the in-app notifications experiences you can build for iOS and macOS applications with Knock. --- title: "Building in-app UI for iOS and macOS" description: Learn more about the in-app notifications experiences you can build for iOS and macOS applications with Knock. section: Building in-app UI --- Our Swift SDK library lets you create notification experiences using Knock's APIs. It comes with pre-built UI components that you can use to easily get up and running with a fully functional notification feed experience in your product. ## Getting started Please reference our iOS SDK [documentation](/in-app-ui/ios/sdk/quick-start) to set up the library. ## Pre-built components The Knock iOS SDK ships the following pre-built UI elements: ### In-app notifications - [`InAppFeedView`](/in-app-ui/ios/components#knockinappfeedview): A full-page list of notifications. - [`InAppFeedViewController`](/in-app-ui/ios/components#knockinappfeedview): A wrapper for InAppFeedView to be used with UIKit. - [`InAppFeedViewModel`](/in-app-ui/ios/components#knockinappfeedviewmodel): All of the logic needed to support your in-app feed. Can be used independently from `InAppFeedView` if you want to build your own UI. - [`InAppFeedNotificationIconButton`](/in-app-ui/ios/components#inappfeednotificationiconbutton): For adding a bell icon to your application that shows the current count of unread or unseen notifications. ## Links - [iOS SDK on GitHub](https://github.com/knocklabs/knock-swift) - [SDK reference](/in-app-ui/ios/sdk/reference) ## Components How to use Knock's UI components in your iOS application. --- title: "Swift SDK pre-built components" description: "How to use Knock's UI components in your iOS application." section: Building in-app UI --- ## KnockInAppFeedView ### Overview `KnockInAppFeedView` is a SwiftUI view that renders the in-app notifications feed using data from `KnockInAppFeedViewModel`. It provides a customizable and interactive user interface for displaying notifications. Please remember that you will still need to initialize the{" "} feedManager manually before displaying this component. Check out our{" "} FeedManager documentation for more information. {" "} See below for an example of how to initialize the feedManager{" "} . } /> ### Properties - **theme** (`InAppFeedTheme`) - Defines the appearance of the feed view and its components. ### Customization You can customize almost every aspect of the UI of the `KnockInAppFeedView` using our customizable themes. ### Using UIKit If you are using UIKit, use `InAppFeedViewController`. See below for an example of how to use this component. ### Examples #### SwiftUI ```swift @EnvironmentObject var viewModel: Knock.InAppFeedViewModel = .init() init() { Task { if Knock.shared.feedManager == nil { Knock.shared.feedManager = try? await Knock.FeedManager(feedId: "ad06b085-54e2-4fb0-b6e7-050338851868") await viewModel.connectFeedAndObserveNewMessages() } } } KnockInAppFeedView(theme: KnockInAppFeedTheme(titleString: "Notifications")) .environmentObject(viewModel) .onReceive(viewModel.didTapFeedItemButtonPublisher) { actionString in print("Button with action \(actionString) was tapped.") } .onReceive(viewModel.didTapFeedItemRowPublisher) { item in print("Row item was tapped") } ``` #### UIKit ```swift class MyViewController: InAppFeedViewController { init() { self.viewModel = Knock.InAppFeedViewModel() self.theme: Knock.InAppFeedTheme = .init() } override func viewDidLoad() { Task { if Knock.shared.feedManager == nil { Knock.shared.feedManager = try? await Knock.FeedManager(feedId: "ad06b085-54e2-4fb0-b6e7-050338851868") await viewModel.connectFeedAndObserveNewMessages() } } super.viewDidLoad() } } ``` ## InAppFeedNotificationIconButton ### Overview `InAppFeedNotificationIconButton` is a SwiftUI view that renders a bell icon button to your application that shows the current count of unread or unseen notifications. This can be used to open your NotificationFeed. ### Properties - **theme** (`InAppFeedNotificationIconButtonTheme`) - Defines the appearance of the feed view and its components. - **action** (`() -> Void`) - A callback to alert you when user taps on the button. ### Examples ```swift TestParentView() .sheet(isPresented: $showingSheet) { Knock.InAppFeedView() .environmentObject(feedViewModel) } .toolbar { Knock.InAppFeedNotificationIconButton() { showingSheet.toggle() } .environmentObject(feedViewModel) } ``` ## Customization How to customize Knock's iOS UI components. --- title: "Customizing Knock UI components in iOS" description: "How to customize Knock's iOS UI components." section: Building in-app UI --- ## InAppFeedTheme ### Overview `InAppFeedTheme` allows for UI customization of the `KnockInAppFeedView`. ### Properties - **rowTheme** (`FeedNotificationRowTheme`) - Defines the UI customization of the row items. - **titleString** (`String?`) - Sets the title of the view. If set to nil, then the title view will be hidden entirely. This is useful if you want to have a completely custom title view. - **titleFont** (`Font?`) - Sets the font of the title view. - **titleColor** (`Color?`) - Sets the color of the title view. - **upperBackgroundColor** (`Color?`) - Sets the background color of the top portion of the view (title view, filter view, and top action buttons view). - **lowerBackgroundColor** (`Color?`) - Sets the background color of the bottom portion of the view (the list). ## FeedNotificationRowTheme ### Overview `FeedNotificationRowTheme` allows for UI customization of the row items in the `KnockInAppFeedView`. ### Properties - **showAvatarView** (`Bool`) - Show or hide the avatar/initials view in the upper left corner of the row. - **avatarViewTheme** (`AvatarViewTheme`) - Customize styling of avatarview. - **notificationContentCSS** (`String?`) - Customize the css of the markdown html of the notification body. - **backgroundColor** (`Color`) - Background color of the FeedNotificationRow. - **markAsReadSwipeConfig** (`SwipeButtonConfig?`) - This is the config to set the mark as read/unread swipe actions. Set to null to remove the action entirely. - **archiveSwipeConfig** (`SwipeButtonConfig?`) - This is the config to set the archive/unarchive swipe actions. Set to null to remove the action entirely. - **unreadNotificationCircleColor** (`Color`) - Color of the unread circle indicator in the top left of the row. - **sentAtDateFormatter** (`DateFormatter`) - DateFormatter for the sent timestamp at the bottom of the row. - **sentAtDateFont** (`Font`) - Font for sent timestamp. - **sentAtDateTextColor** (`Color`) - Color for sent timestamp. - **primaryActionButtonConfig** (`ActionButtonConfig`) - Styling for primary action buttons. - **secondaryActionButtonConfig** (`ActionButtonConfig`) - Styling for secondary action buttons. - **tertiaryActionButtonConfig** (`ActionButtonConfig`) - Styling for tertiary action buttons. ## AvatarViewTheme ### Overview `AvatarViewTheme` allows for UI customization of the user avatar view in the row item. ### Properties - **avatarViewBackgroundColor** (`Color`) - Background color of the view. This is more apparent when the view is showing initials instead of an image. - **avatarViewInitialsFont** (`Font`) - Font for the initials view. - **avatarViewInitialsColor** (`Color`) - Text color for the initials view. - **avatarViewSize** (`CGFloat`) - Overall size of the avatar view. # Swift SDK ## Overview Learn more about integrating Knock into your iOS and macOS applications through our Swift SDK. --- title: "Knock Swift SDK (iOS and macOS)" description: Learn more about integrating Knock into your iOS and macOS applications through our Swift SDK. section: SDKs --- The Knock Swift SDK is a client-side SDK for interacting with the Knock API and for building in-app notification experiences across iOS, macOS, and watchOS. **Quick links** - [SDK on GitHub](https://github.com/knocklabs/knock-swift) - [Full reference documentation](/in-app-ui/ios/sdk/reference) ## Example app You can find a complete iOS example application that uses the Swift SDK here. The app shows patterns for handling push token registration, building an in-app feed using SwiftUI, and managing user notification preferences. ## Need help? Our Swift SDK is worked on full-time by the Knock Mobile team. ### Join the community - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/knock-swift/issues/new) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Quick start Get started with the Knock Swift SDK to build in-app notification experiences. --- title: "Getting started with the Swift SDK" description: Get started with the Knock Swift SDK to build in-app notification experiences. section: SDKs --- To get started, you will need the following: - [A Knock Account](https://dashboard.knock.app/signup) - A public API key for the Knock environment (which you'll use in the `publishableKey`) - An in-app feed channel with a workflow that produces in-app feed messages (optional) - An APNs channel with a workflow that produces push notifications (optional) ## Installation You can install the Swift SDK in a few different ways: - Swift Package Manager (SPM) - Carthage - Cocoapods See here for more information on installation. ### Initializing a Knock instance To initialize the shared Knock instance, you are required to use your public API key, which is identified by the prefix `pk_`. Additionally, if you opt to utilize our `KnockAppDelegate` for comprehensive device token registration and management, you must also include your `pushChannelId` during the setup process of your instance. You should do this setup as soon as you can. Preferably within your `AppDelegate`. ```swift import Knock try? Knock.shared.setup(publishableKey: "your-public-key", pushChannelId: "apns-channel-id") ``` ### Authenticating a user Once you've configured the shared Knock instance with your public API key, the next step is to sign the user into Knock with their `userId`. We recommend you initiate the user sign-in process at the earliest point where the **`userId`** is known to you. This ensures that your application is ready to leverage Knock's features with the context of the signed-in user. For interactions with your production Knock environment, you should enable [**enhanced security mode**](tps://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled) and provide a **`userToken`** to the `signIn` method. The **`userToken`** is a server-signed JWT that identifies the user making the request. This token is used to verify the authenticity of the user and is required when **enhanced security mode** is enabled. ```swift import Knock await Knock.shared.signIn(userId: "userid", userToken: nil) ``` ## Push notifications Usage guides to help you get started with the Push Notifications in the iOS Knock SDK. --- title: "Handling iOS push notifications" description: "Usage guides to help you get started with the Push Notifications in the iOS Knock SDK." section: SDKs --- **Note:** We recommend taking advantage of our [KnockAppDelegate](/in-app-ui/ios/sdk/reference#knockappdelegate) to make managing your push notifications simpler. ## Prerequisites Before proceeding, ensure you've configured push notifications within your Knock account. For guidance on this initial setup, refer to our [Push Notification Configuration Guide](/integrations/push/overview). ## Step 1: Enabling push notifications in your app 1. **Configure APNs in your app.** - Open your project in Xcode. - Navigate to your app target's **Signing & Capabilities** tab. - Click the "+" capability button and add **Push Notifications** to enable Apple Push Notification service (APNs). 2. **Enable background modes.** - Still in the **Signing & Capabilities** tab, add the **Background Modes** capability. - Check **Remote notifications** to enable your app to receive silent push notifications. ## Step 2: Registering for push notifications Implement the following in your AppDelegate or SceneDelegate to register for push notifications: **KnockAppDelegate:** If using the `KnockAppDelegate`, this will be handled for you automatically. **Manually:** ```swift class MyAppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { UNUserNotificationCenter.current().delegate = self // Will request push notification permissions, and will automatically register if perms are granted. Knock.shared.requestAndRegisterForPushNotifications() // Check if launched from the tap of a notification if let launchOptions = launchOptions, let userInfo = launchOptions[.remoteNotification] as? [String: AnyObject] { pushNotificationTapped(userInfo: userInfo) } return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // Register the token with Knock Task { let channelId = await Knock.shared.getPushChannelId() do { let _ = try await Knock.shared.registerTokenForAPNS(channelId: channelId, token: Knock.convertTokenToString(token: deviceToken)) } catch { // Handle error } } } } ``` ## Step 3: Updating the message status of a push notification If a push notification is sent via Knock, it will contain a `knock_message_id` property that includes the corresponding message Id. This can then be used to update the message status. **KnockAppDelegate:** If using the `KnockAppDelegate`, this will be handled for you automatically. When a user taps on a push notification, `KnockAppDelegate` automatically sets the message engagement status to "read" and "interacted". **Manually:** ```swift class AppDelegate: UIResponder, UIApplicationDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { if let messageId = getMessageId(userInfo: notification.request.content.userInfo) { Knock.shared.updateMessageStatus(messageId: messageId, status: .seen) { _ in } } completionHandler(presentationOptions) } } ``` ## Step 4: Configuring silent push notifications Silent push notifications allow your app to update content in the background without alerting the user. Ensure that your Knock APNs message template has silent notifications enabled. **KnockAppDelegate:** ```swift class MyAppDelegate: KnockAppDelegate { override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Pull any information you need out of userInfo, and change the completionHandler value depending on your needs. completionHandler(.noData) } } ``` **Manually:** ```swift class MyAppDelegate: UIResponder, UIApplicationDelegate { open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Pull any information you need out of userInfo, and change the completionHandler value depending on your needs. completionHandler(.noData) } } ``` ## Full example Here's the complete `AppDelegate` implementation: ```swift class AppDelegate: KnockAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { Task { try? await Knock.shared.setup(publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose)) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } override func pushNotificationDeliveredInForeground(notification: UNNotification) -> UNNotificationPresentationOptions { let options = super.pushNotificationDeliveredInForeground(notification: notification) // Handle push notification here return [options] } override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Handle silent push notification here completionHandler(.noData) } } ``` ## Using FCM in iOS If you prefer to use Firebase Cloud Messaging (FCM) within your iOS app, you can follow the same steps outlined above for push notifications with a few modifications to your `AppDelegate`. ### Step 1: Configure Firebase In your `application(_:didFinishLaunchingWithOptions:)` method, add the following: 1. **Initialize Firebase**: ```swift FirebaseApp.configure() ``` 2. **Set the Messaging Delegate**: ```swift Messaging.messaging().delegate = self ``` 3. **Setup Knock**: ```swift Task { try? await Knock.shared.setup( publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose) ) } ``` ### Step 2: Override push notification registration methods Override the `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)` method to prevent the `KnockAppDelegate` from handling this automatically: ```swift override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {} ``` ### Step 3: Implement `MessagingDelegate` Extend your `AppDelegate` to conform to `MessagingDelegate` and implement the `messaging(_:didReceiveRegistrationToken:)` method: ```swift extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { Task { if let channelId = await Knock.shared.getPushChannelId(), let token = fcmToken { let _ = try? await Knock.shared.registerTokenForAPNS(channelId: channelId, token: token) } } } } ``` ### Full example Here's the complete `AppDelegate` implementation with FCM support: ```swift class AppDelegate: KnockAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Step 1: Configure Firebase and Knock FirebaseApp.configure() Messaging.messaging().delegate = self Task { try? await Knock.shared.setup( publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose) ) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // Step 2: Override registration for remote notifications override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {} override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } override func pushNotificationDeliveredInForeground(notification: UNNotification) -> UNNotificationPresentationOptions { let options = super.pushNotificationDeliveredInForeground(notification: notification) return [options] } override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { completionHandler(.noData) } } // Step 3: Implement MessagingDelegate extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { Task { if let channelId = await Knock.shared.getPushChannelId(), let token = fcmToken { let _ = try? await Knock.shared.registerTokenForAPNS(channelId: channelId, token: token) } } } } ``` ## Deep links Follow this documentation to get started with deep/universal linking in the Knock iOS SDK. --- title: Handling deep/universal links description: Follow this documentation to get started with deep/universal linking in the Knock iOS SDK. section: SDKs --- We recommend taking advantage of our{" "} KnockAppDelegate {" "} to simplify deep link handling. } /> ## Deep Links Follow the steps below to configure URLs that deeply link to specific content in your iOS application. - In Xcode, navigate to your app target's **Info** tab. - Add a new URL type under **URL Types** with a unique scheme. - In your trigger `data` payload that you send to Knock, include a property with a value for your deep link. The name of the property doesn't matter, so long as you know beforehand what it will be called. - You can configure the format of the API request sent to APNs in your workflow step's [payload overrides](/integrations/push/apns#using-overrides-to-customize-notifications). You can handle incoming URLs from a push notification by implementing the `pushNotificationTapped` method in your `KnockAppDelegate`. This can also be done manually. ```swift title="Handle with KnockAppDelegate" class MyAppDelegate: KnockAppDelegate { override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } } ```
```swift title="Handle manually" class MyAppDelegate: UIResponder, UIApplicationDelegate { open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Check if launched from the tap of a notification if let launchOptions = launchOptions, let userInfo = launchOptions[.remoteNotification] as? [String: AnyObject] { // retrieve url if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } return true } } ```
```swift title="Handle with KnockAppDelegate" class MyAppDelegate: KnockAppDelegate { override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } } ```
```swift title="Handle manually" class MyAppDelegate: UIResponder, UIApplicationDelegate { open func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } completionHandler() } } ```
## Universal Links Universal links will redirect users to a browser when the app is not installed. - Add the **Associated Domains** capability in your app target's **Signing & Capabilities** tab. - Add your domain in the format **`applinks:yourdomain.com`**. Implement `application(_:continue:restorationHandler:)` in your `AppDelegate` or `SceneDelegate`. ```swift title="Handle incoming universal links" func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb { if let incomingURL = userActivity.webpageURL { // Handle the incoming URL appropriately } } return true } ``` - Ensure your server hosts an Apple App Site Association (AASA) file at **`https://yourdomain.com/.well-known/apple-app-site-association`**. For more detailed instructions on configuring universal links, visit Apple's Official Documentation. ## Reference The complete API reference for the Knock iOS SDK. --- title: "iOS SDK API reference" description: The complete API reference for the Knock iOS SDK. section: SDKs --- In this section, you'll find the documentation for the classes and methods available in the [iOS SDK](https://github.com/knocklabs/knock-swift). ## Knock The top-level Knock class. This is a shared instance to interact with the SDK's API methods. ### `Knock.shared.setup()` Sets up the shared Knock instance. Make sure to call this as soon as you can. Preferably in your AppDelegate. **Params** - **publishableKey** (`string*`) - The public API key for the Knock environment. - **pushChannelId** (`string (optional)`) - The Knock APNs channel id that you plan to use within your app. - **options** (`Knock.KnockStartupOptions (optional)`) - Optional startup options to configure your Knock instance. ## KnockAppDelegate This class serves as an optional base class designed to streamline the integration of Knock into your application. By inheriting from KnockAppDelegate in your AppDelegate, you gain automatic handling of Push Notification registration and device token management, simplifying the initial setup process for Knock's functionalities. The class also provides a set of open helper functions that are intended to facilitate the handling of different Push Notification events such as delivery in the foreground, taps, and dismissals. These helper methods offer a straightforward approach to customizing your app's response to notifications, ensuring that you can tailor the behavior to fit your specific needs. Override any of the provided methods to achieve further customization, allowing you to control how your application processes and reacts to Push Notifications. Additionally, by leveraging this class, you ensure that your app adheres to best practices for managing device tokens and interacting with the notification system on iOS, enhancing the overall reliability and user experience of your app's notification features. Key Features: - Automatic registration for remote notifications, ensuring your app is promptly set up to receive and handle Push Notifications. - Simplified device token management, with automatic storage of the device token, facilitating easier access and use in Push Notification payloads. - Customizable notification handling through open helper functions, allowing for bespoke responses to notification events such as foreground delivery, user taps, and dismissal actions. - Automatic message status updates, based on Push Notification interaction. ```swift import UIKit import Knock class AppDelegate: KnockAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { Task { try? await Knock.shared.setup(publishableKey: "your-publishableKey", pushChannelId: "your-apns-channel-id") } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } override func pushNotificationDeliveredInForeground(notification: UNNotification) -> UNNotificationPresentationOptions { let options = super.pushNotificationDeliveredInForeground(notification: notification) return [options] } override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { completionHandler(.noData) } } ``` ## KnockInAppFeedViewModel `KnockInAppFeedViewModel` manages the state and behavior of the in-app feed. It handles data fetching, user actions, state updates, and feed configuration. This class is used with our [`KnockInAppFeedView`](/in-app-ui/ios/components#knockinappfeedview) component, but it can also be used independently if you would like to build your own UI. ### Properties - **feed** (`Knock.Feed`) - Holds the current feed data. - **currentTenantId** (`String?`) - Identifies the tenant associated with the current feed. - **currentFilter** (`InAppFeedFilter`) - The currently active filter for displaying feed items. - **filterOptions** (`[InAppFeedFilter]`) - Available filter options for customizing feed display. - **topButtonActions** (`[Knock.FeedTopActionButtonType]?`) - Actions available at the top of the feed interface, such as archiving or marking all as read. If set to nil, this view will be hidden. - **feedClientOptions** (`Knock.FeedClientOptions`) - Configuration options for feed. - **didTapFeedItemButtonPublisher** (`PassthroughSubject()`) - Publisher for feed item button tap events. - **didTapFeedItemRowPublisher** (`PassthroughSubject()`) - Publisher for feed item row tap events. ### Methods - **connectFeedAndObserveNewMessages** - Connects to the feed and subscribes to new message events. - **refreshFeed(showLoadingIndicator: Bool)** - Refreshes the feed using the original filter options. - **fetchNewPageOfFeedItems** - Loads additional feed items when the end of the list is reached. - **isMoreContentAvailable** - Determines if there are more pages of feed content that need to be fetched. - **archiveItem(_ item: Knock.FeedItem)** - Archives a specific feed item. - **archiveAll(scope: Knock.FeedItemScope)** - Archives all items within the specified scope. - **markAllAsRead** - Marks all items in the feed as read. - **markAllAsSeen** - Marks all unseen items as seen. - **markAsInteracted** - Marks message as interacted. Typically used when user taps on an item. - **didSwipeRow(item: Knock.FeedItem, swipeAction: FeedNotificationRowSwipeAction)** - Called when a user performs a horizontal swipe action on a row item. - **topActionButtonTapped(action: Knock.FeedTopActionButtonType)** - Called when a user taps on one of the action buttons at the top of the list. ### Examples ```swift @State var viewModel = Knock.InAppFeedViewModel() let feedView = Knock.InAppFeedView().environmentObject(viewModel) ``` ## Authentication ### `Knock.shared.isAuthenticated()` Convenience method to determine if a user is currently authenticated for the Knock instance. **Returns**: `Bool` **Params** - **checkUserToken** (`Bool`) - Whether Knock should also check to make sure user has a user token. Only required when using a Knock prod environment. ### `Knock.shared.signIn()` Sets the userId and userToken for the current Knock instance. If the device token and pushChannelId were set previously, this will also attempt to register the token to the user that is being signed in. This does not get the user from the database nor does it return the full User object. You should consider using this in areas where you update your local user's state. **Params** - **userId** (`String`) - The Knock user ID to make requests against. - **userToken** (`String (optional)`) - A JWT that identifies the authenticated user, signed with the private key provided in the Knock dashboard. Required to secure your production environment. [Learn more](https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled). **Example** ```swift title="Signing user into Knock instance" import Knock await Knock.shared.signIn(userId: "your-user-id", userToken: "your-user-token") ``` ### `Knock.shared.signOut()` Sets the userId and userToken for the current Knock instance back to nil. If the device token and pushChannelId were set previously, this will also attempt to unregister the token to the user that is being signed out so they don't receive pushes they shouldn't get. You should call this when your user signs out - Note: This will not clear the device token so that it can be accessed for the next user to login. **Example** ```swift import Knock await Knock.shared.signOut() ``` ## User Management ### `Knock.shared.getUserId()` Fetch the userId that was set from the Knock.shared.signIn method. **Returns**: `String?` ### `Knock.shared.getUser()` Retrieves the current user's profile by calling the [get user endpoint.](/api-reference/users/get) **Returns**: `Knock.User` ### `Knock.shared.updateUser()` Updates the current user's profile by calling the [identify user endpoint](/api-reference/users/update). When updating an existing user, the provided properties are merged with what is currently set on the user, updating only the fields included in your request. **Returns**: `Knock.User` **Params** - **user** (`Knock.User`) - The User object that you want to set for the current user. ## Channels/Push Notifications ### `Knock.shared.getUserChannelData()` Returns the channel data for the current user on the channel specified with `channelId`. **Params** - **channelId** (`String`) - The channel ID to get channel data for. **Returns**: `Knock.ChannelData` ### `Knock.shared.updateUserChannelData()` Updates the channel data for the current user on the channel specified with `channelId`. **Params** - **channelId** (`String`) - The channel ID to update the channel data for. - **data** (`AnyEncodable`) - The data to update for the channel data. **Returns**: `Knock.ChannelData` ### `Knock.shared.getApnsDeviceToken()` Returns the apnsDeviceToken that was set from the Knock.shared.registerTokenForAPNS. If you use our KnockAppDelegate, the token registration will be handled for you automatically. **Returns** `String?` ### `Knock.shared.registerTokenForAPNS()` Registers an Apple Push Notification Service token so that the device can receive remote push notifications. This is a convenience method that internally gets the channel data and searches for the token. If it exists, then it's already registered and it returns. If the data does not exists or the token is missing from the array, it's added. If the new token differs from the last token that was used on the device, the old token will be unregistered. You can learn more about APNs [here](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns). **Params** - **channelId** (`String`) - The Knock APNs channel id to associate the device token to. - **token** (`String OR Data`) - the APNs device token. **Returns**: `Knock.ChannelData` ### `Knock.shared.unregisterTokenForAPNS()` Unregisters the current deviceId associated to the user so that the device will no longer receive remote push notifications for the provided channelId. **Params** - **channelId** (`String`) - The Knock APNs channel id to associate the device token to. - **token** (`String OR Data`) - the APNs device token. **Returns**: `Knock.ChannelData` ### `Knock.shared.getNotificationPermissionStatus()` Convenience method to determine whether or not the user is allowing Push Notifications for the app. **Returns**: `UNAuthorizationStatus` ### `Knock.shared.requestNotificationPermission()` Requests push notification permissions to the user. **Params** - **options** (`UNAuthorizationOptions`) - The type of push notification permissions you want. Defaults to `[.sound, .badge, .alert]`. **Returns**: `UNAuthorizationStatus` ### `Knock.shared.requestAndRegisterForPushNotifications()` Convenience method to request Push Notification permissions for the app, and then, if successful, registerForRemoteNotifications in order to get a device token. ## Preferences ### `Knock.shared.getAllUserPreferences()` Returns all of the preference sets for the current user. **Returns**: `Knock.PreferenceSet` ### `Knock.shared.getUserPreferences()` Returns a single preference set for the current user, specified by the `preferenceId`. For the default preference set, set the `preferenceId` to be `default`. **Params** - **preferenceId** (`String`) - The ID of the preference set to retrieve. **Returns**: `Knock.PreferenceSet` ### `Knock.shared.setUserPreferences()` Updates the preference set specified by the `preferenceId` with the new `preferenceSet`. **Params** - **preferenceId** (`String`) - The ID of the preference set. - **preferenceSet** (`Knock.PreferenceSet`) - The preferences to update for the preference set. **Returns**: `Knock.PreferenceSet` ## Messages ### `Knock.shared.getMessage()` Retrieves a specific message by its ID by calling the [get message endpoint.](/api-reference/messages/get) **Params** - **messageId** (`String`) - The ID of the message to retrieve. **Returns** `Knock.KnockMessage` ### `Knock.shared.updateMessageStatus()` Updates the [engagement status](/send-notifications/message-statuses#engagement-status) of a single message. **Params** - **messageId** (`String`) - The ID of the message to update. - **message** (`KnockMessage`) - A Knock message to update. - **status** (`KnockMessageStatusUpdateType`) - The engagement status to set (e.g., `read`, `seen`, `archived`, `interacted`). **Returns** `Knock.KnockMessage` ### `Knock.shared.deleteMessageStatus()` Removes an engagement status from a message. **Params** - **messageId** (`String`) - The ID of the message to update. - **message** (`KnockMessage`) - A Knock message to update. - **status** (`KnockMessageStatusUpdateType`) - The engagement status to remove (e.g., `read`, `seen`, `archived`). Cannot be `interacted`. **Returns** `Knock.KnockMessage` ### `batchUpdateStatuses` Updates the engagement status of multiple messages (up to 50) in a single request. **Params** - **messageIds** (`[String]`) - A list of message IDs. - **message** (`[Knock.KnockMessage]`) - A list of messages. - **status** (`Knock.KnockMessageStatusBatchUpdateType`) - The engagement status to set on all messages. Can include removal statuses like `unseen`, `unread`, `unarchived`. **Returns** `[Knock.KnockMessage]` ## FeedManager ## `Knock.FeedManager.init()` Creates a new instance of a `FeedManager` for interacting with a user's in-app notification feed. **Params** - **feedId** (`String`) - The UUID of your Knock in-app feed channel ID. - **options** (`Knock.FeedClientOptions`) - Feed options to apply as defaults. **Example** ```swift title="Creating a Knock.FeedManager" import Knock Knock.shared.feedManager = try? await Knock.FeedManager(feedId: "in-app-channel-id", options: FeedClientOptions(archived: .exclude)) ``` ### `Knock.shared.feedManager.connectToFeed()` Connects the feed instance to the real-time socket so that any new items published to the feed are received over the websocket. **Params** - **options** (`Knock.FeedClientOptions (optional)`) - Feed options to apply. Will override any options specified in the FeedManager constructor. ### `Knock.shared.feedManager.disconnectFromFeed()` Disconnects a connected real-time instance. ### `Knock.shared.feedManager.on()` Binds an event listener for incoming web socket events. Must have called `connectToFeed` first. - **eventName** (`String`) - The event name to listen for. Currently only `new-message` is supported. **Example** ```swift title="Listen to incoming feed messages" Knock.shared.feedManager.on(eventName: "new-message") { _ in // Do something with the new incoming feed message; likely need to refetch the feed contents feedManager.getUserFeedContent(options: options) { result in switch result { case .success(let feed): // Set the new items in the feed case .failure(let error): print(error.localizedDescription) } } } ``` ### `Knock.shared.feedManager.getUserFeedContent()` Retrieves the user's feed content for the feed. Can be scoped by passing `options`, which also allows for paginating the contents of the feed using the `before` and `after` cursors. **Params** - **options** (`Knock.FeedClientOptions*`) - Feed options to apply to the fetch. Will override any options specified in the FeedManager constructor. **Returns** `Knock.Feed` **Example** ```swift title="Fetching user feed content" import Knock knockClient = try! Knock(publishableKey: "your-pk", userId: "user-id") feedManager = Knock.FeedManager(client: knockClient!, feedId: "in-app-channel-id") feedManager?.getUserFeedContent() { result in switch result { case .success(let feed): // Do something with the returned feed case .failure(let error): print(error.localizedDescription) } } ``` --- ### `makeBulkStatusUpdate` Updates all of the items within the feed with the given status. Can be passed `options` to scope the request further. Note: this method returns a `BulkUpdate` via the Knock API, which is an async operation. **Params** - **type** (`Knock.BulkChannelMessageStatusUpdateType`) - The type of update to make in bulk. - **options** (`Knock.FeedClientOptions`) - Feed options to scope the bulk update by. **Returns** `Knock.BulkOperation` ## `Knock.FeedClientOptions` Used to scope a feed request. **Params** - **before** (`String`) - A cursor to return records before, used for pagination. - **after** (`String`) - A cursor to return records after, used for pagination. - **page_size** (`Int`) - The maximum number of items to return per page. - **status** (`FeedItemScope`) - One of either `all`, `unread`, `read`, `unseen`, `seen`. - **source** (`String`) - Scope to a single workflow source for the feed items. - **tenant** (`String`) - Scope to a single tenant. - **has_tenant** (`Bool`) - Scope to whether the feed items have or do not have a tenant set. - **archived** (`FeedItemArchivedScope`) - Scope by archive status. One of `include`, `exclude`, or `only`. - **trigger_data** (`[String: AnyCodable]`) - Match a set of trigger data on the generated feed messages. # Android UI components ## Overview Learn more about the in-app notifications experiences you can build for Android applications with Knock. --- title: "Building in-app UI for Android" description: Learn more about the in-app notifications experiences you can build for Android applications with Knock. section: Building in-app UI --- in the current version of the Android SDK there are no pre-built UI elements. } /> The Knock Android SDK is a low-level set of methods for interacting with the Knock APIs from Android applications. The SDK is designed to help you easily integrate Knock into your application and build in-app notification experiences powered by Knock. ## Features - API methods for interacting with the [Knock in-app API](/in-app-ui/api-overview). - Managed websocket connections to the Knock real-time service. ## Getting started ### 1. Add Jitpack repository support in your `settings.gradle` file ```gradle pluginManagement { repositories { ... maven { url 'https://jitpack.io' } } } dependencyResolutionManagement { repositories { ... maven { url 'https://jitpack.io' } } } ``` ### 2. Add the implementation to your app `build.gradle` file ```gradle  dependencies { implementation 'com.github.knocklabs:knock-android:' } ``` [Read more in the quick start documentation ->](/in-app-ui/android/sdk/quick-start) ## Links - [Android SDK on GitHub](https://github.com/knocklabs/knock-android) - [Android SDK reference](/in-app-ui/android/sdk/reference) ## Components How to use Knock's UI components in your Android application. --- title: "Kotlin SDK pre-built components" description: "How to use Knock's UI components in your Android application." section: Building in-app UI --- ## InAppFeedView ### Overview `InAppFeedView` is a Kotlin Compose view that renders the in-app notifications feed using data from `InAppFeedViewModel`. It provides a customizable and interactive user interface for displaying notifications. ### Properties - **viewModel** (`InAppFeedViewModel`) - The ViewModel containing the logic for the InAppFeedView. - **theme** (`InAppFeedTheme`) - Defines the appearance of the feed view and its components. ### Customization You can customize almost every aspect of the UI of the `InAppFeedView` using our customizable themes. ### Examples ```swift val feedViewModel: InAppFeedViewModel = viewModel(factory = InAppFeedViewModelFactory(LocalContext.current)) val theme = InAppFeedViewTheme(context = LocalContext.current) InAppFeedView(feedViewModel, theme = theme) LaunchedEffect(key1 = Unit) { if (Knock.shared.feedManager == null) { Knock.shared.feedManager = FeedManager(feedId = Utils.inAppChannelId) feedViewModel.connectFeedAndObserveNewMessages() } feedViewModel.didTapFeedItemRowPublisher .onEach { feedItem -> // Handle the feed item row tap event } .launchIn(this) feedViewModel.didTapFeedItemButtonPublisher .onEach { feedItemButtonEvent -> // Handle the feed item button block tap event } .launchIn(this) } ``` ## Customization How to customize Knock's Android UI components. --- title: "Customizing Knock UI components in Android" description: "How to customize Knock's Android UI components." section: Building in-app UI --- ## InAppFeedTheme ### Overview `InAppFeedTheme` allows for UI customization of the `InAppFeedView`. ### Properties - **rowTheme** (`FeedNotificationRowTheme`) - Defines the UI customization of the row items. - **filterTabTheme** (`FilterTabTheme`) - Defines the UI customization of the top filter tabs. - **titleString** (`String?`) - Sets the title of the view. If set to nil, then the title view will be hidden entirely. This is useful if you want to have a completely custom title view. - **textStyle** (`TextStyle?`) - Sets the textStyle for the title of the view. - **upperBackgroundColor** (`Color?`) - Sets the background color of the top portion of the view (title view, filter view, and top action buttons view). - **lowerBackgroundColor** (`Color?`) - Sets the background color of the bottom portion of the view (the list). ## FeedNotificationRowTheme ### Overview `FeedNotificationRowTheme` allows for UI customization of the row items in the `KnockInAppFeedView`. ### Properties - **backgroundColor** (`Color`) - Background color of the FeedNotificationRow. - **bodyTextStyle** (`TextStyle?`) - Set the textStyle of the body of the message. - **unreadNotificationCircleColor** (`Color`) - Color of the unread circle indicator in the top left of the row. - **showAvatarView** (`Bool`) - Show or hide the avatar/initials view in the upper left corner of the row. - **avatarViewTheme** (`AvatarViewTheme`) - Customize styling of avatarview. - **primaryActionButtonConfig** (`ActionButtonConfig`) - Styling for primary action buttons. - **secondaryActionButtonConfig** (`ActionButtonConfig`) - Styling for secondary action buttons. - **sentAtDateFormatter** (`DateTimeFormatter`) - DateTimeFormatter for the sent timestamp at the bottom of the row. - **sentAtDateTextStyle** (`TextStyle`) - TextStyle for sent timestamp. - **markAsReadSwipeConfig** (`SwipeConfig?`) - This is the config to set the mark as read/unread swipe actions. Set to null to remove the action entirely. - **archiveSwipeConfig** (`SwipeConfig?`) - This is the config to set the archive/unarchive swipe actions. Set to null to remove the action entirely. ## AvatarViewTheme ### Overview `AvatarViewTheme` allows for UI customization of the user avatar view in the row item. ### Properties - **avatarViewBackgroundColor** (`Color?`) - Background color of the view. This is more apparent when the view is showing initials instead of an image. - **avatarViewInitialsTextStyle** (`TextStyle?`) - TextStyle for the initials view. - **avatarViewSize** (`CGFloat`) - Overall size of the avatar view. ## FilterTabTheme ### Overview `FilterTabTheme` allows for UI customization of the filter tab bar at the top of the view. ### Properties - **selectedColor** (`Color?`) - The color the tab will be in the selected state. - **unselectedColor** (`Color?`) - The color the tab will be in the unselected state. - **textStyle** (`TextStyle`) - The TextStyle of the filter tab text. ## EmptyFeedViewTheme ### Overview `EmptyFeedViewTheme` allows for UI customization of the EmptyFeedView for each filter. ### Properties - **backgroundColor** (`Color?`) - The background color of the view. - **title** (`String?`) - The title of the EmptyFeedView. - **titleTextStyle** (`TextStyle`) - The TextStyle of the title. - **subtitle** (`TextStyle`) - The subtitle of the EmptyFeedView. - **subtitleTextStyle** (`TextStyle`) - The TextStyle of the subtitle. - **icon** (`ImageVector?`) - The TextStyle of the filter tab text. - **iconResId** (`Int?`) - The TextStyle of the filter tab text. - **iconSize** (`Int?`) - The TextStyle of the filter tab text. - **iconColor** (`Color?`) - The TextStyle of the filter tab text. ## FilterTabTheme ### Overview `FilterTabTheme` allows for UI customization of the filter tab bar at the top of the view. ### Properties - **selectedColor** (`Color?`) - The color the tab will be in the selected state. - **unselectedColor** (`Color?`) - The color the tab will be in the unselected state. - **textStyle** (`TextStyle`) - The TextStyle of the filter tab text. ## SwipeConfig ### Overview `SwipeConfig` allows for UI customization of the left and right swipe actions of the FeedNotificationRow. ### Properties - **action** (`FeedNotificationRowSwipeAction?`) - The action you want taken on the swipe. - **title** (`String?`) - The title of the swipe action (e.g. `Read`). - **inverseTitle** (`String?`) - The inverse title of the swipe action (e.g. `Unread`). - **titleStyle** (`TextStyle?`) - The textStyle of the title. - **imageId** (`Int?`) - The image resource. - **inverseImageId** (`Int?`) - The inverse image resource. - **imageSize** (`Int?`) - The size of the image. - **imageColor** (`Color?`) - The color of the image. - **swipeColor** (`Color?`) - The background color of the swipe action. # Android SDK ## Overview Learn more about integrating Knock into your Android applications through our Android SDK. --- title: "Knock Android SDK" description: Learn more about integrating Knock into your Android applications through our Android SDK. section: SDKs --- The Knock Kotlin SDK is a client-side SDK for interacting with the Knock API and for building notification experiences within Android applications. **Quick links** - [SDK on GitHub](https://github.com/knocklabs/knock-android) - [Full reference documentation](/in-app-ui/android/sdk/reference) ## Example app You can find a complete Android example application that uses the Knock Android SDK here. The app shows patterns for handling push token registration, building an in-app feed using Combine, and managing user notification preferences. ## Need help? Our Android SDK is worked on full-time by the Knock Mobile team. ### Join the community - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/knock-android/issues/new) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Quick start Get started with the Knock Android SDK to build in-app notification experiences. --- title: "Getting started with the Android SDK" description: Get started with the Knock Android SDK to build in-app notification experiences. section: SDKs --- To get started, you will need the following: - [A Knock Account](https://dashboard.knock.app/signup) - A public API key for the Knock environment (which you'll use in the `publishableKey`) - An in-app feed channel with a workflow that produces in-app feed messages (optional) - A Firebase Cloud Messaging channel with a workflow that produces push notifications (optional) ## Installation You can install the Android SDK in a the following ways: - Jitpack - Manually See here for more information on installation. ### Initializing a Knock instance To initialize the shared Knock instance, you are required to use your publishable key, which is identified by the prefix **`pk_`**. Additionally, if you opt to utilize our **`KnockMessagingService`** and **`KnockActivity`** for comprehensive device token registration and management, you must also include your **`pushChannelId`** during the setup process of your instance. You should do this setup as soon as you can. Preferably within your **`Application`** class. ```kotlin Knock.setup(context = "applicationContext", publishableKey = "your-pk", pushChannelId = "apns-channel-id") ``` ### Authenticating a user Once you've configured the shared Knock instance with your publishable key, the next step is to sign the user into Knock. This requires the **`userId`** and, for interactions with your production Knock environment, the **`userToken`**. For further details on **`userTokens`**, please refer to our [documentation](https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled). We recommend you initiate the user sign-in process at the earliest point where the **`userId`** is known to you. This ensures that your application is ready to leverage Knock's features with the context of the signed-in user. ```kotlin Knock.shared.signIn(userId = "userId", userToken = "userToken") ``` ## Push notifications Documentation to help you get started with the Push Notifications in the Android Knock SDK. --- title: "Handling Android push notifications" description: "Documentation to help you get started with the Push Notifications in the Android Knock SDK." section: SDKs --- **Note:** We recommend taking advantage of our [KnockMessagingService & KnockActivity](/in-app-ui/android/sdk/reference#knockmessagingservice--knockactivity) to make managing your push notifications simpler. ## Prerequisites - Before proceeding, ensure you've configured push notifications within your Knock account. For guidance on this initial setup, refer to our [push notification documentation](/integrations/push/overview). - Review Firebase's documentation for advanced features and updated practices. ## 1. Create a Firebase project - **Go to the Firebase Console.** - **Click on "Add project"** and follow the on-screen instructions to create a new Firebase project. ## 2. Add your Android app to the Firebase project - In the Firebase Console, open the project you just created. - Click on the Android icon to add an Android app to your Firebase project. - Enter your app's package name and a nickname for your app. - (Optional) Enter the SHA-1 of your signing certificate. - Download the `google-services.json` file and place it in your app's `app/` directory. ## 3. Add Firebase SDK to your project - In your project-level `build.gradle` file, add the Google services Gradle plugin as a dependency: ```groovy buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.10' } } ``` - In your app-level `build.gradle` file, apply the Google services plugin at the bottom of the file and add Firebase Messaging dependency: ```groovy apply plugin: 'com.android.application' android { // Your android config } dependencies { // Add the Firebase Messaging dependency implementation 'com.google.firebase:firebase-messaging:23.0.0' } // Add this line at the bottom apply plugin: 'com.google.gms.google-services' ``` ## 4. Update your app's manifest - Add the service you just created to your `AndroidManifest.xml`: ```xml ``` ## 5. Requesting user permission for push notifications - To prompt the user to approve or deny push notification permissions call the `Knock.shared.requestNotificationPermission()` method. ```kotlin public class MainActivity { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Knock.shared.requestNotificationPermission(this) } } ``` ## 6. Register device token - **KnockMessagingService:** - If using the `KnockMessagingService`, this will be handled for you automatically. - **Manually:** ```kotlin class MyMessagingService: FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) Knock.shared.registerTokenForFCM(channelId = YOUR_PUSH_CHANNEL_ID, token = token) { _ -> } } } ``` ## 7. Receive push notifications To detect when a push notification is received in the foreground: - **KnockMessagingService:** ```kotlin class MyMessagingService: KnockMessagingService() { override fun fcmRemoteMessageReceived(message: RemoteMessage) { super.fcmRemoteMessageReceived(message) // This is just an example of how you could present a notification with the app in the foreground. // You should customize this to fit your own app's needs. message.presentNotification( context = this, handlingClass = MainActivity::class.java, icon = android.R.drawable.ic_dialog_info ) } } ``` - **Manually:** ```kotlin class MyMessagingService: FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { super.onMessageReceived(message) // This is just an example of how you could present a notification with the app in the foreground. // You should customize this to fit your own app's needs. message.presentNotification( context = this, handlingClass = MainActivity::class.java, icon = android.R.drawable.ic_dialog_info ) } } ``` ## 8. Handling push notification taps - **KnockMessagingService:** When using `KnockMessagingService` and `KnockActivity`, message engagement status is automatically updated to "read" and "interacted" when a user taps on a push notification. ```kotlin class MainActivity: KnockActivity() { override fun onKnockPushNotificationTappedInBackground(intent: Intent) { super.onKnockPushNotificationTappedInBackground(intent) // Perform any action here } override fun onKnockPushNotificationTappedInForeground(message: RemoteMessage) { super.onKnockPushNotificationTappedInForeground(message) // Perform any action here } } ``` - **Manually:** ```kotlin class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // See if there is a pending tap event from a PushNotification checkForPushNotificationTap(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) // See if there is a pending tap event from a PushNotification checkForPushNotificationTap(intent) } fun checkForPushNotificationTap(intent: Intent?) { intent?.extras?.getString(Knock.KNOCK_MESSAGE_ID_KEY)?.let { Knock.shared.updateMessageStatus(it, KnockMessageStatusUpdateType.INTERACTED) {} // Push notification tapped in background } ?: (intent?.extras?.get(Knock.KNOCK_PENDING_NOTIFICATION_KEY) as? RemoteMessage)?.let { message -> // Clear the intent extra intent.extras?.remove(Knock.KNOCK_PENDING_NOTIFICATION_KEY) message.data[Knock.KNOCK_MESSAGE_ID_KEY]?.let { Knock.shared.updateMessageStatus(it, KnockMessageStatusUpdateType.INTERACTED) {} } // Push notification tapped in foreground } } } ``` ## Deep links Follow this documentation to get started with deep linking in the Knock Android SDK. --- title: Handling deep links description: Follow this documentation to get started with deep linking in the Knock Android SDK. section: SDKs --- We recommend taking advantage of our{" "} KnockMessagingService & KnockActivity {" "} to simplify deep link handling. } /> ## App links Follow the steps below to configure URLs that deeply link to specific content in your Android application. You can read more about app links in the Android Developer documentation here. In your `AndroidManifest.xml` file, add intent filters for the app link schemes you want to use. The App Links Assistant in Android Studio simplifies this process with a step-by-step wizard. ```xml title="Add intent filters to your AndroidManifest.xml file" ``` - In your trigger `data` payload that you send to Knock, include a property with a value for your app link. The name of the property doesn't matter, so long as you know beforehand what it will be called. - You can configure the format of the API request sent to FCM in your workflow step's [payload overrides](/integrations/push/firebase#using-overrides-to-customize-notifications). ```json title="Payload override configuration for an FCM notification in Knock" { "data": { "link": "https://example.com/app-link" } } ``` Handle incoming URLs in your `KnockActivity` or `KnockMessagingService`. ```kotlin title="Example for handling app links" class MainActivity: KnockActivity() { override fun onKnockPushNotificationTappedInBackground(intent: Intent) { super.onKnockPushNotificationTappedInBackground(intent) intent?.extras?.getString("link")?.let { appLink -> // Handle your app link routing here } } override fun onKnockPushNotificationTappedInForeground(message: RemoteMessage) { super.onKnockPushNotificationTappedInForeground(message) remoteMessage.data.isNotEmpty().let { val appLink = remoteMessage.data["link"] appLink?.let { // Handle your app link routing here } } } } ``` ## Verify app links You can optionally associate your app links with your website. ## Reference The complete API reference for the Knock Android SDK. --- title: "Android API reference" description: The complete API reference for the Knock Android SDK. section: SDKs --- In this section, you'll find the documentation for the classes and methods available in the [Android SDK](https://github.com/knocklabs/knock-android). ## Knock The top-level Knock class. This is a shared instance to interact with the SDK's API methods. ### `Knock.shared.setup()` Sets up the shared Knock instance. Make sure to call this as soon as you can. Preferably in your AppDelegate. **Params** - **publishableKey** (`String`) - The public API key for the Knock environment. - **pushChannelId** (`String (optional)`) - The Knock APNs channel id that you plan to use within your app. - **options** (`KnockStartupOptions (optional)`) - Optional startup options to configure your Knock instance. ## KnockMessagingService & KnockActivity These classes serves as optional base classes designed to streamline the integration of Knock into your application. By inheriting from KnockMessagingService in your FirebaseMessagingService class and the KnockActivity in your MainActivity class, you gain automatic handling of FCM Push Notification registration and device token management, simplifying the initial setup process for Knock's functionalities. These classes also provide a set of open helper functions that are intended to facilitate the handling of different Push Notification events such as delivery in the foreground and taps. These helper methods offer a straightforward approach to customizing your app's response to notifications, ensuring that you can tailor the behavior to fit your specific needs. Override any of the provided methods to achieve further customization, allowing you to control how your application processes and reacts to Push Notifications. Additionally, by leveraging this class, you ensure that your app adheres to best practices for managing device tokens and interacting with the notification system on Android, enhancing the overall reliability and user experience of your app's notification features. Key Features: - Automatic registration for remote notifications, ensuring your app is promptly set up to receive and handle Push Notifications. - Simplified device token management, with automatic storage of the device token, facilitating easier access and use in Push Notification payloads. - Customizable notification handling through open helper functions, allowing for bespoke responses to notification events such as foreground delivery, and user taps. - Automatic message status updates, based on Push Notification interaction. ```kotlin title="Example KnockMessagingService" class ExampleMessagingService: KnockMessagingService() { override fun messageReceivedInForeground(message: RemoteMessage) { super.messageReceivedInForeground(message) // This is just an example of how you could present a notification with the app in the foreground. // You should customize this to fit your own app's needs. message.presentNotification( context = this, handlingClass = MainActivity::class.java, icon = android.R.drawable.ic_dialog_info ) } } ``` --- ```kotlin title="Example KnockActivity" class MainActivity : KnockComponentActivity() { override fun onKnockPushNotificationTappedInBackGround(intent: Intent) { super.onKnockPushNotificationTappedInBackGround(intent) Log.d(Utils.loggingTag, "tapped in background") } override fun onKnockPushNotificationTappedInForeground(message: RemoteMessage) { super.onKnockPushNotificationTappedInForeground(message) Log.d(Utils.loggingTag, "tapped in foreground") } } ``` ## Authentication ### `Knock.shared.isAuthenticated()` Convienience method to determine if a user is currently authenticated for the Knock instance. **Returns**: `Bool` **Params** - **checkUserToken** (`Bool`) - Whether Knock should also check to make sure user has a user token. Only required when using a Knock prod environment. ### `Knock.shared.signIn()` Sets the userId and userToken for the current Knock instance. If the device token and pushChannelId were set previously, this will also attempt to register the token to the user that is being signed in. This does not get the user from the database nor does it return the full User object. You should consider using this in areas where you update your local user's state. **Params** - **userId** (`String`) - The Knock user ID to make requests against. - **userToken** (`String (optional)`) - A JWT that identifies the authenticated user, signed with the private key provided in the Knock dashboard. Required to secure your production environment. [Learn more](https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled). **Example** ```kotlin title="Signing user into Knock instance" import Knock await Knock.shared.signIn(userId: "your-user-id", userToken: "your-user-token") ``` ### `Knock.shared.signOut()` Sets the userId and userToken for the current Knock instance back to nil. If the device token and pushChannelId were set previously, this will also attempt to unregister the token to the user that is being signed out so they don't receive pushes they shouldn't get. You should call this when your user signs out **Example** ```swift import Knock await Knock.shared.signOut() ``` ## User Management ### `Knock.shared.getUserId()` Fetch the userId that was set from the Knock.shared.signIn method. **Returns**: `String?` ### `Knock.shared.getUser()` Retrieves the current user's profile by calling the [get user endpoint.](/api-reference/users/get) **Returns**: `KnockUser` ### `Knock.shared.updateUser()` Updates the current user's profile by calling the [identify user endpoint](/api-reference/users/update). When updating an existing user, the provided properties are merged with what is currently set on the user, updating only the fields included in your request. **Returns**: `KnockUser` **Params** - **user** (`KnockUser`) - The User object that you want to set for the current user. ## Channels/Push Notifications ### `Knock.shared.getUserChannelData()` Returns the channel data for the current user on the channel specified with `channelId`. **Params** - **channelId** (`String`) - The channel ID to get channel data for. **Returns**: `ChannelData` ### `Knock.shared.updateUserChannelData()` Updates the channel data for the current user on the channel specified with `channelId`. **Params** - **channelId** (`String`) - The channel ID to update the channel data for. - **data** (`AnyEncodable`) - The data to update for the channel data. **Returns**: `ChannelData` ### `Knock.shared.getCurrentDeviceToken()` Returns the FCM device token that was set from the Knock.shared.registerTokenForFCM. If you use our KnockMessagingService, the token registration will be handled for you automatically. **Returns** `String?` ### `Knock.shared.registerTokenForFCM()` Registers an FCM token so that the device can receive remote push notifications. This is a convenience method that internally gets the channel data and searches for the token. If it exists, then it's already registered and it returns. If the data does not exists or the token is missing from the array, it's added. If the new token differs from the last token that was used on the device, the old token will be unregistered. You can learn more about FCM [here](https://firebase.google.com/docs/cloud-messaging/android/client). **Params** - **channelId** (`String`) - The Knock FCM channel id to associate the device token to. - **token** (`String OR Data`) - the FCM device token. **Returns**: `ChannelData` ### `Knock.shared.unregisterTokenForFCM()` Unregisters the current deviceId associated to the user so that the device will no longer receive remote push notifications for the provided channelId. **Params** - **channelId** (`String`) - The Knock FCM channel id to associate the device token to. - **token** (`String OR Data`) - the FCM device token. **Returns**: `ChannelData` ### `Knock.shared.isPushPermissionGranted()` Convenience method to determine whether or not the user is allowing Push Notifications for the app. **Returns**: `Boolean` ### `Knock.shared.requestNotificationPermission()` Requests push notification permissions to the user. **Params** - **requestCode** (`Int`) - Application specific request code to match with a result reported to. Default is `1`. ## Preferences ### `Knock.shared.getAllUserPreferences()` Returns all of the preference sets for the current user. **Returns**: `PreferenceSet` ### `Knock.shared.getUserPreferences()` Returns a single preference set for the current user, specified by the `preferenceId`. For the default preference set, set the `preferenceId` to be `default`. **Params** - **preferenceId** (`String`) - The ID of the preference set to retrieve. **Returns**: `PreferenceSet` ### `Knock.shared.setUserPreferences()` Updates the preference set specified by the `preferenceId` with the new `preferenceSet`. **Params** - **preferenceId** (`String`) - The ID of the preference set. - **preferenceSet** (`PreferenceSet`) - The preferences to update for the preference set. **Returns**: `PreferenceSet` ## Messages ### `Knock.shared.getMessage()` Retrieves a specific message by its ID by calling the [get message endpoint.](/api-reference/messages/get) **Params** - **messageId** (`String`) - The ID of the message to retrieve. **Returns** `KnockMessage` ### `Knock.shared.updateMessageStatus()` Updates the [engagement status](/send-notifications/message-statuses#engagement-status) of a single message. **Params** - **messageId** (`String`) - The ID of the message to update. - **message** (`KnockMessage`) - A Knock message to update. - **status** (`KnockMessageStatusUpdateType`) - The engagement status to set (e.g., `read`, `seen`, `archived`, `interacted`). **Returns** `KnockMessage` ### `Knock.shared.deleteMessageStatus()` Removes an engagement status from a message. **Params** - **messageId** (`String`) - The id of the message to update. - **message** (`KnockMessage`) - A Knock message to update. - **status** (`KnockMessageStatusUpdateType`) - The engagement status to remove (e.g., `read`, `seen`, `archived`). Cannot be `interacted`. **Returns** `KnockMessage` ### `batchUpdateStatuses` Updates the engagement status of multiple messages (up to 50) in a single request. **Params** - **messageIds** (`[String]`) - A list of message IDs. - **status** (`KnockMessageStatusUpdateType`) - The engagement status to set on all messages. Can include removal statuses like `unseen`, `unread`, `unarchived`. **Returns** `[KnockMessage]` ## FeedManager ## `Knock.FeedManager.init()` Creates a new instance of a `FeedManager` for interacting with a user's in-app notification feed. **Params** - **feedId** (`String`) - The UUID of your Knock in-app feed channel ID. - **options** (`FeedClientOptions`) - Feed options to apply as defaults. ### `Knock.shared.feedManager.connectToFeed()` Connects the feed instance to the real-time socket so that any new items published to the feed are received over the websocket. **Params** - **options** (`FeedClientOptions (optional)`) - Feed options to apply. Will override any options specified in the FeedManager constructor. ### `Knock.shared.feedManager.disconnectFromFeed()` Disconnects a connected real-time instance. ### `Knock.shared.feedManager.on()` Binds an event listener for incoming web socket events. Must have called `connectToFeed` first. - **eventName** (`String`) - The event name to listen for. Currently only `new-message` is supported. **Example** ```kotlin Knock.shared.feedManager?.on("new-message") { viewModelScope.launch { val feedOptions = FeedClientOptions(before = feed.value?.pageInfo?.before) val result = withContext(Dispatchers.IO) { Knock.shared.feedManager?.getUserFeedContent(feedOptions) } result?.let { feedResult -> _feed.value?.let { currentFeed -> val updatedEntries = feedResult.entries + (currentFeed.entries) _feed.value = currentFeed.copy(entries = updatedEntries) } _feed.value?.let { it.meta.unseenCount = feedResult.meta.unseenCount it.meta.unreadCount = feedResult.meta.unreadCount it.meta.totalCount = feedResult.meta.totalCount it.pageInfo.before = feedResult.entries.firstOrNull()?.feedCursor } } } } ``` ### `Knock.shared.feedManager.getUserFeedContent()` Retrieves the user's feed content for the feed. Can be scoped by passing `options`, which also allows for paginating the contents of the feed using the `before` and `after` cursors. **Params** - **options** (`FeedClientOptions (optional)`) - Feed options to apply to the fetch. Will override any options specified in the FeedManager constructor. **Returns** `Feed` ### `Knock.shared.feedManager.makeBulkStatusUpdate()` Updates all of the items within the feed with the given status. Can be passed `options` to scope the request further. Note: this method returns a `BulkUpdate` via the Knock API, which is an async operation. **Params** - **type** (`KnockMessageStatusUpdateType`) - The type of update to make in bulk. - **options** (`FeedClientOptions (optional)`) - Feed options to scope the bulk update by. **Returns** `BulkOperation` ## `FeedClientOptions` Used to scope a feed request. **Params** - **before** (`String (optional)`) - A cursor to return records before, used for pagination. - **after** (`String (optional)`) - A cursor to return records after, used for pagination. - **page_size** (`Int (optional)`) - The maximum number of items to return per page. - **status** (`FeedItemScope (optional)`) - One of either `all`, `unread`, `read`, `unseen`, `seen`. - **source** (`String (optional)`) - Scope to a single workflow source for the feed items. - **tenant** (`String (optional)`) - Scope to a single tenant. - **has_tenant** (`Boolean (optional)`) - Scope to whether the feed items have or do not have a tenant set. - **archived** (`FeedItemArchivedScope (optional)`) - Scope by archive status. One of `include`, `exclude`, or `only`. - **trigger_data** (`Map (optional)`) - Match a set of trigger data on the generated feed messages. # Flutter UI Components ## Overview Learn more about the in-app notifications experiences you can build for Flutter applications with Knock. --- title: "Building in-app UI for Flutter" description: Learn more about the in-app notifications experiences you can build for Flutter applications with Knock. section: Building in-app UI --- in the current version of the Flutter SDK there are no pre-built UI elements. } /> The Knock Flutter SDK is a low-level set of methods for interacting with the Knock APIs from Flutter applications. The SDK is designed to help you easily integrate Knock into your application and build in-app notification experiences powered by Knock. ## Features - API methods for interacting with the [Knock in-app API](/in-app-ui/api-overview). - Managed websocket connections to the Knock real-time service. - State management for powering in-app feeds, with optimistic client-side updates. ## Getting started ```bash flutter pub add knock_flutter ``` [Read more in the quick start documentation ->](/in-app-ui/flutter/sdk/quick-start) ## Links - [`knock_flutter` on pub.dev](https://pub.dev/packages/knock_flutter) - [Example application using the Flutter SDK](https://github.com/knocklabs/knock-flutter/tree/main/example) - [Full SDK reference](/in-app-ui/flutter/sdk/reference) - [GitHub repository](https://github.com/knocklabs/knock-flutter) # Flutter SDK ## Overview Learn more about integrating Knock into your Flutter applications. --- title: "Knock Flutter SDK" description: Learn more about integrating Knock into your Flutter applications. section: SDKs tags: ["flutter", "dart"] --- Our `knock_flutter` library lets you create in-app notification experiences in Flutter applications using Knock's client APIs. Version 1.0.0 {" "} is a breaking release. Highlights include a pure-Dart package (no native plugin), API types renamed with a Knock prefix, and{" "} KnockApiException now implementing Exception{" "} (not Error). } /> **Quick links:** - [`knock_flutter` on pub.dev](https://pub.dev/packages/knock_flutter) - [Example application using the Flutter SDK](https://github.com/knocklabs/knock-flutter/tree/main/example) - [Full reference](/in-app-ui/flutter/sdk/reference) - [GitHub repository](https://github.com/knocklabs/knock-flutter) Using the Flutter SDK it's possible to: - **Register devices for push.** Token retrieval is your responsibility (for example with firebase_messaging); the SDK forwards tokens to Knock channels via UserClient.registerTokenForChannel. See the SDK README section on push notifications (manual engagement handling still applies where relevant). - Build in-app experiences, like feeds that update in real-time - Create notification preference control centers ## Need help? Our Flutter library is worked on full-time by the Knock Mobile team. ### Join the community Ask questions and find answers on the following platforms: - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Quick start Get started with the Knock Flutter SDK to build in-app notification experiences. --- title: "Getting started with the Flutter SDK" description: Get started with the Knock Flutter SDK to build in-app notification experiences. section: SDKs --- The Knock Flutter SDK is a client-side SDK for interacting with the Knock API and for building in-app notification experiences for Flutter applications. This documentation shows some of the ways you can interact with the SDK. ## Quick links - [SDK on GitHub](https://github.com/knocklabs/knock-flutter) - [Full reference documentation](/in-app-ui/flutter/sdk/reference) - [Example application](https://github.com/knocklabs/knock-flutter/tree/main/example) ## Requirements - Dart SDK `>=3.8.0` - Flutter `>=3.32.0` (These match the `environment` constraints in the package pubspec.yaml.) ## Installation You can find `knock_flutter` on pub.dev. ```bash flutter pub add knock_flutter ``` That command resolves to the current 1.x line. If you are upgrading from `0.1.x`, use the migration table in the SDK README. ## Setup the SDK ```dart import 'package:knock_flutter/knock_flutter.dart'; final knock = Knock( const String.fromEnvironment('KNOCK_PUBLIC_API_KEY'), // Optional: override the API host (e.g. for staging). // options: KnockOptions(host: 'https://api.knock.app'), ); knock.authenticate('your-user-id', 'optional-signed-user-token'); // When you are done with the Knock instance (e.g. logout / app teardown): knock.dispose(); ``` Earlier versions shipped a native plugin for token helpers; 1.0.0 does not. Add push dependencies yourself (for example{" "} firebase_messaging ), obtain the device token in your app, then register it with registerTokenForChannel . See the README push notifications section for patterns and caveats. } /> Each knock.feed(...) call returns a new{" "} FeedClient. When you no longer need it (for example when a screen is disposed), call feedClient.dispose() to release sockets and streams. See the{" "} FeedClient lifecycle {" "} notes on the reference page. } /> ## Reference The complete API reference for the Knock Flutter SDK. --- title: "Flutter API reference" description: The complete API reference for the Knock Flutter SDK. section: SDKs --- In this section, you'll find the documentation for the classes and methods available in the [Flutter SDK](https://github.com/knocklabs/knock-flutter). ## `Knock` The top-level Knock class. Create an authenticated Knock client instance for interacting with Knock. **Params** - **apiKey** (`String*`) - The public API key for the Knock environment. - **options** (`KnockOptions`) - Any additional options to instantiate your Knock instance with. **Returns** `Knock` ### `authenticate` Authenticates the current user. ```dart void authenticate(String userId, [String? userToken]) ``` **Params** - **userId** (`String*`) - The ID of the user to authenticate against. - **userToken** (`String?`) - Optional second positional argument. The signed user token, required when using enhanced security mode in the environment. **Returns** `void` ### `logout` Clears any user authentication and disposes of any created clients. **Returns** `void` ### `isAuthenticated` Returns whether or this Knock instance is authenticated. Passing `true` will check the presence of the `userToken` as well. **Returns** `bool` ### `dispose` Disposes the cached `KnockApiClient`, which closes the Phoenix socket and ends the client status stream. Call this when the Knock instance is no longer needed (for example on logout or app teardown). Call it alongside (or before) `FeedClient.dispose()` for any feed clients you created. **Returns** `void` ### Push tokens The SDK does not read FCM or APNs tokens. Obtain tokens in your app (for example with [`firebase_messaging`](https://pub.dev/packages/firebase_messaging)), then register them with [`registerTokenForChannel`](#registertokenforchannel). See the README section on [push notifications](https://github.com/knocklabs/knock-flutter/blob/main/README.md#push-notifications). ### `client` Returns the shared `KnockApiClient` used for HTTP and Phoenix traffic. Most apps do not need this; it is cached lazily and is useful for advanced integration or debugging. **Returns** `KnockApiClient` ### `messages` Returns the [`MessagesClient`](#messagesclient) for the authenticated user. **Returns** `MessagesClient` ### `user` Returns the current user client. **Returns** `UserClient` ### `preferences` Returns the current preferences client. **Returns** `PreferencesClient` ### `feed` Returns a new feed client for the channel ID specified. **Params** - **feedChannelId** (`String*`) - The channel ID from Knock for the in-app feed. - **options** (`FeedOptions`) - Any additional options to instantiate your feed instance with. **Returns** `FeedClient` --- ## Types These types appear throughout the public API. In 1.0.0, several were renamed with a `Knock` prefix. ### `KnockApiClient` HTTP client and Phoenix connection manager (formerly `ApiClient`). You usually access it only via [`Knock.client()`](#client) if at all. ### `KnockApiResponse` Wrapper for successful API payloads (formerly `ApiResponse`). ### `KnockApiClientStatus` Status values for the shared API client connection (formerly `ApiClientStatus`). ### `KnockApiException` Thrown when the API returns an error response (formerly `ApiError`). It implements `Exception`, not `Error`, so use `on KnockApiException catch (e)` (not `on Error`). Inspect fields such as `response.status` for HTTP details. ### `NetworkStatus` Enum describing feed load state: `initial`, `loading`, `fetchMore`, `ready`, and `error`. `Feed.initialState()` uses `NetworkStatus.initial`. Prefer checking `feed.requestInFlight` (and related flags) instead of comparing to `NetworkStatus.ready` only. See the [`feed`](#feed-1) stream on `FeedClient`. ### `KnockOptions` Optional configuration for [`Knock`](#knock). Fields include `host` to override the API base URL (for example for staging). --- ## `UserClient` Methods for interacting with the current user resource in Knock. You access this under `knock.user()`. ### `get` Returns the current authenticated user from Knock. **Returns** `Future` ### `identify` Upserts the current authenticated user properties in Knock. Named parameters (`email`, `name`, `phoneNumber`, `avatar`, `locale`) use a sentinel default: **omitting** a parameter leaves that field unchanged in Knock; **passing** `null` clears it. **Returns** `Future` ### `getChannelData` Returns any channel data set for the channelId given for the current authenticated user. - **channelId** (`String`) - The channel ID from Knock. **Returns** `Future` ### `setChannelData` Updates the channel data for the current user on the channel specified with `channelId`. **Params** - **channelId** (`String`) - The channel ID to update the channel data for. - **data** (`Any`) - The data to update for the channel data. **Returns** `Future` ### `registerTokenForChannel` Registers the current device's token for the user in Knock. Failed HTTP responses (4xx/5xx) throw `KnockApiException`. **Params** - **channelId** (`String`) - The push channel ID from Knock. - **token** (`String`) - The device token to register. - **languageTag** (`String?`) - Optional. BCP 47 language tag for the device locale. When omitted, the SDK uses `PlatformDispatcher.instance.locale.toLanguageTag()`. **Returns** `Future` ### `deregisterTokenForChannel` De-registers the current device's token for the user in Knock. Failed HTTP responses (4xx/5xx) throw `KnockApiException`. **Params** - **channelId** (`String`) - The push channel ID from Knock. - **token** (`String`) - The device token to remove. **Returns** `Future` --- ## `PreferencesClient` Access via `knock.preferences()` or `knock.preferences(options: ...)`. ### `PreferencesOptions` Constructor options for the preferences client. **Params** - **preferenceSetId** (`String`) - Which preference set to use. Defaults to 'default'. Example: knock.preferences(options: PreferencesOptions(preferenceSetId: 'tenant-x')). ### `getAll` Returns all preference sets for the current user. **Returns** `Future>` ### `get` Returns a single preference set for the current user. **Returns** `Future` ### `set` - **properties** (`SetPreferencesProperties`) - The preference set to upsert. **Returns** `Future` --- ## `FeedClient` Returned from `knock.feed(...)`. Each call returns a **new** client; you own its lifetime. - **feedChannelId** (`String`) - The in-app channel ID from Knock. - **options** (`FeedOptions`) - Default options to apply to the feed instance. **Returns** `FeedClient` ### FeedClient lifecycle Each `knock.feed(...)` returns a distinct `FeedClient`. Pair creation with `feedClient.dispose()` when you tear down the corresponding UI (typically in `State.dispose()`). `Knock.dispose()` does **not** dispose feed clients; it only disposes the shared `KnockApiClient`. ### `dispose` Releases resources for this feed: Phoenix channel subscription, socket lifecycle listeners, status subscription, and broadcast event stream. Call when navigating away or in `dispose()`. Safe to call more than once (idempotent). **Returns** `void` ### `feed` Stream of `Feed` snapshots. Subscribing triggers an initial HTTP fetch on listen (not gated on a replayed Phoenix open event). Expect `Feed.initialState()` with `NetworkStatus.initial` immediately, then a transition through `loading` toward `ready`. **Returns** `Stream` ### `fetchNextPage` Fetches the next page when `pageInfo.after` is non-null. If `pageInfo.after` is `null`, this is a no-op. **Returns** `void` ### `on` Binds an event listener to the feed. The event stream ends when the underlying `KnockApiClient` is disposed (`knock.dispose()` / `knock.logout()`) or when this `FeedClient.dispose()` runs. Calling `markAs*` after disposal throws `StateError`. **Params** - **bindableFeedEvent** (`BindableFeedEvent`) - The type of event to listen to. **Returns** `Stream` ### `markAsSeen` Marks the given set of items as seen. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsUnseen` Marks the given set of items as unseen. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsRead` Marks the given set of items as read. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsUnread` Marks the given set of items as unread. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsArchived` Marks the given set of items as archived. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsUnarchived` Marks the given set of items as unarchived. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAsInteracted` Marks the given set of items as interacted. Will optimistically update the feed. **Params** - **items** (`List`) - One or more feed items to mark. **Returns** `Future` ### `markAllAsSeen` Marks all items in the user's feed as seen. Will optimistically update the items currently in the feed. **Returns** `Future` ### `markAllAsRead` Marks all items in the user's feed as read. Will optimistically update the items currently in the feed. **Returns** `Future` ### `markAllAsArchived` Marks all items in the user's feed as archived. Will optimistically update the items currently in the feed. **Returns** `Future` --- ## `MessagesClient` Returned by [`knock.messages()`](#messages). Reads and updates individual [messages](/concepts/messages) for the authenticated user. API failures throw `KnockApiException`. ### `get` **Params** - **messageId** (`String`) - The Knock message ID. **Returns** `Future` ### `updateStatus` Sets engagement status on a message. The SDK method also accepts optional named request parameters. **Params** - **messageId** (`String`) - The Knock message ID. - **status** (`MessageEngagementStatus`) - Engagement status to set. **Returns** `Future` ### `removeStatus` **Params** - **messageId** (`String`) - The Knock message ID. - **status** (`RemovableMessageStatus`) - Status to remove from the message. **Returns** `Future` ### `batchUpdateStatuses` Applies a batch status change. The SDK method also accepts optional named request parameters. **Params** - **messageIds** (`List`) - Message IDs to update. - **status** (`BatchMessageStatus`) - Batch status operation to apply. **Returns** `Future>` ### `bulkUpdateAllStatusesInChannel` Runs a bulk status update for messages in a channel. See the SDK signature for the full set of named parameters (including optional request options). **Params** - **channelId** (`String`) - In-app channel ID for the bulk operation. - **status** (`BatchMessageStatus`) - Status to apply in bulk. **Returns** `Future` ### Convenience helpers `markAsSeen`, `markAsRead`, `markAsArchived`, `markAsInteracted`, `markAsUnseen`, `markAsUnread`, and `markAsUnarchived` each take a `messageId` and delegate to `updateStatus` or `removeStatus`. # Expo SDK ## Overview Learn more about integrating Knock into your Expo applications through our Expo SDK. --- title: "Knock Expo SDK" description: Learn more about integrating Knock into your Expo applications through our Expo SDK. section: SDKs tags: ["expo", "rn", "react native"] --- Our [`@knocklabs/expo`](https://www.npmjs.com/package/@knocklabs/expo) library lets you create in-app notification experiences using Knock's client APIs in applications built with React Native and Expo. See our{" "} React Native SDK. Our Expo SDK is only meant for use with React Native apps that are built with Expo. } /> The Expo library is built on top of the `@knocklabs/client` JS SDK and includes that library as an implicit dependency. ### Installation ```bash title="Installing dependencies" npm install @knocklabs/expo ``` ### Configuration To configure the feed you will need: 1. A public API key (found in the Knock dashboard) 2. A user ID and an auth token {" "} Auth tokens are strongly recommended for production environments and are required when enhanced security mode is enabled. For more information, see our{" "} Security & Authentication documentation . } /> 3. If integrating an in-app feed, a feed channel ID (found in the Knock dashboard) ### Usage You can integrate the feed into your app as follows: ```typescript import { KnockProvider, KnockFeedProvider, NotificationFeed, } from "@knocklabs/expo"; const YourAppLayout = () => { return ( {/* Optionally, use the KnockFeedProvider to connect an in-app feed */} { console.log("Notification tapped:", item); }} /> ); }; ``` ### Headless usage Alternatively, if you don't want to use our components you can render the feed in a headless mode using our hooks: ```typescript import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/expo"; const YourAppLayout = () => { const knockClient = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: currentUser.id }, ); const notificationFeed = useNotifications( knockClient, process.env.KNOCK_FEED_ID, ); const { metadata } = useNotificationStore(notificationFeed); useEffect(() => { notificationFeed.fetch(); }, [notificationFeed]); return Total unread: {metadata.unread_count}; }; ``` **Quick links:** - [`@knocklabs/expo` on npm](https://www.npmjs.com/package/@knocklabs/expo) - [`@knocklabs/client` on npm](https://www.npmjs.com/package/@knocklabs/client) - [Expo SDK reference](/in-app-ui/expo/sdk/reference) - [JavaScript SDK reference](/in-app-ui/javascript/sdk/reference) Using the Expo SDK it's possible to build: - [Notification feeds](/in-app-ui/react-native/notification-feeds) that update in real time - Notification preference control centers - Push notification management ## Example app Our Expo SDK example app shows patterns for handling push token registration, building an in-app feed, and managing user notification preferences. ## Need help? Our Expo SDK is worked on full-time by the Knock JavaScript team. ### Join the community Ask questions and find answers on the following platforms: - [Knock community Slack](https://knock.app/join-slack) ### Provide feedback - [Open an issue](https://github.com/knocklabs/javascript/issues/new) - Click the "Contact support" button at the top of this page to reach our support team. ### Contributing All contributors are welcome, from casual to regular. Feel free to open a pull request. ## Push notifications Documentation on integrating Expo push notifications with the Knock SDK in your React Native application. --- title: "Handling Push Notifications with React Native and Expo" description: Documentation on integrating Expo push notifications with the Knock SDK in your React Native application. section: SDKs --- **Note:** This documentation assumes you're utilizing our `KnockExpoPushNotificationProvider` for a streamlined push notification setup within your React Native and Expo environment. ## Prerequisites Before diving into the integration process, ensure your Knock account is set up for push notifications. For initial setup instructions, please visit our [push notification documentation](/integrations/push/overview). 1. **Create a Push Notification Channel in Knock:** - Log in to your Knock account and navigate to **Channels and sources** in your account settings. - Create a new channel with type `Expo Push Notifications` and note the channel ID. 2. **Install Expo Dependencies:** - `expo": "~50.0.14` - `expo-constants": "^15.4.0` - `expo-device": "^5.9.3` - `expo-notifications": "^0.27.6` 1. **Wrap Your App with `KnockExpoPushNotificationProvider`:** - Ensure your app is wrapped with `KnockProvider` and then `KnockExpoPushNotificationProvider`, passing the Expo channel ID from Knock. ```jsx import React from "react"; import { View } from "react-native"; import { KnockProvider, KnockExpoPushNotificationProvider, } from "@knocklabs/expo"; export default function App() { return ( {/* Your app content here */} ); } ``` 2. **Initiate Registration in Your Component:** - The `KnockExpoPushNotificationProvider` automatically registers for push notifications. - If you want to manually register, utilize the `useExpoPushNotifications` hook: ```jsx import React, { useEffect } from "react"; import { Text } from "react-native"; import { useExpoPushNotifications } from "@knocklabs/expo"; const MyComponent = () => { const { expoPushToken, registerForPushNotifications } = useExpoPushNotifications(); useEffect(() => { registerForPushNotifications(); }, []); return Your Expo Push Token: {expoPushToken}; }; ``` The `KnockExpoPushNotificationProvider` automatically handles receiving and tapping on notifications. When a user receives or taps on a push notification, the provider automatically sets the message engagement status to "interacted". To customize this behavior: 1. **Custom Notification Handling:** - Use the `onNotificationReceived` and `onNotificationTapped` methods from the `useExpoPushNotifications` hook to set custom handlers. ```jsx useEffect(() => { onNotificationReceived((notification) => { console.log("Notification received:", notification); }); onNotificationTapped((response) => { console.log("Notification tapped:", response); }); }, []); ``` Use the Knock dashboard or API to send a test notification to ensure your setup is correct. Verify that the notification appears on your device and that tapping on it triggers the expected behavior. ## Troubleshooting - **Not Receiving Notifications:** Ensure your Expo push token is correctly registered with Knock and that your device's notification settings allow push notifications from your app. - **Handling Silent Notifications:** If implementing silent notifications, ensure that your notification payload is correctly configured to not display an alert or sound. For further assistance, [reach out to our support team](mailto:support@knock.app). ## Reference Complete API reference for the Knock Expo SDK. --- title: "Expo SDK API reference" description: Complete API reference for the Knock Expo SDK. tags: ["sdk"] section: SDKs --- In this section, you'll find the complete documentation for the components exposed in `@knocklabs/expo`, including the props available. **Note**: You can see a reference for the methods available for the `Knock` class, as well as a `Feed` instance under the [client JS docs](/in-app-ui/javascript/sdk/overview). ## Components ### `KnockProvider` The top-level provider that connects to Knock with the given API key and authenticates a user. #### Props Accepts `KnockProviderProps` - **apiKey*** (`string`) - The public API key for the environment. - **user** (`UserIdentificationOptions`) - User identification data. - **userToken** (`string`) - A JWT that identifies the authenticated user, signed with the private key provided in the Knock dashboard. Required to secure your production environment. [Learn more.](https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security) - **enabled** (`boolean`) - Defaults to `true`. When `false`, children still render but the Knock client stays idle: no identify call, no API requests, and no websocket. Flipping it to `true` authenticates and connects the client; flipping it back to `false` disconnects it and clears its data. Use it to defer activity until you have a complete identity (see below). - **host** (`string`) - A custom API host for Knock. - **i18n** (`I18nContent`) - An optional set of translations to override the default `en` translations used in the feed components. #### Deferring activity with `enabled` `KnockProvider` takes an `enabled` prop that defaults to `true`. When it's `false`, the provider still renders its children, but the Knock client sits idle: it doesn't identify the user, make any API requests, or open a websocket. Set it back to `true` and the client authenticates and connects; set it to `false` again and it disconnects and clears its data. This is the recommended way to gate the provider on a complete identity — for example, an enhanced-security user token that isn't ready on the first render — rather than mounting and unmounting `KnockProvider` as that identity changes: ```jsx {/* ... */} ``` Auto push-notification registration also waits for `enabled` to become `true`, so a user isn't shown the OS permission prompt until client authentication completes. When `enabled` becomes `true`, feed components remount and reload their data. To react to these transitions in your own components, use the [`useKnockAuthState`](#useknockauthstate) hook. ### `KnockFeedProvider` The feed-specific provider that connects to a feed for that user. Must be a child of the `KnockProvider`. #### Props Accepts `KnockFeedProviderProps`: - **feedId*** (`string`) - The channel ID of the in-app feed to be displayed. - **defaultFeedOptions** (`FeedClientOptions`) - Set defaults for `tenant`, `has_tenant`, `source`, `archived` to scope all subsequent feed queries. - **colorMode** (`ColorMode`) - Sets the theme as either light or dark mode (defaults to light). ### `KnockExpoPushNotificationProvider` A context provider designed to streamline the integration of Expo push notifications within your React Native application. It facilitates the registration of device push tokens with the Knock backend, enabling the delivery of notifications. Moreover, this provider empowers developers to define custom behavior for handling notifications when they are received or interacted with, either by tapping or performing another action. By default, it provides a basic notification handling strategy, but it also allows for custom logic to be easily implemented according to specific application needs. **Note:** Must be a child of the `KnockProvider`. #### Props Accepts `KnockExpoPushNotificationProviderProps`: - **knockExpoChannelId*** (`string`) - The channel ID of your Expo channel from Knock. - **customNotificationHandler** (`Promise`) - Allows developers to define custom behavior for handling notifications, including whether to show alerts, play sounds, or set badge counts. - **autoRegister** (`boolean`) - When true, the Expo provider retrieves a push token from Expo and stores it as channel data on the user. Registration waits until the client is authenticated, so a user isn't shown the OS permission prompt until client authentication completes. ## Hooks ### `useKnock` The `KnockProvider` exposes a `useKnock` hook for all child components. **Returns**: `Knock`, an instance of the Knock JS client. **Example**: ```jsx import { KnockProvider, useKnock } from "@knocklabs/react"; const App = ({ authenticatedUser }) => ( ); const MyComponent = () => { const knock = useKnock(); return null; }; ``` ### `useKnockFeed` The `KnockFeedProvider` exposes a `useKnockFeed` hook for all child components. **Returns**: `KnockFeedProviderState` - **knock** (`Knock`) - The instance of the Knock client. - **feedClient** (`Feed`) - The instance of the authenticated Feed. - **useFeedStore** (`UseStore`) - A zustand store containing the FeedStoreState. - **status** (`FilterStatus`) - Current value of the filter status for the Feed. - **setStatus** (`function`) - A function to set the current FilterStatus. - **colorMode** (`ColorMode`) - The current theme color. **Example**: ```jsx import { KnockProvider, KnockFeedProvider, useKnockFeed, } from "@knocklabs/expo"; const App = ({ authenticatedUser }) => ( ); const MyFeedComponent = () => { const { useFeedStore } = useKnockFeed(); const items = useFeedStore((state) => state.items); return ( {items.map((item) => ( ))} ); }; ``` ### `useAuthenticatedKnockClient` Creates an authenticated Knock client. **Returns**: `Knock` instance, authenticated against the user **Example**: ```jsx import { useAuthenticatedKnockClient } from "@knocklabs/expo"; const MyComponent = () => { const knock = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: user.id }, user.knockToken, ); return null; }; ``` ### `useKnockAuthState` Subscribes to a `Knock` client's authentication state, re-rendering when the authenticated user changes. Use it to react to the [`enabled` prop on `KnockProvider`](#knockprovider) flipping between states. **Returns**: `KnockAuthState` - **status** (`'authenticated' | 'unauthenticated'`) - Whether a user is authenticated to the client. - **userId** (`string | undefined | null`) - The ID of the authenticated user, or `undefined` when no user is authenticated. - **userToken** (`string | undefined`) - The user token in use for the authenticated user, when one was provided. **Example**: ```jsx import { useKnock, useKnockAuthState } from "@knocklabs/expo"; const MyComponent = () => { const knock = useKnock(); const { status, userId } = useKnockAuthState(knock); return null; }; ``` ### `useNotifications` Creates a `Feed` instance for the provided `Knock` client which creates a stateful, real-time connection to Knock to build in-app experiences. **Returns**: `Feed` instance **Example**: ```js import { useAuthenticatedKnockClient, useNotifications, useNotificationStore, } from "@knocklabs/expo"; const MyComponent = () => { const knock = useAuthenticatedKnockClient( process.env.KNOCK_PUBLIC_API_KEY, { id: user.id }, user.knockToken, ); const notificationFeed = useNotifications( knock, process.env.KNOCK_FEED_CHANNEL_ID, ); const { metadata } = useNotificationStore(notificationFeed); useEffect(() => { notificationFeed.fetch(); }, [notificationFeed]); return ( Total unread: {metadata.unread_count} ); }; ``` ### `useTranslations` Exposed under `KnockI18nProvider` child components. **Returns**: - **locale** (`string`) - The current locale code (defaults to `en`). - **t** (`(key: string) => string`) - A helper function to get the value of a translation from the current `Translations`. ### `useExpoPushNotifications` The `KnockExpoPushNotificationProvider` exposes a `useExpoPushNotifications` hook for all child components, enabling them to interact with push notification functionalities and state. **Returns**: `KnockExpoPushNotificationContextType` - **expoPushToken** (`string | null`) - The Expo push token for the current device. - **registerForPushNotifications** (`() => Promise`) - A function to initiate the push notification registration process. - **registerPushTokenToChannel** (`(token: string, channelId: string) => Promise`) - Registers the device's push token with a specific channel in the Knock backend. - **unregisterPushTokenFromChannel** (`(token: string, channelId: string) => Promise`) - Removes the device's push token from a specific channel in the Knock backend. - **onNotificationReceived** (`(handler: (notification: Notifications.Notification) => void) => void`) - Sets a custom handler for notifications received while the app is in the foreground. - **onNotificationTapped** (`(handler: (response: Notifications.NotificationResponse) => void) => void`) - Sets a custom handler for user interactions with notifications. **Example**: ```jsx import React, { useEffect } from "react"; import { View, Text } from "react-native"; import { KnockExpoPushNotificationProvider, useExpoPushNotifications, } from "@knocklabs/expo"; const App = () => ( ); const MyComponent = () => { const { expoPushToken, onNotificationReceived, onNotificationTapped } = useExpoPushNotifications(); useEffect(() => { onNotificationReceived((notification) => { console.log("Notification Received: ", notification); }); onNotificationTapped((response) => { console.log("Notification Tapped: ", response); }); }, []); return ( Expo Push Token: {expoPushToken} ); }; ``` ## Types ### UserIdentificationOptions User identification data to pass through to the `authenticate` method. - **id** (`string*`) - The `id` for the user. - **[attribute_name]** (`any`) - Attribute to attach to the user on identification. ### `ChannelData` - **channel_id** (`string*`) - The unique identifier for the channel. - **data** (`any`) - Channel data for a given channel type. ### `I18nContent` Used to set translations available in the child components exposed under `KnockFeedProvider` and `KnockSlackProvider`. Used in the `useTranslations` hook. **Note:** `locale` must be a valid locale code. ```typescript interface Translations { readonly emptyFeedTitle: string; readonly emptyFeedBody: string; readonly notifications: string; readonly poweredBy: string; readonly markAllAsRead: string; readonly archiveNotification: string; readonly all: string; readonly unread: string; readonly read: string; readonly unseen: string; readonly slackConnectChannel: string; readonly slackChannelId: string; readonly slackConnecting: string; readonly slackDisconnecting: string; readonly slackConnect: string; readonly slackConnected: string; readonly slackConnectContainerDescription: string; readonly slackSearchbarDisconnected: string; readonly slackSearchbarNoChannelsConnected: string; readonly slackSearchbarNoChannelsFound: string; readonly slackSearchbarChannelsError: string; readonly slackSearchChannels: string; readonly slackConnectionErrorOccurred: string; readonly slackConnectionErrorExists: string; readonly slackChannelAlreadyConnected: string; readonly slackError: string; readonly slackDisconnect: string; readonly slackChannelSetError: string; readonly slackAccessTokenNotSet: string; readonly slackReconnect: string; } interface I18nContent { readonly translations: Partial; readonly locale: string; } ``` --- # API reference ## Overview ## Client libraries ## OpenAPI ## API keys ## Authentication ## Rate limits ## Batch rate limits ## Idempotent requests ## Data retention ## Bulk endpoints ## Trigger data filtering ## Pagination ## Errors ## Common error codes # Workflows ## Overview ## Trigger workflow ## Cancel workflow # Workflow runs ## Overview ## List workflow recipient runs ## Get a workflow recipient run ## Object definitions ## WorkflowRecipientRun ## WorkflowRecipientRunDetail ## WorkflowRecipientRunEvent # Messages ## Overview ## Get message ## Get message content ## List messages ## List events ## List delivery logs ## List activities ## Mark message as seen ## Mark message as unseen ## Mark message as read ## Mark message as unread ## Mark message as interacted ## Archive message ## Unarchive message ## Batch operations ## Overview ## Batch get message contents ## Mark messages as seen ## Mark messages as unseen ## Mark messages as read ## Mark messages as unread ## Mark messages as interacted ## Mark messages as archived ## Mark messages as unarchived ## Object definitions ## BatchMessagesStatusRequest ## Object definitions ## Message ## Activity ## MessageDeliveryLog ## MessageEvent ## ListMessagesResponse ## MessageContents ## MessageInAppFeedButtonSetBlock ## MessageInAppFeedContentBlock # Channels ## Overview ## Bulk ## Overview ## Bulk update message statuses for channel # Users ## Overview ## Get user ## List users ## Identify user ## Merge users ## Delete user ## List user messages ## List user schedules ## List user subscriptions ## List user preference sets ## Get user preference set ## Update user preference set ## Delete user preference set ## Get channel data ## Set channel data ## Unset channel data ## Feeds ## Overview ## List feed items ## Get feed settings ## Guides ## Overview ## List guides ## Mark guide as seen ## Mark guide as interacted ## Mark guide as archived ## Mark guide as unarchived ## Reset guide engagement ## Object definitions ## GuideArchivedRequest ## GuideActionResponse ## GuideInteractedRequest ## GuideSeenRequest ## Bulk operations ## Overview ## Bulk identify users ## Bulk set preferences ## Bulk delete users ## Preference center ## Overview ## Get preference center config ## Create preference center signed URL ## Object definitions ## PreferenceCenterBrandingConfig ## Object definitions ## User ## IdentifyUserRequest ## InlineIdentifyUserRequest ## PreferenceSetCommercialSubscribedSetting ## ListSchedulesResponse ## ListSubscriptionsResponse # Objects ## Overview ## Set an object ## Get an object ## List objects in a collection ## Delete an object ## List preference sets ## Get object preference set ## Update a preference set ## Delete object preference set ## List object schedules ## List messages ## Get channel data ## Set channel data ## Unset channel data ## List subscriptions ## Add subscriptions ## Delete subscriptions ## Bulk operations ## Overview ## Bulk set objects ## Bulk add subscriptions ## Bulk delete objects ## Bulk delete subscriptions ## Object definitions ## Object ## InlineIdentifyObjectRequest # Tenants ## Overview ## Delete a tenant ## Get a tenant ## Set a tenant ## List tenants ## Bulk operations ## Overview ## Bulk delete tenants ## Bulk set tenants ## Object definitions ## Tenant ## TenantRequest ## InlineTenantRequest # Schedules ## Overview ## Create schedules ## List schedules ## Update schedules ## Delete schedules ## Bulk schedules ## Overview ## Create schedules in bulk ## Object definitions ## Schedule ## ScheduleRepeatRule # Audiences ## Overview ## Add members ## List members ## Remove members ## Object definitions ## AudienceMember ## AudienceMemberRequest # Bulk operations ## Overview ## Get bulk operation ## Object definitions ## BulkOperation # Providers ## Overview ## Slack ## Overview ## List channels ## Check auth ## Revoke access ## Microsoft Teams ## Overview ## List channels ## List teams ## Check auth ## Revoke access # Integrations ## Overview ## Census ## Overview ## Process a Census RPC request ## Hightouch ## Overview ## Process a Hightouch RPC request # Recipients ## Overview ## Subscriptions ## Overview ## Object definitions ## Subscription ## Preferences ## Overview ## Object definitions ## PreferenceSet ## PreferenceSetRequest ## InlinePreferenceSetRequest ## PreferenceSetChannelTypes ## PreferenceSetChannelTypeSetting ## PreferenceSetChannelSetting ## Channel data ## Overview ## Object definitions ## ChannelData ## ChannelDataRequest ## PushChannelDataTokensOnly ## PushChannelDataDevicesOnly ## SlackChannelData ## AWSSNSPushChannelDataTargetARNsOnly ## AWSSNSPushChannelDataDevicesOnly ## MsTeamsChannelData ## DiscordChannelData ## OneSignalChannelDataPlayerIdsOnly ## InlineChannelDataRequest ## Object definitions ## Recipient ## RecipientRequest ## RecipientReference # Shared ## Overview ## Object definitions ## Condition ## PageInfo --- # Getting started ## Overview ## Install the Knock CLI ## Authentication ## Global flags ## Configuring your project ## Directory structure # Authentication ## Login ## Logout # Managing resources ## Initialize a new project ## Pull all resources ## Push all resources # Environments ## Overview ## List environments # Channels ## Overview ## List channels # Branches ## Overview ## List branches ## Create branches ## Delete branches ## Switch to a branch ## Exit a branch ## Rebase branch ## Merge branch # Workflows ## Overview ## File structure ## List workflows ## Get workflow ## Create a new workflow ## Pull workflows ## Push workflows ## Run workflow ## Validate workflow ## Activate workflow ## Open workflow ## Generate types # Email layouts ## Overview ## File structure ## List email layouts ## Get email layout ## Create a new email layout ## Pull email layouts ## Push email layouts ## Validate email layout ## Open email layout # Translations ## Overview ## File structure ## List translations ## Get translations ## Pull translations ## Push translations ## Validate translations # Partials ## Overview ## File structure ## List partials ## Get partial ## Create a new partial ## Pull partials ## Push partials ## Validate partial ## Open partial # Schemas ## Overview ## File structure ## Pull schemas ## Push schemas # Commits ## Overview ## List commits ## Get commits ## Commit changes ## Promote changes # Guides ## Overview ## File structure ## List guides ## Get guide ## Create a new guide ## Pull guides ## Push guides ## Validate guide ## Activate guide ## Open guide ## Generate types # Message types ## Overview ## File structure ## List message types ## Get message type ## Create a new message type ## Pull message types ## Push message types ## Validate message type ## Open message type # Audiences ## Overview ## File structure ## List audiences ## Get audience ## Create a new audience ## Pull audiences ## Push audiences ## Validate audience ## Open audience ## Archive audience --- # API reference ## Overview ## Client libraries ## OpenAPI ## Authentication ## Errors ## Postman # Environments ## Overview ## List environments ## Get an environment ## Object definitions ## Environment # Channels ## Overview ## List channels ## Get a channel ## Object definitions ## Channel ## ChannelEnvironmentSettings ## ChatChannelSettings ## EmailChannelSettings ## PushChannelSettings ## SmsChannelSettings ## InAppFeedChannelSettings # Channel groups ## Overview ## List channel groups ## Get a channel group ## Upsert a channel group ## Delete a channel group ## Object definitions ## ChannelGroup ## ChannelGroupRule # Workflows ## Overview ## List workflows ## Get a workflow ## Upsert a workflow ## Activate a workflow ## Run a workflow ## Validate a workflow ## Steps ## Overview ## Preview a workflow template ## Object definitions ## Condition ## ConditionGroup ## Duration ## SendWindow ## Workflow ## WorkflowStep ## WorkflowBatchStep ## WorkflowBranchStep ## WorkflowChatStep ## WorkflowInAppFeedStep ## WorkflowInAppGuideStep ## WorkflowEmailStep ## WorkflowPushStep ## WorkflowSmsStep ## WorkflowWebhookStep ## WorkflowDelayStep ## WorkflowFetchStep ## WorkflowThrottleStep ## WorkflowTriggerWorkflowStep ## WorkflowRandomCohortStep ## WorkflowRandomCohortStepBranch ## WorkflowUpdateDataStep ## WorkflowUpdateUserStep ## WorkflowUpdateTenantStep ## WorkflowUpdateObjectStep ## WorkflowAIAgentStep ## InlineIdentifyUserRequest ## WorkflowRequest ## ConditionGroupAllMatch # Templates ## Overview ## Preview a template ## Object definitions ## ChatTemplate ## EmailTemplate ## PushTemplate ## SmsTemplate ## InAppFeedTemplate ## RequestTemplate ## WebhookTemplate # Broadcasts ## Overview ## Send a broadcast ## Validate a broadcast ## List broadcasts ## Get a broadcast ## Upsert a broadcast ## Cancel a scheduled broadcast ## Object definitions ## BroadcastRequest ## Broadcast # Email layouts ## Overview ## List email layouts ## Get email layout ## Upsert email layout ## Validate email layout ## Preview an email layout ## Object definitions ## EmailLayout ## EmailLayoutRequest ## BrandingOverrides # Audiences ## Overview ## Archive an audience ## Get an audience ## Upsert an audience ## Validate an audience ## List audiences ## Object definitions ## Audience ## DynamicAudience ## StaticAudience ## AudienceCondition ## AudienceRequest # Goals ## Overview ## List goals ## Archive a goal ## Get a goal ## Upsert a goal ## Validate a goal ## Clone a goal ## Object definitions ## GoalCondition ## GoalRequest ## Goal # Partials ## Overview ## List partials ## Get a partial ## Upsert a partial ## Validate a partial ## Preview a partial ## Object definitions ## Partial ## PartialRequest # Assets ## Overview ## List assets ## Object definitions ## Asset # Guides ## Overview ## List guides ## Get a guide ## Archive a guide ## Upsert a guide ## Validate a guide ## Activate a guide ## Object definitions ## Guide ## GuideStep ## GuideActivationUrlPattern ## GuideRequest # Message types ## Overview ## List message types ## Get message type ## Upsert message type ## Validate message type ## Object definitions ## MessageType ## MessageTypeVariant ## MessageTypeRequest # Preference center ## Overview ## Get preference center configuration ## Upsert preference center configuration ## Reset preference center configuration to default # Preference categories ## Overview ## List preference categories ## Delete a preference category ## Upsert a preference category ## Object definitions ## PreferenceCategory # Item schemas ## Overview ## List item schemas ## Get an item schema ## Upsert item schema properties ## Validate an item schema's configuration ## Object definitions ## ItemSchema # Tags ## Overview ## Delete a tag ## Upsert a tag ## List tags ## Object definitions ## Tag # Commits ## Overview ## List commits ## Commit all changes ## Get a commit ## Promote all changes ## Promote one commit ## Object definitions ## Commit # Translations ## Overview ## List translations ## Get translation ## Upsert translation ## Validate translation ## Object definitions ## Translation ## TranslationRequest # Variables ## Overview ## List variables ## Get a variable ## Object definitions ## Variable # Branches ## Overview ## List branches ## Delete a branch ## Get a branch ## Create a branch ## Rebase a branch ## Object definitions ## Branch # Members ## Overview ## List members ## Get a member ## Remove a member ## Object definitions ## Member ## MemberUser # Data sources ## Overview ## List sources ## Get a source ## Upsert a source ## Get source status ## Rehearse a source event ## List source events ## List source logs ## List source providers ## Get a source provider ## Object definitions ## Source ## SourceRequest ## SourcePreprocessScript ## SourceEnvironmentSettings ## SourceEvent ## SourceEventsResponse ## SourceEventActionMapping ## SourceLog ## SourceLogAction ## SourceLogsResponse ## SourceProviderResponse ## SourceProvidersResponse ## SourceRehearseRequest ## SourceRehearseResponse ## SourceStatusResponse ## SourcesResponse # API keys ## Overview ## Exchange for API key # Billing ## Overview ## Get billing summary ## Object definitions ## BillingSummary # Authentication ## Overview ## Verify scope # Shared ## Overview ## Object definitions ## PageInfo ## MessageTypeBooleanField ## MessageTypeButtonField ## MessageTypeImageField ## MessageTypeUrlField ## MessageTypeJsonField ## MessageTypeMarkdownField ## MessageTypeMultiSelectField ## MessageTypeSelectField ## MessageTypeTextField ## MessageTypeTextareaField ## MessageTypeColorField ## MessageTypeNumberField ## MessageTypeListField ## RecipientReference ## GoalAttachment --- # Overview Learn how to use the Knock developer tools to build integrations and automate workflows. --- title: Developer tools description: Learn how to use the Knock developer tools to build integrations and automate workflows. tags: [ "developer tools", "api keys", "service tokens", "knock cli", "management api", ] section: Developer tools --- # API keys Learn more about API keys in Knock and what they're used for. --- title: API keys description: Learn more about API keys in Knock and what they're used for. section: Developer tools --- In Knock, all requests to the [Knock API](/api-reference) are issued using an API key. Your API keys are [environment](/concepts/environments) specific and allow Knock to tie a request on the API back to an isolated Knock environment. ## Finding your API keys You can find your **environment-specific API keys** under **Platform** > **API keys** in the left-hand side bar. Remember: each environment has its own unique set of API keys. ## Secret vs public API keys Each Knock environment can have any number of API keys. There are two types of keys you can create: public keys and secret keys. You can uniquely identify these keys as they start with `pk_` for a public key, vs `sk_` for a secret key. - **Public keys.** Public keys are only meant to identify your account with Knock. They aren't secret, and can safely be made public in any of your client-side code. - **Secret keys.** Secret keys can perform any API request to Knock and should be kept secure and private. Be sure to prevent secret keys from being made publicly accessible, such as in client-side code, GitHub, unsecured S3 buckets, and so forth. ## Creating API keys You can create any number of API keys per environment in the Knock dashboard. To create a new API key: 1. Navigate to **Platform** > **API keys** in your dashboard sidebar. 2. Click "Create API key." 3. Choose the key type (secret or public). 4. Optionally, provide a description to help identify the key's purpose. 5. Click "Create" to generate the key. Creating multiple API keys is useful when you need to: - Provide different keys to different services or applications. - Rotate keys without downtime by creating a new key before revoking the old one. - Track usage across different parts of your infrastructure. ## Revoking API keys You can revoke any API key at any time. To revoke an API key: 1. Navigate to **Platform** > **API keys** in your dashboard sidebar. 2. Find the key you want to revoke. 3. Select the "..." menu next to the key. 4. Click "Revoke API key." Revoking a key immediately invalidates it. Any requests made with a revoked key will return a 401 error. This action cannot be undone. ## Frequently asked questions You can create any number of API keys per environment. There is no limit on the number of secret or public keys you can have active at once. Currently, it's not possible to reduce the scope of an API key and limit it to a particular set of resources. Please contact our [support team if you need this feature](mailto:support@knock.app). # Service tokens Learn more about service tokens on your Knock account and how they authenticate the Management API, CLI, and MCP server. --- title: Service tokens description: Learn more about service tokens on your Knock account and how they authenticate the Management API, CLI, and MCP server. section: Developer tools --- A Knock account service token authenticates requests to the Knock [Management API](/developer-tools/management-api), [CLI](/developer-tools/knock-cli), and [MCP server](/ai/mcp-server) for resources under your Knock account. Service tokens always start with `knock_st_` and are different from your Knock API keys as they authenticate requests to the Knock Management API **only**. only account owners or admins have the privilege to generate (or revoke) service tokens. Service tokens will inherit the privilege of the owner or the admin that creates the service token, and therefore have full access to the management API. } /> ## Generating a new service token To use the Management API, CLI, or MCP server, you will first need to generate a service token and use it as a means of authentication. To generate a service token, from the dashboard go to "Settings," select the "Service tokens" tab, and click the "+ New token" button. Then, provide a name for the token and click "generate" to view and save your newly generated service token. Note: once generated,{" "} you cannot see a service token again from the Knock dashboard, {" "} so be sure to copy it to a secure location. } /> ## Revoking a service token Service tokens can be revoked under the three-dot menu and by clicking on "Delete token." Deleting a token will **immediately** revoke its ability to be used against the Knock Management API, CLI, and MCP server. ## Frequently asked questions No, a service token can only be used against the Knock Management API, CLI, and MCP server, not the Knock API. Yes. Pass the token as a bearer credential in your MCP client config. See [authenticate with a service token](/ai/mcp-server#authenticate-with-a-service-token). Changes made via the Management API will appear as audit logs, similar to changes made manually in the dashboard, but attributing the service token used to make the change as the author. In addition, internally we audit requests and tie them back to a corresponding service token. If you need further help understanding which request originated from a service token, please [contact our support team](mailto:support@knock.app). # Knock CLI Learn how to use the Knock CLI to build, test, and manage your Knock notification system from the terminal. --- title: Knock CLI description: Learn how to use the Knock CLI to build, test, and manage your Knock notification system from the terminal. section: Developer tools --- The Knock CLI helps you build, test, and manage your Knock notification system, right from the terminal. You can use the CLI to: - Pull Knock workflows down to your local machine and work with your notification logic and templates in your code editor. - [Integrate Knock into your CI/CD pipeline](/tutorials/integrating-into-cicd) and automatically promote changes between Knock environments on release. - Map your translation files into Knock to localize your notifications. ## Installing the CLI You can install the Knock CLI using `npm`, a node package manager, with the following command: ```bash title="Installing the Knock CLI" npm install -g @knocklabs/cli ``` Once the CLI is installed, you can call it by using the `knock` command in your terminal. The CLI is also available via Homebrew for macOS. See [the installation documentation](/cli/overview/installation). ## Authenticating the CLI ### Using your Knock account You can authenticate your Knock account against the CLI by running `knock login`. This will open a browser window where you can sign in to your Knock account and authorize the CLI to access your account. Once authenticated, you can verify it works by running `knock whoami`. If your account is valid and configured properly, you'll receive a 200 response that shows the account name and your user ID. Using your Knock account against the CLI will inherit the permissions of the user that is logged in on the account you authorized. } /> If you need to switch between accounts, you can run `knock logout` to log out of your current account and log in to a different one. ### Using a service token If you need to authenticate in a remote environment, or want complete control, you can generate a [service token](/developer-tools/service-tokens) in the Knock dashboard. Once you have generated a service token, you can verify it works by running `knock whoami --service-token=YOUR_SERVICE_TOKEN`. If your token is valid and configured properly, you'll receive a 200 response that shows the account name and the service token name. ## Working with a knock.json file You can configure the Knock CLI for your project by creating a `knock.json` file or by using the `knock init` command to generate one for you. The `knock.json` file is a project-level configuration file that tells the Knock CLI where to find your Knock resources. ```json title="Example knock.json file" { "knockDir": "./knock" } ``` Once set, the Knock CLI will use the `knockDir` property for all push and pull commands. If you don't specify a `knockDir` property, or if the property is not set, the Knock CLI will use the current working directory as the default, or you will have to pass in the `--knock-dir` or `--{resource-type}-dir` flags for each command. ## CLI reference You can find a complete reference for all of the commands and flags available on the [CLI in our comprehensive reference](/cli/overview). ## Using the CLI with AI coding agents The `knock-cli` skill packages the CLI docs and patterns into a set of rules that AI agents—such as Cursor and Claude—can use to pull, push, and manage Knock resources directly from your editor. Install the skill to enable your agent to work with workflows, templates, guides, and partials without needing manual prompting: ```bash npx skills add knocklabs/skills --skill knock-cli ``` Learn more on the [CLI](/ai/cli) and [skills](/ai/skills) pages. # SDKs Libraries and tools for interacting with your Knock integration. --- title: Knock SDKs description: Libraries and tools for interacting with your Knock integration. tags: ["cancellations", "cancellation_key", "cancel", "batch", "remove"] section: Send notifications --- ## Server-side SDKs Knock's server-side helper libraries (also known as server-side SDKs) reduce the amount of work required to use Knock's REST APIs, starting with reducing the boilerplate code you have to write when integrating Knock in your server-side application. ## Client-side SDKs Knock's client-side libraries (also known as client-side SDKs) reduce the amount of work required to use Knock within your client-side applications and provide a foundation for [building in-app notification experiences](/in-app-ui/overview) on top of. ### Web SDKs ### Mobile SDKs ## Management API SDKs Knock also provides a Node.js SDK for the [Management API](/developer-tools/management-api), which you can use to programmatically manage your Knock dashboard resources. # Management API Learn more about the Knock management API for programmatically interacting with your Knock dashboard resources. --- title: Management API description: Learn more about the Knock management API for programmatically interacting with your Knock dashboard resources. section: Developer tools --- The Knock management API provides you with a programmatic way to interact with the resources you create and manage in your Knock dashboard, including workflows, templates, and translations. It's separate from the [Knock API](/api-reference) and only provides access to a limited subset of resources. You can use the Knock management API to: - Create, update, and manage your Knock workflows and the notification templates within those workflows. - Create, update and manage your [email layouts](/integrations/email/layouts). - Create and manage the [translations](/template-editor/translations) used by your notification templates. - Create, update, and manage your [partials](/template-editor/partials). - Commit and promote changes between your Knock environments. [View management API reference](/mapi) ## Interacting with the management API Knock provides a Node.js SDK for the management API. You can also use the [CLI](/developer-tools/knock-cli) for easily interacting with the resources in your Knock account. ## Use cases ### Previewing templates Using the management API, you can generate previews of the templates within your workflows. One example use case is providing a way for your users to customize the content of notifications sent through Knock. To preview a template, you [use the preview template endpoint exposed by the management API](/mapi-reference/workflows/steps/preview_template). This API endpoint accepts a workflow key, and a [step reference](/mapi-reference/workflows/schemas/workflow_step) (`ref`) for the step that contains the template you want to preview. Using the endpoint, you pass through details about the `recipient`, `actor`, `tenant`, and any other data variables that are used within the template. The endpoint will return a JSON object with fully rendered template generated using the data you provided. It will be the same as previewing the template in the Knock dashboard. ```bash title="An example request to preview a template" curl -X POST https://control.knock.app/v1/workflows/my-workflow/steps/my-step/preview_template \ -H "Authorization: Bearer $SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"recipient": "chris", "data": {"project_name": "My Project"}}' ```
```json title="An example response from the preview template endpoint" { "result": "success", "content_type": "email", "template": { "html_content": "

Hello world!

", "text_content": "Hello world!", "subject": "Hello, world!" } } ``` ### Committing changes You can commit and promote changes between your Knock environments using the management API. This includes [empty commits](/version-control/commits#empty-commits), which let you publish a versioned resource without changing its content. #### Targeted empty commit Use `PUT /v1/commits` with `allow_empty=true` to create an empty commit for a single resource that has already been published and has no unpublished changes. You must specify both `resource_type` and `resource_id`. ```bash title="An example request to create an empty commit" curl -X PUT "https://control.knock.app/v1/commits?environment=development&allow_empty=true&resource_type=workflow&resource_id=my-workflow-key&commit_message=Empty%20touch" \ -H "Authorization: Bearer $SERVICE_TOKEN" ```
```json title="An example response from the commit endpoint" { "result": "success" } ``` See the [commit all changes endpoint](/mapi-reference/commits/commit_all) for the full list of query parameters. #### Upsert with an empty commit You can also create an empty commit when upserting a resource by passing `commit=true` and `allow_empty=true` as query parameters on the resource PUT route. This is useful when you re-upsert an unchanged resource payload and want to create a new commit log entry. ```bash title="An example request to upsert and create an empty commit" curl -X PUT "https://control.knock.app/v1/workflows/my-workflow?environment=development&commit=true&allow_empty=true&commit_message=Empty%20touch" \ -H "Authorization: Bearer $SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d @workflow.json ``` Query parameters: - **`allow_empty`** — Optional boolean. On `PUT /v1/commits`, requires a single `resource_type` and `resource_id`. On versioned resource PUT routes, use alongside `commit=true`. - **`commit_message`** — Optional message stored in commit audit metadata. Your account may require a commit message. This pattern applies to PUT routes for workflows, partials, email layouts, guides, audiences, message types, and translations. # API logs Learn more about viewing and debugging API request logs in Knock. --- title: API Logs description: Learn more about viewing and debugging API request logs in Knock. section: Developer tools --- Knock automatically captures and stores all of the API requests you make to the Knock API, and makes these requests accessible under the **Observability** > **Logs** section of the Knock dashboard. Knock does not store response bodies for the /feeds endpoint in our logs. } /> See the{" "} data retention docs for more details on how Knock enforces this policy. } /> ## Filtering API logs You can filter API logs in your Knock account using the following filters: - **Request ID**: filter for a particular log returned by the `X-Request-ID` header. - **Status**: filter for failed and succeeded requests. - **Endpoint type**: filter for requests to particular sets of endpoints. ## Log truncation Knock truncates logs with long binaries, lists, maps, and strings to prevent the logs from becoming too large. The truncation occurs at the top level of the JSON object and is noted by the presence of either the `__knock_truncated__` key in the JSON object (indicating that some of the keys were dropped) or as a `[TRUNCATED]` value under a given key. ```json title="Example of a truncated log" // Original JSON object with many keys { "foo": "bar", "biz": "this is a very long string value", "baz": null, ... } // Truncated JSON object { // indicates that some of the key-value pairs were dropped "__knock_truncated__": true, "foo": "bar", "biz": "[TRUNCATED]" } ``` ## Frequently asked questions Knock will only keep 30 days of API logs available to be queried and displayed at any time. --- # Knock and Postman A Postman collection for the Knock API. --- title: Using Knock with Postman description: A Postman collection for the Knock API. tags: ["postman", "api", "getting started"] section: Getting started --- Our Postman collection is a good way to get familiar with the Knock API. Here's how to get started. This page walks through forking the Knock collection into your Postman workspace and creating Postman environments that map to your environments in Knock. ## For the Knock Postman collection 1. Install [Postman](https://www.postman.com/downloads/) if you don't have it already. 2. [Fork the Knock API Postman collection](https://god.gw.postman.com/run-collection/10721026-cd261902-9249-4714-b7d3-896c15987fa5?action=collection%2Ffork&collection-url=entityId%3D10721026-cd261902-9249-4714-b7d3-896c15987fa5%26entityType%3Dcollection%26workspaceId%3De0ad9a88-e3dd-462b-8c44-695c9c10b8e5#?env%5BKnock%20environment%20template%5D=W3sia2V5IjoiYmFzZV91cmwiLCJ2YWx1ZSI6Imh0dHBzOi8vYXBpLmtub2NrLmFwcCIsImVuYWJsZWQiOnRydWV9LHsia2V5Ijoic2VjcmV0X2tleSIsInZhbHVlIjoic2tfdGVzdF94eHh4IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJwdWJsaWNfa2V5IiwidmFsdWUiOiJwa190ZXN0X3h4eHgiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6IndvcmtmbG93X2tleSIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJyZWNpcGllbnRfdXNlcl9pZCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJhY3Rvcl91c2VyX2lkIiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImZlZWRfaWQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoidXNlcl9pZCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJ1c2VyX25hbWUiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoidXNlcl9lbWFpbCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJ1c2VyX2F2YXRhcl91cmwiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicHJlZmVyZW5jZV9zZXRfaWQiLCJ2YWx1ZSI6ImRlZmF1bHQiLCJlbmFibGVkIjp0cnVlfV0=). We recommend forking the collection (as opposed to creating a copy) so that you can pull changes we make to the source collection. - We provide a separate [Management API Postman collection](https://www.postman.com/knock-labs/workspace/knock-public-workspace/collection/15616728-9ed6000c-13bc-43f5-a2cf-db6daea256bd?action=share&creator=15616728&active-environment=15616728-6df39335-c6f9-4c9d-99d5-73e3c7ffe524). You can use use this to test our [Management API](https://docs.knock.app/mapi#overview). ## Configure your Knock environments in Postman We recommend creating a Postman environment for each of the environments you're using in Knock. This way you can store environment-level variables (such as API keys) and easily switch between environments without having to update your endpoint parameters. 1. Navigate to the "Environments" section in Postman. You should see an environment named "Knock environment template." 2. Duplicate the template so you have one environment in Postman for every environment you use in Knock. Once your environments are in place in Postman, grab the secret key from their corresponding environment in Knock and add it to the environment's secret key variable in Postman. You're all set to send requests to the Knock API from your Postman workspace. # Security Learn about our security policies. --- title: Security at Knock description: Learn about our security policies. section: Getting started tags: ["compliance", "gdpr", "soc2", "soc 2", "hipaa", "hippa", "baa", "ccpa"] --- Knock was built with security and privacy in mind from day one. Below you can learn more about our security credentials, our internal security practices, and how to disclose security issues to our team. If you're looking to learn more about how we think about data privacy at Knock, you can read our privacy policy. ## Our security posture Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We perform regular penetration tests. If you'd like copies of our SOC 2 report or penetration test report, please let us know at [security@knock.app](mailto:security@knock.app). You can learn more about our GDPR certification in our privacy policy.
Here's a little more about our security practices at Knock: - We implement best practices around least privilege, with limited access to production data for our employees. - Access to all systems is enforced by 2FA for our employees. - All of our code changes are signed off by at least one other person, and tested in a staging environment before being deployed. - We retain server logs for a maximum of 1 year, after which time they are permanently deleted. - We have regular third party penetration tests and infrastructure audits. - All data is encrypted at rest, and we use TLS 1.2 for all cross-service communication. ## More information and responsible disclosure We're always improving the security of our product. If you'd like to learn more about our data protection processes, you can email us at [security@knock.app](mailto:security@knock.app). If you are a security researcher and would like to disclose an issue, contact [security@knock.app](mailto:security@knock.app). We are strong advocates for responsible disclosure by independent security researchers. We believe the best way to protect current and future customers is to encourage researchers to come forward with issues and reply promptly. Our promise to you is: - We will read and respond to all reported vulnerabilities. - We will not take any harmful action (including legal action) against researchers who act ethically and in good faith. - We will highlight the contributions of security researchers who make significant reports. In return we ask: - That you do not attempt to access, modify, or delete data belonging to Knock customers. - That you report issues promptly once discovered. - That you do not attempt denial of service against the Knock service.
# Type safety Learn more about issuing type safe workflow triggers using the Knock CLI and TypeScript SDK. --- title: Type safety description: Learn more about issuing type safe workflow triggers using the Knock CLI and TypeScript SDK. section: Developer tools --- Using Knock you can get type safety between your codebase and the workflows that you trigger on Knock. By defining trigger data schemas for your workflows and generating types from them, you can catch integration errors at compile time and ensure your workflow triggers always include the correct data. ## Setting up a workflow trigger schema Before you can generate types for your workflow triggers, you need to define a JSON schema that describes the expected structure of the data passed to your workflow, which we call a [trigger data schema](/designing-workflows/validating-trigger-data). This schema serves as the source of truth for type generation. You can set up trigger data validation in two ways: **Via the Workflow Builder:** 1. Navigate to the "Trigger step" in the workflow builder 2. Under the "API params" section, click "Edit schema" 3. Supply a valid JSON schema that describes your trigger data 4. Commit your changes for the schema to take effect **Via the Management API or CLI:** - Use the `trigger_data_json_schema` field when creating or updating a workflow Here's an example schema for a comment notification workflow: ```json title="Comment workflow trigger schema" { "type": "object", "properties": { "comment_id": { "type": "string", "description": "The unique identifier for the comment" }, "comment_text": { "type": "string", "description": "The text content of the comment" }, "document_id": { "type": "string", "description": "The ID of the document being commented on" }, "document_name": { "type": "string", "description": "The name of the document being commented on" }, "priority": { "type": "string", "enum": ["low", "medium", "high"], "description": "The priority level of the notification" }, "metadata": { "type": "object", "properties": { "source": { "type": "string" }, "version": { "type": "number" } }, "required": ["source"] } }, "required": ["comment_id", "comment_text", "document_id", "document_name"] } ``` ## Generating types for your workflow triggers Once you have trigger data schemas defined for your workflows, you can use the [Knock CLI](/cli/overview) to generate type definitions for TypeScript, Python, Ruby, and Go. ### Basic usage ```bash title="Generate TypeScript types" knock workflow generate-types --output-file=./knock-workflows.ts ``` This command will: 1. Fetch all workflows from your development environment that have trigger data schemas 2. Generate type definitions based on those schemas 3. Output the types to the specified file with the language inferred from the file extension ### Language support The target language is automatically determined by the file extension you specify: - `.ts` - Generates TypeScript interface definitions - `.py` - Generates Python class definitions using Pydantic - `.rb` - Generates Ruby class definitions using dry-types - `.go` - Generates Go struct definitions ### Command options - **--environment** (`string`) - The environment to fetch workflows from. Defaults to development. - **--output-file** (`string`) - Specifies the file to generate types into. The language is inferred from the file suffix. ### Examples ```bash title="Generate TypeScript types" knock workflow generate-types --output-file=./src/types/knock-workflows.ts ``` ```bash title="Generate Python types" knock workflow generate-types --output-file=./src/types/knock_workflows.py ``` ```bash title="Generate Ruby types" knock workflow generate-types --output-file=./lib/knock_workflows.rb ``` ```bash title="Generate Go types" knock workflow generate-types --output-file=./types/knock_workflows.go ``` ```bash title="Generate types from production environment" knock workflow generate-types \ --environment=production \ --output-file=./types/knock-workflows.ts ``` ## Using the generated types The generated types provide compile-time safety when triggering workflows in your application code. Here's how to use them in each supported language: ### TypeScript Generated TypeScript interfaces can be imported and used with the Knock TypeScript SDK: ```typescript title="Generated TypeScript types" // Generated in knock-workflow-types.ts export interface CommentCreatedData { comment_id: string; comment_text: string; document_id: string; document_name: string; priority: "low" | "medium" | "high"; metadata: { source: string; version?: number; }; } export interface UserInvitedData { user_email: string; workspace_name: string; role: "admin" | "member" | "viewer"; } ``` ```typescript title="Using the generated types" import { Knock } from "@knocklabs/node"; import { CommentCreatedData } from "./knock-workflow-types"; const knock = new Knock({ apiKey: process.env.KNOCK_API_KEY }); // Type-safe workflow trigger const triggerData: CommentCreatedData = { comment_id: "comment_123", comment_text: "Great work on this!", document_id: "doc_456", document_name: "Project Proposal", priority: "medium", metadata: { source: "web_app", version: 1, }, }; await knock.workflows.trigger("comment-created", { recipients: ["user_789"], data: triggerData, // ✅ Type-safe! }); ``` ### Python Generated Python classes use Pydantic for validation: ```python title="Generated Python types" # Generated in knock_workflow_types.py from pydantic import BaseModel from typing import Literal, Optional from enum import Enum class Priority(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" class CommentCreatedMetadata(BaseModel): source: str version: Optional[float] = None class CommentCreatedData(BaseModel): comment_id: str comment_text: str document_id: str document_name: str priority: Priority metadata: CommentCreatedMetadata ``` ```python title="Using the generated types" from knockapi import Knock from knock_workflow_types import CommentCreatedData, Priority, CommentCreatedMetadata client = Knock(api_key="sk_12345") # Type-safe workflow trigger trigger_data = CommentCreatedData( comment_id="comment_123", comment_text="Great work on this!", document_id="doc_456", document_name="Project Proposal", priority=Priority.MEDIUM, metadata=CommentCreatedMetadata( source="web_app", version=1 ) ) client.workflows.trigger( key="comment-created", recipients=["user_789"], data=trigger_data.dict() # ✅ Type-safe! ) ``` ### Ruby Generated Ruby classes use dry-types for type safety: ```ruby title="Generated Ruby types" # Generated in knock_workflow_types.rb require 'dry-types' require 'dry-struct' module KnockWorkflowTypes module Types include Dry.Types() end class CommentCreatedMetadata < Dry::Struct attribute :source, Types::String attribute :version, Types::Float.optional end class CommentCreatedData < Dry::Struct attribute :comment_id, Types::String attribute :comment_text, Types::String attribute :document_id, Types::String attribute :document_name, Types::String attribute :priority, Types::String.enum('low', 'medium', 'high') attribute :metadata, CommentCreatedMetadata end end ``` ```ruby title="Using the generated types" require 'knockapi' require_relative 'knock_workflow_types' knock = Knockapi::Client.new(api_key: "sk_12345") # Type-safe workflow trigger trigger_data = KnockWorkflowTypes::CommentCreatedData.new( comment_id: "comment_123", comment_text: "Great work on this!", document_id: "doc_456", document_name: "Project Proposal", priority: "medium", metadata: KnockWorkflowTypes::CommentCreatedMetadata.new( source: "web_app", version: 1 ) ) knock.workflows.trigger("comment-created", recipients: ["user_789"], data: trigger_data.to_h # ✅ Type-safe! ) ``` ### Go Generated Go structs include JSON tags for serialization: ```go title="Generated Go types" // Generated in knock_workflow_types.go package main import "encoding/json" type Priority string const ( PriorityLow Priority = "low" PriorityMedium Priority = "medium" PriorityHigh Priority = "high" ) type CommentCreatedMetadata struct { Source string `json:"source"` Version *float64 `json:"version,omitempty"` } type CommentCreatedData struct { CommentID string `json:"comment_id"` CommentText string `json:"comment_text"` DocumentID string `json:"document_id"` DocumentName string `json:"document_name"` Priority Priority `json:"priority"` Metadata CommentCreatedMetadata `json:"metadata"` } ``` ```go title="Using the generated types" package main import ( "context" "github.com/knocklabs/knock-go" "github.com/knocklabs/knock-go/option" "github.com/knocklabs/knock-go/shared" ) func main() { client := knock.NewClient(option.WithAPIKey("sk_12345")) // Type-safe workflow trigger triggerData := CommentCreatedData{ CommentID: "comment_123", CommentText: "Great work on this!", DocumentID: "doc_456", DocumentName: "Project Proposal", Priority: PriorityMedium, Metadata: CommentCreatedMetadata{ Source: "web_app", Version: &[]float64{1}[0], }, } // Convert to map for the API call data := map[string]interface{}{} jsonData, _ := json.Marshal(triggerData) json.Unmarshal(jsonData, &data) _, err := client.Workflows.Trigger(context.Background(), "comment-created", knock.WorkflowTriggerParams{ Recipients: knock.F([]knock.RecipientRequestUnionParam{ shared.UnionString("user_789"), }), Data: knock.F(data), // ✅ Type-safe! }) } ``` # Outbound webhooks ## Overview Learn how to use outbound webhooks to get the data you need from Knock into your product. --- title: Outbound Webhooks description: Learn how to use outbound webhooks to get the data you need from Knock into your product. section: Developer tools tags: ["events", "data", "analytics", "webhook configuration"] --- Use outbound webhooks to be notified of events happening within your Knock environment, and respond to those events in realtime in your product. ## An overview of webhooks Knock can send a JSON payload to your backend with data about events that occur within Knock, like a message's status changing from `sent` to `delivered`. You can configure an endpoint and select which events you'd like it to listen to. When that event happens you'll get a POST request to the endpoint you've provided and then can use that data to trigger side-effects in your app. You can learn more about the types of events that [Knock sends webhooks for here](/developer-tools/outbound-webhooks/event-types). ## Quickstart 1. [Create an endpoint in your app to receive webhook requests](#1-create-an-endpoint-to-receive-the-webhook-payload) and respond to requests with a `200` or `204` status code 2. [Create a webhook in the Knock dashboard](#2-create-the-knock-webhook) pointing to your endpoint 3. Start receiving webhook events! ## Payload & headers A webhook payload will always take the following base shape, where the `type` and its associated `data` schema will be determined by the supported [webhook event types](/developer-tools/outbound-webhooks/event-types). The `data` field will include the entity that the event references. Some event types also include an `event_data` field with additional context specific to that event — for example, failure details on a `message.undelivered` event or the clicked URL on a `message.link_clicked` event. For event types that do not include additional context, `event_data` will be `null`. See the [webhook event types](/developer-tools/outbound-webhooks/event-types) page for a full breakdown of which events include `event_data` and what fields each contains. ```json title="A sample event payload" { "__typename": "Event", // The type of event that triggered the webhook "type": "message.undelivered", "created_at": "2026-01-31T17:12:59.958652Z", "data": { // Information about the entity, as determined by the webhook event type. }, // Only present for event types that include additional metadata. Null otherwise. "event_data": { "__typename": "EventData", "failure_reason": "fatal_error", "failure_details": "The message could not be delivered to the provider." } } ``` The request header will also contain the following: - `x-knock-event`: the field and the value found in the `"type"` key of the payload - `x-knock-environment-id`: the environment the webhook belongs to - `x-knock-signature`: the encoded result of a timestamp, payload, and shared secret key so you can verify that the contents of the webhook are from Knock and not a result of a Man in the middle attack. Learn more about [verifying signatures below](/developer-tools/outbound-webhooks/overview#verifying-the-signature). When you create a webhook, it will be for the environment you're currently in. If you'd like to add that webhook to another environment, you'll have to create it again there. What this means is that when you create a webhook in a development environment, it will only be triggered by notifications from workflows in development. } /> ## How to create a webhook ### 1. Create an endpoint to receive the webhook payload In your app, create an endpoint specifically to receive incoming requests from a webhook you'll set up in Knock. This must be an HTTP or HTTPS endpoint on your server with a URL. This is where Knock will send a request with data about events you'll select in the next step. Keep in mind that you can use one webhook endpoint per event type, or you can send multiple types of events to the same endpoint. #### Retries Knock webhooks have built-in retries for `3xx`, `4xx`, and `5xx` responses from your endpoint. Make sure to send a `2xx` (i.e. `200`) response from your endpoint once you've received the webhook request before operating on the data to avoid a timeout, which will result in the webhook being retried. **It will attempt to send the webhook 8 times before it discards the message.** You can track the webhooks sent in the webhook delivery logs, as detailed in the [managing webhooks](#reading-webhook-delivery-logs) section below. ### 2. Create the Knock webhook You can create a new webhook by navigating to `Webhooks` in the Knock dashboard. You can find this in the sidebar under `Platform`. When you click "Create webhook" you'll be prompted to add the endpoint from the previous step, an optional description, and then select the events you want to be notified of from the list. Once you create the webhook, it will be activated and you will begin receiving requests to your endpoint. To stop receiving requests, you can [delete the webhook](#deleting-a-webhook). ## Receiving a webhook payload ### IP addresses for firewall whitelisting If your infrastructure uses a firewall that restricts incoming traffic by IP address, you can whitelist the following Knock IP addresses to ensure webhook deliveries: - `3.138.92.104` - `18.116.161.231` - `3.19.195.170` ### Verifying the signature We recommend that upon receiving the webhook request you verify the signature before using the contents of the payload. We follow the Stripe specification to add a layer of security to our webhook requests. The signature is generated with a HMAC using the SHA256 algorithm and, prior to being encoded, is comprised of the timestamp and the stringified JSON payload of the request. We encode `"timestamp in milliseconds"."stringified payload"` as the signature of the request. Note that unlike Stripe's specification which uses seconds, Knock uses milliseconds. The `x-knock-signature` header is a string comprised of the timestamp used in the encoding and the encoded value above. It will look like this: `t=timestamp,s=encoded-signature` To test that the payload sent has not been compromised, you can recreate the signature using the shared secret key found on the Webhook page and compare to the one sent in the header. 1. Split the `x-knock-signature` on the comma (",") and extract the values of timestamp and signature. 2. Construct the value of the signature by concatenating: - The timestamp (as a string) - The character `.` - The stringified JSON payload 3. Generate the signature with an HMAC and SHA256 algorithm using the secret key from your webhook's dashboard 4. Compare your generated signature with the one extracted in step one; they should match exactly. If the timestamp is more than five minutes old compared to the current time, you may decide you want to reject the payload for additional security. Here's an example to follow: ```javascript title="Validating a webhook payload with your secret key" // This example uses Express to receive webhooks const express = require("express"); const crypto = require("crypto"); const app = express(); // Find your webhook's secret key on its page in your Knock dashboard const webhookSecret = "some-secret"; // Match the raw body to content type application/json app.post( "/webhook", express.raw({ type: "application/json" }), (request, response) => { const sig = request.headers["x-knock-signature"]; // Extract the timestamp and signature from the header and remove the identifiers const timestamp = sig.split(",")[0].substring(2); const originalSignature = sig.split(",")[1].substring(2); // Construct the value to be encoded (convert the raw body buffer to a string) const value = `${timestamp}.${request.body.toString()}`; // Generate the signature with a HMAC using the SHA256 algorithm const reconstructedSignature = crypto .createHmac("sha256", webhookSecret) .update(value) .digest("base64"); // Compare the signature from the header with your reconstructed signature to validate const isValid = crypto.timingSafeEqual( Buffer.from(originalSignature, "utf-8"), Buffer.from(reconstructedSignature, "utf-8"), ); // For additional security, validate that the timestamp is within your // tolerance for proximity to the current time (example shown within 5 minutes). // The timestamp is already in milliseconds, so we just need to parse it as an integer. const date = new Date(parseInt(timestamp)); const now = Date.now(); const isWithinFiveMinutes = Math.abs(now - date.getTime()) < 300000; if (isValid && isWithinFiveMinutes) { response.json({ received: true }); } else { response.status(400).send("Webhook Error, invalid signature"); } }, ); app.listen(4242, () => console.log("Running on port 4242")); ``` ### Using the data Once you receive a webhook request and return a `2xx` status code, you can make additional requests to the Knock API to gather more information about the event. For example, if you're building a webhook for the `message.undelivered` event, and you'd like to log the message's content, you can use the message ID to send a request to [get message content](/api-reference/messages/get_content) when that event is received. ## Managing webhooks ### Reading webhook delivery logs See the{" "} data retention docs for more details on how Knock enforces this policy. } /> When you visit a webhook's page, if it has begun to send data to your endpoint you'll see a log for every POST request. This will give you the following information: - **Status code:** your server's response to the webhook request - **Event type:** the type of event it was reporting on - **Timestamp:** when the event was sent to your endpoint You'll see details about each log as well. When you select a log, you'll be able to see the **full request payload** sent to your endpoint. You'll also see if your endpoint has stopped working as it will display a `4xx` or `5xx` status code for undelivered webhooks. ### Disabling a webhook When you create a webhook it will automatically be set to `enabled`. You can disable it by going to the webhook's page, and toggling the status to `disabled`. This means that it will not trigger any webhook requests when an event it is configured to listen to is fired. When you're ready to start receiving requests again, toggle the status back to `enabled`. ### Deleting a webhook To stop receiving data to your endpoint, you can delete the webhook from its page. From `/webhooks`, click on the webhook you'd like to delete and you'll see a three-dot menu to the right of the header. Click there and you'll see the delete prompt. Keep in mind that if you delete a webhook, you'll have to recreate it to start receiving data to that endpoint again. ## Event types Learn more about the types of events that Knock sends webhook events for. --- title: Webhook event types description: Learn more about the types of events that Knock sends webhook events for. section: Developer tools > Outbound Webhooks tags: ["events", "data", "analytics", "webhook configuration"] --- ## Message events The event_data field is event-specific and will be{" "} null when not applicable. When present, its available fields and content may vary by channel provider. } /> ### `message.sent` Occurs when a message is successfully sent to a channel provider. - **data** (`Message`) - The associated message. ### `message.delivered` Occurs when a message is marked as delivered to the user by the provider. Not all channels support delivery tracking. Reference the docs on the [`delivered` message status](/send-notifications/message-statuses#7-delivered) to see which channels and providers are supported. - **data** (`Message`) - The associated message. ### `message.delivery_attempted` Occurs when a message delivery attempt fails and may be retried. - **data** (`Message`) - The associated message. - **event_data** (`EventData`) - Additional information about the attempt, the total attempts, and whether or not it will be retried. - **attempt** (`number`) - The current attempt count. - **max_attempts** (`number`) - The total number of attempts that can be tried. - **retryable** (`boolean`) - Whether or not the current attempt is retryable. This field may be absent if retryability is not determined. ### `message.undelivered` Occurs when a message delivery attempt fails permanently. Delivery will not be retried. - **data** (`Message`) - The associated message. - **event_data** (`EventData`) - May include additional information about the failure. - **failure_reason** (`string`) - The reason for a message failure. One of `fatal_error` or `retries_exhausted`. - **failure_details** (`string`) - Details about a message failure. This field varies by provider and is nullable. ### `message.bounced` Occurs when a message delivery attempt fails due to bounce or a delivery status check results in a bounce because of a bad recipient(s) identifier. Delivery will not be retried. - **data** (`Message`) - The associated message. - **event_data** (`EventData`) - May include additional information about the failure. - **failure_reason** (`string`) - The reason for a message failure. - **failure_details** (`string`) - Details about a message bounce. - **token** (`string`) - The erroneous token causing the bounce. Only applicable for push messages. ### `message.seen` Occurs when a message is seen by its recipient. - **data** (`Message`) - The associated message. ### `message.unseen` Occurs when a message is unseen by its recipient. - **data** (`Message`) - The associated message. ### `message.read` Occurs when a message is read by its recipient. - **data** (`Message`) - The associated message. ### `message.unread` Occurs when a message is unread by its recipient. - **data** (`Message`) - The associated message. ### `message.archived` Occurs when a message is archived by its recipient. - **data** (`Message`) - The associated message. ### `message.unarchived` Occurs when a message is unarchived by its recipient. - **data** (`Message`) - The associated message. ### `message.interacted` Occurs when a message is interacted with by its recipient. For the Knock in-app feed, this indicates that your recipient has explicitly clicked on the notification cell in their feed. - **data** (`Message`) - The associated message. - **event_data** (`EventData`) - Includes any additional metadata included in the interacted event. ### `message.link_clicked` Occurs when a link is clicked by the message recipient. This is only available when Knock link tracking is enabled. - **data** (`Message`) - The associated message. - **event_data** (`EventData`) - May include additional information about the link click event. - **url** (`string`) - The target URL that was clicked. ## Workflow recipient run events Workflow recipient run webhooks are currently in beta and enabled on an on-demand basis. To request access for your account,{" "} get in touch . } /> A [workflow recipient run](/concepts/workflows#workflow-runs-and-recipients) represents the execution of a workflow for a single recipient. Use these events to respond when a recipient's workflow run starts, completes, or encounters an error. You can also inspect runs via the [workflow recipient runs API](/api-reference/workflow_recipient_runs). ### `workflow_recipient_run.started` Occurs when a workflow recipient run begins execution. - **data** (`WorkflowRecipientRun`) - The associated workflow recipient run. ### `workflow_recipient_run.completed` Occurs when a workflow recipient run completes. - **data** (`WorkflowRecipientRun`) - The associated workflow recipient run. ### `workflow_recipient_run.error` Occurs when an error is encountered during a workflow recipient run. - **data** (`WorkflowRecipientRun`) - The associated workflow recipient run. ## Workflow events ### `workflow.updated` Occurs whenever a workflow is updated in the environment. - **data** (`Workflow`) - The associated workflow. ### `workflow.committed` Occurs whenever a workflow is committed to the environment. - **data** (`Workflow`) - The associated workflow. - **event_data** (`EventData`) - Additional information about the commit. - **commit_id** (`string`) - The ID of the corresponding commit for the event. ## Email layout events ### `email_layout.updated` Occurs whenever an email layout is updated in the environment. - **data** (`EmailLayout`) - The associated email layout. ### `email_layout.committed` Occurs whenever an email layout is committed to the environment. - **data** (`EmailLayout`) - The associated email layout. - **event_data** (`EventData`) - Additional information about the commit. - **commit_id** (`string`) - The ID of the corresponding commit for the event. ## Translation events ### `translation.updated` Occurs whenever a translation is updated in the environment. - **data** (`Translation`) - The associated translation. ### `translation.committed` Occurs whenever a translation is committed to the environment. - **data** (`Translation`) - The associated translation. - **event_data** (`EventData`) - Additional information about the commit. - **commit_id** (`string`) - The ID of the corresponding commit for the event. ## Source event action events ### `source_event_action.updated` Occurs whenever a source event action is updated in the environment. - **data** (`SourceEventAction`) - The associated source event action. ### `source_event_action.committed` Occurs whenever a source event action is committed to the environment. - **data** (`SourceEventAction`) - The associated source event action. - **event_data** (`EventData`) - Additional information about the commit. - **commit_id** (`string`) - The ID of the corresponding commit for the event. ## Partial events ### `partial.updated` Occurs whenever a partial is updated in the environment. - **data** (`Partial`) - The associated partial. ### `partial.committed` Occurs whenever a partial is committed to the environment. - **data** (`Partial`) - The associated partial. - **event_data** (`EventData`) - Additional information about the commit. - **commit_id** (`string`) - The ID of the corresponding commit for the event. # Migration manuals ## Node.js 1.0 Learn how to upgrade to v1.0 of the Knock Node.js SDK. --- title: Node.js SDK upgrade (v0.x to v1.0) description: Learn how to upgrade to v1.0 of the Knock Node.js SDK. section: Developer tools --- ## Basic changes ### Import style Import the SDK using the standard ES6 import syntax: ```javascript title="Importing the Knock SDK" import Knock from "@knocklabs/node"; ``` ### Client initialization Initialize your client with an options object: ```javascript title="Initializing the Knock client" const client = new Knock({ apiKey: process.env["KNOCK_API_KEY"], // This is the default and can be omitted }); ``` ## What's new in v1.0 The v1.0 SDK brings complete TypeScript support, improved error handling, and new resources to the Node.js ecosystem. ### Fully typed API Complete TypeScript definitions for all requests and responses: ```typescript title="Using TypeScript definitions" const user: Knock.User = await client.users.get("dnedry"); ``` ### Enhanced error handling Typed errors with better context: ```typescript title="Handling API errors with types" try { await client.users.get("dnedry"); } catch (err) { if (err instanceof Knock.APIError) { console.log(err.status, err.name, err.headers); } } ``` ### Auto-pagination Iterate through paginated resources automatically: ```typescript title="Auto-pagination through user list" // Automatically fetches all pages const allUsers = []; for await (const user of client.users.list()) { allUsers.push(user); } ``` ### Request/response access Get more control over raw responses: ```typescript title="Accessing raw response data" const { data: user, response } = await client.users .get("dnedry") .withResponse(); console.log(response.headers.get("X-My-Header")); ``` ### Configurable logging Better debugging capabilities: ```typescript title="Configuring client logging" const client = new Knock({ logLevel: "debug", // Show all log messages }); ``` ### Configurable timeouts and retries Fine-tune request behavior: ```typescript title="Configuring timeouts and retries" const client = new Knock({ timeout: 20 * 1000, // 20 seconds maxRetries: 3, }); ``` ### New resources Additional resources like Audiences, Channels, and Integrations: ```typescript title="Working with audience resources" // Working with audiences await client.audiences.addMembers("audience-id", { members: ["user-1", "user-2"], }); ``` ## Breaking changes ### Client initialization [Client initialization](/developer-tools/migration-guides/node#client-initialization) accepts an options object instead of just the API key. ### Parameter changes Some parameter names have changed to match the API (e.g., `cancellationKey` is now `cancellation_key`). ### Error handling Error handling has been completely revamped with typed errors. ### Token signing The `signUserToken` method should now be imported from `@knocklabs/node/lib/tokenSigner` if referenced directly: ```javascript title="Importing token signing utilities" import { signUserToken, buildUserTokenGrant, Grants, } from "@knocklabs/node/lib/tokenSigner"; const token = await signUserToken("user-1", { grants: [ buildUserTokenGrant({ type: "tenant", id: "org_3sh72ds78" }, [ Grants.MsTeamsChannelsRead, ]), ], }); ``` ### Method changes Several method names have changed: - `notify()` is now `workflows.trigger()` - `users.getSchedules()` is now `users.listSchedules()` - `workflows.createSchedules()` is now `schedules.create()` - `workflows.listSchedules()` is now `schedules.list()` - `workflows.updateSchedules()` is now `schedules.update()` - `workflows.deleteSchedules()` is now `schedules.delete()` ### Bulk operations Bulk operations are now organized in their own namespaces: `users.bulk.*`, `objects.bulk.*`, `schedules.bulk.*`, etc. ### Parameter changes A `PreferenceSet` ID parameter is now required for `users.getPreferences()`. All parameters are now in camelCase in the method signature but use snake_case in the request body. Query parameters are now passed as a separate object in most list methods: ```javascript title="Parameter style comparison" // Old SDK await knockClient.users.list({ page: 2, pageSize: 50 }); // New SDK await client.users.list({ page: 2, page_size: 50 }); ``` ### Response handling The new SDK returns the data directly in most cases rather than wrapped in a `data` property. Pagination is handled differently with cursor-based pagination. ## Common operations **Old version:** ```javascript title="Triggering workflows with the old SDK" await knockClient.notify("dinosaurs-loose", { actor: "dnedry", recipients: ["jhammond", "agrant"], data: { type: "trex", priority: 1, }, tenant: "jurassic-park", cancellationKey: triggerAlert.id, }); ``` **New version:** ```javascript title="Triggering workflows with the new SDK" await client.workflows.trigger("dinosaurs-loose", { actor: "dnedry", recipients: ["jhammond", "agrant"], data: { type: "trex", priority: 1, }, tenant: "jurassic-park", cancellation_key: triggerAlert.id, }); ``` **Old version:** ```javascript title="Identifying users with the old SDK" await client.users.identify("jhammond", { name: "John Hammond", email: "jhammond@ingen.net", }); ``` **New version:** ```javascript title="Identifying users with the new SDK" await client.users.update("jhammond", { name: "John Hammond", email: "jhammond@ingen.net", }); ``` ## Need help? If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. ## Python 1.0 Learn how to upgrade to v1.0 of the Knock Python SDK. --- title: Python SDK upgrade (v0.x to v1.0) description: Learn how to upgrade to v1.0 of the Knock Python SDK. section: Developer tools --- ## Basic changes ### Client initialization Initialize your client with the API key: ```python title="Initializing the Knock client" from knockapi import Knock client = Knock(api_key="sk_12345") # defaults to ENV["KNOCK_API_KEY"] ``` ## What's new in v1.0 The v1.0 SDK introduces async support, strong type checking, and improved developer experience for Python applications. ### Async support First-class async/await support: ```python title="Using the async client" from knockapi import AsyncKnock client = AsyncKnock(api_key="sk_12345") async def main(): response = await client.workflows.trigger( key="dinosaurs-loose", recipients=["dnedry"], data={"dinosaur": "triceratops"}, ) print(response.workflow_run_id) ``` ### Strong type checking Improved type hints with TypedDict and Pydantic models: ```python title="Using type hints for better IDE support" # With type checking enabled, you get IDE support and validation from knockapi.types import User user: User = client.users.get("dnedry") ``` ### Auto-pagination Iterate through paginated resources automatically: ```python title="Auto-pagination through user list" # Automatically fetches more pages as needed all_users = [] for user in client.users.list(): all_users.append(user) # Or asynchronously async for user in async_client.users.list(): process_user(user) ``` ### Enhanced error handling More specific error classes: ```python title="Handling different types of API errors" import knockapi try: client.users.get("dnedry") except knockapi.APIConnectionError as e: print("The server could not be reached") except knockapi.RateLimitError as e: print("A 429 status code was received; we should back off") except knockapi.APIStatusError as e: print("Status code:", e.status_code) ``` ### Configurable timeouts and retries Fine-tune request behavior: ```python title="Configuring timeouts and retries" # Configure the default for all requests: client = Knock( timeout=20.0, # 20 seconds (default is 60) max_retries=3 # default is 2 ) # Or, configure per-request: client.with_options(max_retries=5).users.get("dnedry") ``` ### Access to raw response data Get more control over HTTP interactions: ```python title="Accessing raw response data" # Get raw response data including headers response = client.with_raw_response.users.get("dnedry") print(response.headers.get("X-Request-ID")) ``` ## Breaking changes ### Client design The new SDK has a cleaner interface with direct method calls. The old approach of passing all parameters as keyword arguments is replaced with specific method signatures. Parameters that were previously named keyword arguments may now be positional arguments. ### Method naming changes Several method names have changed: - `client.users.identify()` is now `client.users.update()` - `client.users.get_user()` is now `client.users.get()` - Client-level `.notify()` shorthand is removed, use `client.workflows.trigger()` instead - `client.users.getSchedules()` is now `client.users.listSchedules()` - `client.workflows.createSchedules()` is now `client.schedules.create()` - `client.workflows.listSchedules()` is now `client.schedules.list()` - `client.workflows.updateSchedules()` is now `client.schedules.update()` - `client.workflows.deleteSchedules()` is now `client.schedules.delete()` ### Bulk operations Bulk operations are now organized in their own namespaces: `client.users.bulk.*`, `client.objects.bulk.*`, `client.schedules.bulk.*`, etc. ### Method parameters Most methods now take the ID(s) as positional parameter(s), followed by named parameters. The `data` wrapper is removed for user properties (they're now top-level parameters). Parameter names have changed in some cases (e.g., `id` is now `user_id`). Preference methods require an explicit preference set ID parameter. ### Response types The new SDK returns typed Pydantic model instances instead of dictionaries. Use `.model_dump()` to get a dictionary representation of a response object. Pagination is handled via cursor-based pagination instead of page-based. ## Common operations **Old SDK:** ```python title="Triggering workflows with the old SDK" client.notify( key="dinosaurs-loose", actor="dnedry", recipients=["jhammond", "agrant"], data={ "type": "trex", "priority": 1 }, cancellation_key=alert.id, tenant="jurassic-park" ) ``` **New SDK:** ```python title="Triggering workflows with the new SDK" client.workflows.trigger( key="dinosaurs-loose", recipients=["jhammond", "agrant"], actor="dnedry", data={ "type": "trex", "priority": 1 }, cancellation_key=alert.id, tenant="jurassic-park" ) ``` **Old SDK:** ```python title="Identifying users with the old SDK" client.users.identify( id="jhammond", data={ "name": "John Hammond", "email": "jhammond@ingen.net" } ) ``` **New SDK:** ```python title="Identifying users with the new SDK" client.users.update( user_id="jhammond", name="John Hammond", email="jhammond@ingen.net" ) ``` **Old SDK:** ```python title="Setting user preferences with the old SDK" client.users.set_preferences( user_id="jhammond", channel_types={'email': True}, workflows={'dinosaurs-loose': False} ) ``` **New SDK:** ```python title="Setting user preferences with the new SDK" client.users.set_preferences( user_id="jhammond", id="default", channel_types={'email': True}, workflows={'dinosaurs-loose': False} ) ``` **Old SDK:** ```python title="Setting object properties with the old SDK" client.objects.set( collection="dinosaurs", id="trex1", properties={ "name": "Rexy", "species": "Tyrannosaurus rex" } ) ``` **New SDK:** ```python title="Setting object properties with the new SDK" client.objects.set( collection="dinosaurs", id="trex1", name="Rexy", species="Tyrannosaurus rex" ) ``` **Old SDK:** ```python title="Setting tenant properties with the old SDK" client.tenants.set( id="jurassic-park", name="Jurassic Park", logo_url="https://example.com/jp-logo.png", settings={ "branding": { "primary_color": "#FF6B35", "logo_url": "https://example.com/jp-logo.png" } } ) ``` **New SDK:** ```python title="Setting tenant properties with the new SDK" client.tenants.set( id="jurassic-park", extra_body={ "name": "Jurassic Park", "logo_url": "https://example.com/jp-logo.png" }, settings={ "branding": { "primary_color": "#FF6B35", "logo_url": "https://example.com/jp-logo.png" } } ) ``` ## Need help? If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. ## Java 1.0 Learn how to upgrade to v1.0 of the Knock Java SDK. --- title: Java SDK upgrade (v0.x to v1.0) description: Learn how to upgrade to v1.0 of the Knock Java SDK. section: Developer tools --- ## Basic changes ### Client initialization Initialize your client using environment variables or explicit configuration: ```java title="Initializing the Knock client" import app.knock.api.client.KnockClient; import app.knock.api.client.okhttp.KnockOkHttpClient; // Using environment variables (KNOCK_API_KEY and KNOCK_BASE_URL) KnockClient client = KnockOkHttpClient.fromEnv(); // Or explicit configuration KnockClient client = KnockOkHttpClient.builder() .baseUrl("https://api.knock.app") .apiKey("sk_12345") .build(); ``` ## What's new in v1.0 The v1.0 SDK introduces significant improvements for Java developers, including async support, immutable objects, and better error handling. ### Asynchronous execution Switch to asynchronous operations when needed: ```java title="Using asynchronous client operations" // Perform operations asynchronously CompletableFuture userFuture = client.async().users().get( UserGetParams.builder().userId("jhammond").build() ); // Or create an async client from the beginning KnockClientAsync asyncClient = KnockOkHttpClientAsync.fromEnv(); ``` ### Immutable objects All objects are immutable for thread safety: ```java title="Working with immutable parameter objects" // Each class is immutable once constructed and has a toBuilder() method // for creating modified copies if needed UserUpdateParams params = UserUpdateParams.builder() .userId("jhammond") .name("John Hammond") .build(); // Create a modified copy UserUpdateParams updatedParams = params.toBuilder() .email("john@ingen.com") .build(); ``` ### Pagination helpers Work with paginated resources more easily: ```java title="Auto-pagination through user list" // Auto-pagination for iterating through all results UserListPage page = client.users().list(params); for (User user : page.autoPager()) { System.out.println(user); } // Or as a Stream client.users().list(params).autoPager().stream() .limit(50) .forEach(user -> System.out.println(user)); ``` ### Raw response access Access raw HTTP responses when needed: ```java title="Accessing raw HTTP response data" // Access the raw HTTP response HttpResponseFor userResponse = client.users().withRawResponse().get( UserGetParams.builder().userId("jhammond").build() ); int statusCode = userResponse.statusCode(); Headers headers = userResponse.headers(); User user = userResponse.parse(); ``` ### Enhanced error handling Handle errors more efficiently with more detailed error information: ```java title="Handling different types of API errors" try { client.users().get(UserGetParams.builder().userId("nonexistent").build()); } catch (NotFoundException e) { // Handle 404 error } catch (KnockServiceException e) { // Handle other API errors } catch (KnockException e) { // Handle generic errors } ``` ### Additional properties Add undocumented parameters when needed: ```java title="Adding custom headers and properties" WorkflowTriggerParams params = WorkflowTriggerParams.builder() .putAdditionalHeader("Secret-Header", "42") .putAdditionalQueryParam("secret_query_param", "42") .putAdditionalBodyProperty("secretProperty", JsonValue.from("42")) .build(); ``` ## Breaking changes ### Request parameter style The new SDK uses builder patterns for all request parameters. Parameters are grouped into structured objects rather than flat lists. ### Method signatures Most methods now take parameter objects instead of individual parameters. The new SDK uses a more consistent naming convention across all methods. ### Type system The new SDK uses `JsonValue` and typed fields for better type safety. Field access methods have changed to accommodate the new type system. ### Error handling The new SDK provides more detailed and structured error information. ### API key configuration API key is now passed as a bearer token instead of a dedicated API key parameter. ## Common operations **Old SDK:** ```java title="Triggering workflows with the old SDK" WorkflowTrigger workflowTrigger = WorkflowTrigger.builder() .key("dinosaurs-loose") .actor("dnedry") .recipients(List.of("jhammond", "agrant")) .data("fences_electrified", false) .data("breeds", List.of("velociraptors", "trex")) .build(); WorkflowTriggerResult result = client.workflows().trigger(workflowTrigger); ``` **New SDK:** ```java title="Triggering workflows with the new SDK" import app.knock.api.core.JsonValue; import app.knock.api.models.workflows.WorkflowTriggerParams; import app.knock.api.models.workflows.WorkflowTriggerResponse; WorkflowTriggerParams params = WorkflowTriggerParams.builder() .key("dinosaurs-loose") .recipients(List.of( RecipientRequest.ofUserRecipient("jhammond"), RecipientRequest.ofUserRecipient("agrant") )) .actor("dnedry") .data(WorkflowTriggerParams.Data.builder() .putAdditionalProperty("fences_electrified", JsonValue.from(false)) .putAdditionalProperty("breeds", JsonValue.from(List.of("velociraptors", "trex"))) .build()) .build(); WorkflowTriggerResponse response = client.workflows().trigger(params); ``` **Old SDK:** ```java title="Identifying users with the old SDK" UserIdentity userIdentity = UserIdentity.builder() .id("jhammond") .name("John Hammond") .email("jhammond@ingen.com") .property("expenses_spared", "none") .build(); client.users().identify(userIdentity); ``` **New SDK:** ```java title="Identifying users with the new SDK" import app.knock.api.core.JsonValue; import app.knock.api.models.users.UserUpdateParams; client.users().update( UserUpdateParams.builder() .userId("jhammond") .name("John Hammond") .email("jhammond@ingen.com") .putAdditionalBodyProperty("expenses_spared", JsonValue.from("none")) .build() ); ``` **Old SDK:** ```java title="Setting user preferences with the old SDK" // Set preference set for user PreferenceSetRequest request = PreferenceSetRequest.builder() .channelTypes( new PreferenceSetBuilder() .email(true) .buildChannelTypes()) .build(); client.users().setPreferences("jhammond", request); // Set workflow-specific preferences PreferenceSetRequest workflowRequest = PreferenceSetRequest.builder() .workflow("dinosaurs-loose", new PreferenceSetBuilder() .email(false) .sms(true) .condition("recipient.handles_dino_types", "contains", "data.dino_type") .build()) .build(); client.users().setPreferences("jhammond", workflowRequest); // Get preferences PreferenceSet defaultPrefs = client.users().getDefaultPreferences("jhammond"); PreferenceSet specificPrefs = client.users().getPreferencesById("jhammond", "other-preference-set"); ``` **New SDK:** ```java title="Setting user preferences with the new SDK" import app.knock.api.core.JsonValue; import app.knock.api.models.recipients.preferences.PreferenceSetRequest; import app.knock.api.models.users.UserSetPreferencesParams; // Set preference set for user PreferenceSetRequest request = PreferenceSetRequest.builder() .channelTypes( PreferenceSetRequest.PreferenceSetChannelTypes.builder() .putAdditionalProperty("email", JsonValue.from(true)) .build()) .build(); client.users().setPreferences( UserSetPreferencesParams.builder() .userId("jhammond") .id("default") .preferenceSetRequest(request) .build() ); // Set workflow-specific preferences PreferenceSetRequest workflowRequest = PreferenceSetRequest.builder() .workflows( PreferenceSetRequest.Workflows.builder() .putAdditionalProperty("dinosaurs-loose", JsonValue.from(Map.of( "channel_types", Map.of("email", false, "sms", true), "conditions", List.of(Map.of( "variable", "recipient.handles_dino_types", "operator", "contains", "argument", "data.dino_type" )) ))) .build() ) .build(); client.users().setPreferences( UserSetPreferencesParams.builder() .userId("jhammond") .id("default") .preferenceSetRequest(workflowRequest) .build() ); // Get preferences var defaultPrefs = client.users().getPreferences( app.knock.api.models.users.UserGetPreferencesParams.builder() .userId("jhammond") .id("default") .build() ); var specificPrefs = client.users().getPreferences( app.knock.api.models.users.UserGetPreferencesParams.builder() .userId("jhammond") .id("other-preference-set") .build() ); ``` **Old SDK:** ```java title="Setting channel data with the old SDK" String channelId = "114a928a-5b35-4e1b-9069-ac873ee972d3"; ChannelData channelData = client.users().setChannelData( "jhammond", channelId, Map.of("tokens", List.of("some-token")) ); // Get channel data ChannelData retrievedChannelData = client.users().getUserChannelData("jhammond", channelId); // Unset (delete) channel data client.users().unsetUserChannelData("jhammond", channelId); ``` **New SDK:** ```java title="Setting channel data with the new SDK" import app.knock.api.core.JsonValue; import app.knock.api.models.users.UserSetChannelDataParams; import app.knock.api.models.users.UserGetChannelDataParams; import app.knock.api.models.users.UserUnsetChannelDataParams; String channelId = "114a928a-5b35-4e1b-9069-ac873ee972d3"; // Set channel data var channelData = client.users().setChannelData( UserSetChannelDataParams.builder() .userId("jhammond") .channelId(channelId) .data(UserSetChannelDataParams.Data.builder() .putAdditionalProperty("tokens", JsonValue.from(List.of("some-token"))) .build()) .build() ); // Get channel data var retrievedData = client.users().getChannelData( UserGetChannelDataParams.builder() .userId("jhammond") .channelId(channelId) .build() ); // Unset channel data client.users().unsetChannelData( UserUnsetChannelDataParams.builder() .userId("jhammond") .channelId(channelId) .build() ); ``` ## Need help? If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. ## Ruby 1.0 Learn how to upgrade to v1.0 of the Knock Ruby SDK. --- title: Ruby SDK upgrade (v0.x to v1.0) description: Learn how to upgrade to v1.0 of the Knock Ruby SDK. section: Developer tools --- ## Basic changes ### Client initialization Initialize a client instance with your API key: ```ruby title="Initializing the Knock client" require "knockapi" # Initialize a client instance knock = Knockapi::Client.new( api_key: "sk_12345" # defaults to ENV["KNOCK_API_KEY"] ) ``` ## What's new in v1.0 The v1.0 SDK brings strong typing with Sorbet, auto-pagination, and enhanced error handling to the Ruby ecosystem. ### Strong typing with Sorbet Improved type definitions for better IDE integration. ### Auto-pagination Iterate through paginated resources automatically: ```ruby title="Auto-pagination through user list" # Automatically fetches more pages as needed. page = knock.users.list page.auto_paging_each do |user| puts user.id end ``` ### Enhanced error handling Handle errors more effectively with improved error classes: ```ruby title="Handling API errors with specific classes" begin user = knock.users.get("dnedry") rescue Knockapi::Errors::APIError => e puts e.status # 400 end ``` ### Configurable timeouts and retries Fine-tune request behavior by customizing timeouts and retry limits: ```ruby title="Configuring timeouts and retries" knock = Knockapi::Client.new( timeout: 20, # 20 seconds (default is 60) max_retries: 3 # default is 2 ) ``` ### New resources Work with additional resources like [Audiences](/concepts/audiences), [Channels](http://localhost:3002/concepts/channels), and [Integrations](/integrations/sources/overview): ```ruby title="Working with audience resources" # Working with audiences knock.audiences.add_members( "audience-id", members: ["user-1", "user-2"] ) ``` ### Request options More control over individual requests: ```ruby title="Setting per-request options" knock.users.get("dnedry", request_options: { timeout: 5 }) ``` ## Breaking changes ### Client design The new SDK uses an instance-based client rather than module functions. Create a client instance with `knock = Knockapi::Client.new()` and call methods via the client: `knock.workflows.trigger()` instead of `Knock::Workflows.trigger()`. ### Method naming and organization The main module is now `Knockapi` instead of `Knock`. Bulk operations are now organized in their own namespaces: `knock.users.bulk.*`, `knock.objects.bulk.*`, `knock.schedules.bulk.*`, etc. Several method names have changed: - `Knock::Users.identify()` is now `knock.users.update()` - `Knock::Users.set_preferences()` is now `knock.recipients.preferences.set()` - `Knock::Users.get_channel_data()` is now `knock.recipients.channel_data.get()` - `Knock::Users.getSchedules()` is now `knock.users.listSchedules()` - `Knock::Workflows.createSchedules()` is now `knock.schedules.create()` - `Knock::Workflows.listSchedules()` is now `knock.schedules.list()` - `Knock::Workflows.updateSchedules()` is now `knock.schedules.update()` - `Knock::Workflows.deleteSchedules()` is now `knock.schedules.delete()` ### Method parameters Most methods now take the ID(s) as positional parameter(s), followed by named parameters. Resource IDs moved from named parameters to positional parameters (e.g., `id: "user-1"` is now `"user-1"`). `data:` parameters have been flattened in most methods. ### Preference management Preferences are now handled through the `recipients.preferences` module. The preference set ID must be explicitly provided (usually "default"). ### Response handling The new SDK returns model instances rather than raw hashes. Pagination is handled via cursor-based pagination instead of page-based. ## Common operations **Old SDK:** ```ruby title="Triggering workflows with the old SDK" Knock::Workflows.trigger( key: "dinosaurs-loose", actor: "dnedry", recipients: ["jhammond", "agrant"], data: { type: "trex", priority: 1, }, cancellation_key: trigger_alert.id, ) ``` **New SDK:** ```ruby title="Triggering workflows with the new SDK" knock.workflows.trigger( "dinosaurs-loose", recipients: ["jhammond", "agrant"], actor: "dnedry", data: { type: "trex", priority: 1, }, cancellation_key: trigger_alert.id, ) ``` **Old SDK:** ```ruby title="Identifying users with the old SDK" Knock::Users.identify( id: "jhammond", data: { name: "John Hammond", email: "jhammond@ingen.net", } ) ``` **New SDK:** ```ruby title="Identifying users with the new SDK" knock.users.update( "jhammond", name: "John Hammond", email: "jhammond@ingen.net" ) ``` **Old SDK:** ```ruby title="Setting user preferences with the old SDK" Knock::Users.set_preferences( user_id: "jhammond", channel_types: { email: true, sms: false }, workflows: { 'dinosaurs-loose': { channel_types: { email: false, in_app_feed: false } } } ) ``` **New SDK:** ```ruby title="Setting user preferences with the new SDK" knock.recipients.preferences.set( "users", "jhammond", "default", channel_types: { email: true, sms: false }, workflows: { 'dinosaurs-loose': { channel_types: { email: false, in_app_feed: false } } } ) ``` ## Need help? If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. ## Go 1.0 Learn how to upgrade to v1.0 of the Knock Go SDK. --- title: Go SDK upgrade (v0.x to v1.0) description: Learn how to upgrade to v1.0 of the Knock Go SDK. section: Developer tools --- ## Basic changes ### Client initialization The new SDK uses updated configuration methods: ```go title="Initializing the Knock client" import ( "context" "os" "github.com/knocklabs/knock-go" "github.com/knocklabs/knock-go/option" "github.com/knocklabs/knock-go/shared" ) ctx := context.Background() // Create a new Knock API client (uses KNOCK_API_KEY env var by default) client := knock.NewClient( option.WithApiKey(os.Getenv("KNOCK_API_KEY")), ) ``` ## What's new in v1.0 The v1.0 SDK brings significant improvements to type safety, error handling, and developer experience. ### Strong type system The new SDK uses field wrappers to distinguish between zero values and unset fields: ```go title="Using field wrappers for type safety" // Set a field knock.F("value") // Explicitly set to null knock.Null[string]() // Send a value of a different type than the field's type knock.Raw[string](123) ``` ### Auto-pagination Iterate through paginated resources without manual page management: ```go title="Auto-pagination through user list" // Automatically fetches more pages as needed iter := client.Users.ListAutoPaging(ctx, knock.UserListParams{}) for iter.Next() { user := iter.Current() fmt.Printf("%+v\n", user) } if err := iter.Err(); err != nil { panic(err.Error()) } ``` ### Enhanced error handling Get detailed error information with better debugging utilities: ```go title="Handling errors with detailed debugging" _, err := client.Users.Get(ctx, "dnedry") if err != nil { var apiErr *knock.Error if errors.As(err, &apiErr) { fmt.Println(string(apiErr.DumpRequest(true))) // Print the serialized HTTP request fmt.Println(string(apiErr.DumpResponse(true))) // Print the serialized HTTP response } } ``` ### Request options Configure requests with flexible per-request options: ```go title="Configuring per-request options" // Configure per-request options client.Users.Get( ctx, "dnedry", option.WithHeader("X-Custom-Header", "custom_value"), option.WithRequestTimeout(20*time.Second), ) ``` ### Raw JSON access Access raw JSON data for advanced use cases: ```go title="Accessing raw JSON response data" user, _ := client.Users.Get(ctx, "jhammond") // Check if a field is null or missing if user.Name == "" { if user.JSON.Name.IsNull() { fmt.Println("Name is explicitly null") } if user.JSON.Name.IsMissing() { fmt.Println("Name field was not present in the response") } } // Access extra fields not defined in the struct extraField := user.JSON.ExtraFields["custom_field"].Raw() ``` ### Undocumented request parameters The new SDK allows undocumented request parameters to be passed in the request body. To make requests using undocumented parameters, you may use either the `option.WithQuerySet()` or the `option.WithJSONSet()` methods. ```go title="Making requests with undocumented parameters" params := FooNewParams{ ID: knock.F("id_xxxx"), Data: knock.F(FooNewParamsData{ FirstName: knock.F("John"), }), } client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe")) ``` ## Breaking changes ### Request parameter style The new SDK wraps all request parameters with `param.Field` using helpers like `knock.F()` to distinguish between zero values and unset fields. ### Method signatures Most methods now take IDs as positional parameters, followed by a params struct. The new SDK spreads parameters across the method signature for better readability. ### Type system The new SDK uses a strong type system with special wrapper types. Field values need to be wrapped with `knock.F()` to be included in requests. Union types are used for parameters that can have multiple types. ### Error handling Error types and handling mechanisms have changed significantly. The new SDK provides more detailed error information and better debugging utilities. ### Pagination The new SDK introduces cursor-based pagination with auto-pagination helpers. ## Common operations **Old SDK:** ```go title="Triggering workflows with the old SDK" req := &knock.TriggerWorkflowRequest{ Workflow: "dinosaurs-loose", Data: map[string]interface{}{ "type": "trex", "priority": 1, }, } req.AddRecipientByID("jhammond") req.AddRecipientByID("agrant") // Trigger workflow with idempotency key workflow, _ := client.Workflows.Trigger(ctx, req, &knock.MethodOptions{ IdempotencyKey: "an-idempotency-key", }) ``` **New SDK:** ```go title="Triggering workflows with the new SDK" response, err := client.Workflows.Trigger( ctx, "dinosaurs-loose", knock.WorkflowTriggerParams{ Recipients: knock.F([]knock.RecipientRequestUnionParam{ shared.UnionString("jhammond"), shared.UnionString("agrant"), }), Data: knock.F(map[string]interface{}{ "type": "trex", "priority": 1, }), CancellationKey: knock.F("an-idempotency-key"), }, ) ``` **Old SDK:** ```go title="Identifying users with the old SDK" user, _ := client.Users.Identify(ctx, &knock.IdentifyUserRequest{ ID: "jhammond", Name: "John Hammond", CustomProperties: map[string]interface{}{ "welcome": "to jurassic park", }, }) ``` **New SDK:** ```go title="Identifying users with the new SDK" user, err := client.Users.Update( ctx, "jhammond", knock.UserUpdateParams{ IdentifyUserRequest: knock.IdentifyUserRequestParam{ Name: knock.F("John Hammond"), ExtraFields: map[string]interface{}{ "welcome": "to jurassic park", }, }, }, ) ``` **Old SDK:** ```go title="Setting object properties with the old SDK" object, _ := client.Objects.Set(ctx, &knock.SetObjectRequest{ Collection: "dinosaurs", ID: "trex1", Properties: map[string]interface{}{ "name": "Rexy", "species": "Tyrannosaurus rex", "timezone": "America/Chicago", }, }) ``` **New SDK:** ```go title="Setting object properties with the new SDK" object, err := client.Objects.Set( ctx, "dinosaurs", "trex1", knock.ObjectSetParams{ Timezone: knock.F("America/Chicago"), }, option.WithJSONSet("name", "Rexy"), option.WithJSONSet("species", "Tyrannosaurus rex"), ) ``` ## Need help? If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. # Agent Toolkit ## Overview Use Knock's Agent Toolkit to give your AI agents the ability to send cross-channel messaging and power rich human-in-the-loop flows. --- title: Agent Toolkit description: Use Knock's Agent Toolkit to give your AI agents the ability to send cross-channel messaging and power rich human-in-the-loop flows. section: Developer tools --- Knock Agent Toolkit exposes a set of tools that your AI agent applications can use to interact with Knock via function calling. Using Agent Toolkit, you can: - Give your AI agent applications the ability to [send cross-channel notifications](/developer-tools/agent-toolkit/workflows-as-tools) to your users. - Power rich [human-in-the-loop flows](/developer-tools/agent-toolkit/human-in-the-loop-flows) that allow your agents to solicit structured input from users, or require human approval of actions taken by your agents. - Let your agents create and manage resources within your Knock account. Agent Toolkit is currently in beta as a TypeScript package only. We're considering adding support for Python too. If you're interested in using Agent Toolkit in another language, please let us know at{" "} support@knock.app. } /> ## Getting started To get started with the Knock agent toolkit, you must have: - A Knock account - A [service token](/developer-tools/service-tokens) for your Knock account Once you have a Knock account and a service token, you can install the Knock agent toolkit using your preferred package manager. ```bash npm install @knocklabs/agent-toolkit ``` You can then set environment variables for your Knock service token. ```bash export KNOCK_SERVICE_TOKEN= ``` Depending on which AI/Agent framework you're using, you can then initialize the agent toolkit and use it to power your agent. ```typescript import { createAgentToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createAgentToolkit(); ``` Read more about [getting started with the Knock agent toolkit](/developer-tools/agent-toolkit/getting-started). ## Workflows-as-tools By default Agent Toolkit will expose each of your workflows as a tool that your agents can invoke to power cross-channel messaging. If you have a workflow (`comment-created`) within your Knock account, Agent Toolkit will expose a `trigger_comment_created_workflow` tool that your agents can invoke to send a notification. Each tool describes the parameters that are required to trigger the workflow, where the parameters for the data payload are read from the [workflow's trigger data JSON schema](/developer-tools/validating-trigger-data). Adding the JSON schema for your workflow trigger data allows the LLM to understand the exact shape and definition of the data payload for the workflow. If the workflow does not specify a trigger data JSON schema, the tool will define a generic data payload for the AI agent to fill in. You can control this behavior by specifying a list of workflow keys that are allowed to be exposed to the agent (`workflows.trigger = ["comment-created"]`). By default, no workflows are exposed as tools. Read more about [exposing workflows as tools](/developer-tools/agent-toolkit/workflows-as-tools). ## Human-in-the-loop flows You can use Agent Toolkit to power rich human-in-the-loop interactions. For example, you can use Agent Toolkit to create a workflow that allows a person to approve or reject the agent's work, where the approval is an actionable notification sent by Knock. The toolkit exposes wrappers for requiring asynchronous human input. When wrapped, your tools will first delegate to Knock to trigger a workflow that you define, then asynchronously continue tool execution once you have received a response from a human. ```typescript title="Wrapping a tool as requiring human input" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = createKnockToolkit(); const classifyToolRequiringInput = toolkit.requireHumanInput(classifyWork, { workflow: "approve-classification", recipients: ["user_123"], }); ``` You can read more about [powering human-in-the-loop flows in the documentation](/developer-tools/agent-toolkit/human-in-the-loop-flows). ## Creating Knock resources You can create Knock resources using Agent Toolkit through function calling. For example, you may want an agent to create or update a user's record in Knock as part of your agent workflow, or subscribe a user to another object so they can receive notifications later. Agent Toolkit exposes most common Knock resources as tools that your agents can invoke. You can find the list of available tools in the [tools reference](/developer-tools/agent-toolkit/tools-reference). ## Security and authorization To authenticate with Agent Toolkit, you need to provide a [service token](/developer-tools/service-tokens) for your Knock account. This token should be kept secret and not exposed to the LLM. Your service token grants access to all of your Knock resources, so it's important to keep it secure. You can also customize the tools exposed to the AI Agent by specifying a set of permissions that define the resources that the agent has access to. For example, the following permissions will expose only the `users` resource to the agent, which will include the ability to read and manage users. ```typescript title="Setting permissions for the agent" { permissions: { users: { read: true, manage: true }, }, }; ``` ## Relevant links - [Getting started with Agent Toolkit](/developer-tools/agent-toolkit/getting-started) - [Building with LLMs](/developer-tools/building-with-llms) - [Model Context Protocol (MCP) Server](/ai/mcp-server) ## Getting started Learn how to use the Knock Agent Toolkit within your AI agent workflows to power cross-channel messaging and rich human-in-the-loop interactions. --- title: Getting started with the Knock Agent Toolkit description: Learn how to use the Knock Agent Toolkit within your AI agent workflows to power cross-channel messaging and rich human-in-the-loop interactions. section: Developer tools --- ## Installing the Knock Agent Toolkit You can install the Knock Agent Toolkit using npm, yarn, or pnpm. ```bash npm install @knocklabs/agent-toolkit ``` Once installed you can use the toolkit through the exposed SDK adapters, or if you need low-level access to the toolkit you can use the `@knocklabs/agent-toolkit` package directly. ## Setting up your .env file You'll need to have a [service token](/developer-tools/service-tokens) setup on your Knock account to authenticate with the Knock Agent Toolkit. ```bash KNOCK_SERVICE_TOKEN=st_1234567890 ``` ## Usage with the Vercel AI SDK ```typescript title="Usage with the Vercel AI SDK" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createKnockToolkit({ permissions: { users: { manage: true }, }, }); const result = await generateText({ model: openai("gpt-4o"), tools: { ...toolkit.getTools("users"), }, maxSteps: 5, prompt: "Update the current user's profile with information about them, knowing that they are Alan Grant from Jurassic Park. Include custom properties about their favorite dinosaur.", }); ``` ## Usage with the OpenAI SDK ```typescript title="Usage with the OpenAI SDK" import OpenAI from "openai"; import { createKnockToolkit } from "@knocklabs/agent-toolkit/openai"; const toolkit = await createKnockToolkit(); const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); const messages = [ { role: "user", content: "Update the current user's profile with information about them, knowing that they are Alan Grant from Jurassic Park. Include custom properties about their favorite dinosaur.", }, ]; (async (): Promise => { const result = await openai.chat.completions.create({ model: "gpt-4o", messages, }); while (true) { const completion = await openai.chat.completions.create({ model: "gpt-4o", messages, tools: toolkit.getTools("users"), }); const message = completion.choices[0].message; messages.push(message); if (message.tool_calls) { const toolMessages = await Promise.all( message.tool_calls.map((tc) => toolkit.handleToolCall(tc)), ); messages = [...messages, ...toolMessages]; } else { console.log(completion.choices[0].message); break; } } })(); ``` ## Usage with the LangChain framework ```typescript title="Usage with the LangChain framework" import { createKnockToolkit } from "@knocklabs/agent-toolkit/langchain"; import { ChatOpenAI } from "@langchain/openai"; import type { ChatPromptTemplate } from "@langchain/core/prompts"; import { pull } from "langchain/hub"; import { AgentExecutor, createStructuredChatAgent } from "langchain/agents"; const toolkit = await createKnockToolkit(); const llm = new ChatOpenAI({ model: "gpt-4o", }); (async (): Promise => { const prompt = await pull( "hwchase17/structured-chat-agent", ); const tools = toolkit.getTools("users"); const agent = await createStructuredChatAgent({ llm, tools, prompt, }); const agentExecutor = new AgentExecutor({ agent, tools, }); const response = await agentExecutor.invoke({ input: "Update the user's profile with information about them, knowing that they are Alan Grant from Jurassic Park. Include custom properties about their favorite dinosaur.", }); console.log(response); })(); ``` ## Related links - [Powering human-in-the-loop interactions](/developer-tools/agent-toolkit/human-in-the-loop-flows) - [Providing context](/developer-tools/agent-toolkit/providing-context) - [Building with LLMs](/developer-tools/building-with-llms) - [Model Context Protocol (MCP) Server](/ai/mcp-server) ## Workflows-as-tools Learn how Agent Toolkit exposes your workflows as tools that your AI Agents can invoke to power cross-channel user messaging, without any integration logic. --- title: Workflows-as-tools description: Learn how Agent Toolkit exposes your workflows as tools that your AI Agents can invoke to power cross-channel user messaging, without any integration logic. section: Developer tools --- By default Agent Toolkit will expose each of your workflows as a tool that your agents can invoke to power cross-channel messaging. If you have a workflow (`comment-created`) within your Knock account, Agent Toolkit will expose a `trigger_comment_created_workflow` tool that your agents can invoke to send a notification. You can think of this as exposing a well-scoped API endpoint that describes exactly how the workflow should be triggered to the LLM/AI Agent. That makes it trivial to build agents that can trigger complex cross-channel messaging flows, with no integration logic. ## Configuring which workflows are exposed as tools When using Agent Toolkit, you can optionally configure exactly which workflows are exposed as tools. To do so, you can set the `permissions.workflows.run` property on the Agent Toolkit configuration. In the below example, we're ensuring that **only** the `comment-created`, `task-completed`, and `welcome-user` workflows are exposed as tools to the LLM: ```typescript const toolkit = await createAgentToolkit({ permissions: { workflows: { trigger: ["comment-created", "task-completed", "welcome-user"], }, }, }); ``` ## Describing the trigger data for each workflow The trigger data tool created for each workflow exposes the [workflow's trigger data JSON schema](/developer-tools/validating-trigger-data) to the LLM to describe the data payload required to trigger the workflow, and what the shape of that expected data should be. This allows you to define an explicit contract for your workflow and ensure that the LLM is passing in the correct data when invoking the workflow to send notifications, adding a safe guard so that workflows cannot be triggered with incorrect data supplied. ## Tips for exposing workflows to LLMs The workflow description is a great place to add additional calling context for the LLM as it is injected into the prompt that exposes the tool. That means you can use the description to add additional calling context, like when the workflow should be used, or what the context of the workflow is. It's best if you limit the workflows exposed to the LLM as tools to only those that you absolutely know that the LLM can trigger. This helps keep the LLM "focused" on the task, and sticks to best practice about the total number of tools available. The trigger data JSON schema is a great place to add descriptions to the fields of the data payload. This helps the LLM understand the data payload and fill it in accurately. Although the LLM can fill in the trigger data for the workflow, it's best to keep the trigger data JSON schema as simple as possible to aid with accuracy. ## Example prompts for triggering workflows Here are some example prompts for invoking your workflows as tools in your AI Agent: - "Trigger the comment-created workflow for the current user." - "When the task completes, notify the user using the task-completed workflow." - "Send all of the users in the engineering team a welcome message using the welcome-user workflow." ## Human-in-the-loop flows Learn how to use the Knock Agent Toolkit within your AI agent workflows to power rich human-in-the-loop interactions. --- title: Powering human-in-the-loop flows with the Knock Agent Toolkit description: Learn how to use the Knock Agent Toolkit within your AI agent workflows to power rich human-in-the-loop interactions. section: Developer tools --- Knock Agent Toolkit enables you to power rich human-in-the-loop interactions. For example, you can use Agent Toolkit to create a workflow that lets a person to approve or reject work that the agent has done, powered by Knock's cross-channel messaging workflows. Agent Toolkit exposes a set of helper methods that make it trivial for you to wrap your tool calls in asynchronous human approvals or inputs. Knock takes care of the heavy lifting for you, making it easy to build rich human-in-the-loop flows with little effort. ## Use cases Using Knock, it's possible to power human-in-the-loop interactions that: - Send cross-channel notifications to email, SMS, in-app feeds, and workplace chat applications like Slack and MS Teams. - Power advanced cross-channel escalation flows to bubble up messages between channels or across team members. - Solicit structured input such as approve/reject buttons, or unstructured feedback in the form of text or prompt modifications. - Handle long-running async interactions that "interrupt" and "resume" your agent workflows. - Give full visibility into the messages sent from your AI agent workflows and track engagement of those messages across channels. ## How Knock powers human-in-the-loop flows Knock uses [workflows](/concepts/workflows) to power human-in-the-loop messaging across channels. Here's a high-level overview of how this works: 1. You setup a workflow in the Knock dashboard that describes the message to send and the channels to send it to. 2. Your agent calls to Knock to [trigger the workflow](/send-notifications/triggering-workflows/overview) with a set of recipients and any additional context for the request. 3. Your user receives the message on the channel, which includes the context for the request and a set of actions to take. 4. The user then interacts with the message and the result is sent back to Knock. 5. Knock then triggers an [outbound webhook](/developer-tools/outbound-webhooks/overview) back to your agent application with the result of the interaction. 6. Your agent application can then use the incoming interaction event from Knock to resume execution, or respond to the interaction. ## Helper methods Note: The following helper methods are only exposed for the AI SDK. If you're using the Knock Agent Toolkit with another agent framework and need to implement a custom flow, please reach out to us at{" "} support@knock.app. } /> The Knock Agent Toolkit exposes a set of helper methods that you can use to power human-in-the-loop flows for common cases, as well as low-level primitives should you need to implement a custom flow. ### `toolkit.requiresHumanInput` The `requiresHumanInput` helper method allows you to wrap a tool in requiring human input to proceed. When a tool is wrapped with this method, when the LLM tries to invoke the call it will instead delegate the tool call to Knock, passing the tool execution context to the Knock workflow. ```typescript title="Wrap tool in requiring input" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createKnockToolkit(); const { classifyWork: classifyToolRequiringInput } = toolkit.requireHumanInput( { classifyWork }, { // (required) The key of the workflow to trigger workflow: "approve-classification", // (required) The list of recipients to trigger the workflow for recipients: ["user_123"], // (optional) Any additional data to pass along with the request data: {}, }, ); ``` Calling `requireHumanInput` will return a new tool that you pass in place of the original tool in your model. When the LLM invokes the new tool, it will instead delegate the tool call to Knock, passing the tool execution context to the Knock workflow. Note: when the LLM invokes the new tool, it will appear as if the tool-call completed, albeit with a result that indicates that the tool is still waiting for human input. This is expected behavior. } /> ### `toolkit.handleMessageInteracted` The `handleMessageInteracted` helper method allows you to handle an incoming `message.interacted` event from Knock and determine if there's an interaction with the message that should be handled by your agent workflow. ```typescript title="Handling an incoming interaction" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createKnockToolkit(); function handleIncomingInteraction(event) { const interactionEvent = toolkit.handleMessageInteracted(event); if ( interactionEvent && interactionEvent.workflow === "my-approval-workflow" && interactionEvent.interaction.action === "approved" ) { // Do something as a result of the approval } } ``` ### `toolkit.resumeToolExecution` The `resumeToolExecution` helper method allows you to resume tool execution after a human has interacted with the message and you have received an affirmative response to proceed. You pass the `resumeToolExecution` method the `DeferredToolCallInteractionResult` that you've received from Knock via the `handleMessageInteracted` method. Here's an example of how you might use this method: ```typescript title="Resuming tool execution" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createKnockToolkit(); function handleIncomingInteraction(event) { const interactionEvent = toolkit.handleMessageInteracted(event); if (interactionEvent && interactionEvent.hasToolCall) { const toolResult = await toolkit.resumeToolExecution(interactionEvent); // Do something with the tool result } } ``` When using the `resumeToolExecution` method, you must have first registered any tools that require human input using the `requiresHumanInput` method. This ensures that the toolkit has access to the necessary information to resume the tool execution. In the AI SDK, the `resumeToolExecution` method will return a tool status object where you can access the result under the `result` property. The original `toolCallId` is also included. ## Example: In-app approval flow This example shows you how to set up an approval flow that sends an in-app notification to a notification feed requesting approval when the agent wishes to invoke a particular tool. To start, you'll want to setup a Knock workflow with a single, in-app feed channel step that sends an approval message to an in-app feed. You can find a workflow template for this in the workflow templates repository. Next, you'll need to render the in-app feed in your application. You can find steps for implementing this in the [building in-app feed documentation](/in-app-ui/react/feed). By default, your in-app feed will handle the interaction on the "Approve" and "Reject" buttons and send the `message.interacted` event to Knock. A human-in-the-loop interaction is inherently an asynchronous action, and therefore requires that we might need to handle the approval at some later point. Knock uses [outbound webhooks](/developer-tools/outbound-webhooks/overview) to forward your events to your application, helping to implement this asynchronous behavior. To set up an outbound webhook, you'll need to create a new webhook under **Platform** > **Webhooks** in the Knock dashboard. Add a URL for your service and select the `message.interacted` event. You can read more about the available [event types in our documentation](/developer-tools/outbound-webhooks/event-types). The Agent Toolkit exposes a `requiresHumanInput` helper that you can use to wrap a tool call as one that requires human input to proceed. ```typescript title="Wrap a tool as requiring human input" import { createKnockToolkit } from "@knocklabs/agent-toolkit/ai-sdk"; const toolkit = await createKnockToolkit(); const { classificationTool: wrappedClassificationTool } = toolkit.requiresHumanInput({ classificationTool }, { // The workflow to use to send the approval request workflow: "approve-tool-call", // A list of users who should be notified about this request recipients: ["user_123", "user_456"], data: { // (optional) any additional data to send with the request that helps identify it // this data will then be made available to you when receiving the webhook } }); ``` Now, when your agent calls your `classificationTool`, it will instead call your `wrappedClassificationTool` and delegate the tool call to Knock, bringing one or more humans into the agent's loop. Finally, you will need to implement a handler for the `message.interacted` webhook event that fires when someone interacts with the "approve" or "reject" buttons in the notification feed. You'll use this method to handle the incoming interaction event and to resume your agent's execution. Depending on which agent framework you're using, you'll need to implement this handler differently. We've left this example fairly abstract as a result. ```typescript title="Handling the response and resuming tool execution" import { createKnockToolkit, handleMessageInteracted } from "@knocklabs/agent-toolkit/ai-sdk"; const handleMessageInteracted = async (event: MessageInteractedEvent) => { const agent = await getAgentInstance(); const toolkit = await createKnockToolkit(); const interactionEvent = toolkit.handleMessageInteracted(event); if (interactionEvent && interactionEvent.interaction.action === "approved") { const toolResult = await toolkit.resumeToolExecution(interactionEvent); // Resume my agent's execution await agent.resumeLoopWithToolResult(toolResult); } } ``` ## Frequently asked questions No, right now you must setup a URL handler on your server to implement these actions. One possible approach is that you drive the resulting approve or reject click directly to your agent workflow to handle. No, as with email you must implement a webhook handler for these channels where you handle this interaction before sending it back to Knock to proceed. ## Providing context Learn how to configure the Knock Agent Toolkit to use context to aid with tool calls in your AI agent workflows. --- title: Providing context to your Knock Agent Toolkit instance description: Learn how to configure the Knock Agent Toolkit to use context to aid with tool calls in your AI agent workflows. section: Developer tools --- The Knock Agent Toolkit allows you to pass context on initialization to automatically scope requests to Knock. This is useful when you're interacting with a single logged in user, or you already know the organization or user context that you'll be working with. ## Available context - `userId`: The user ID of the user that the agent is interacting with. - `tenantId`: The tenant ID of the organization/workspace/account that the agent is interacting with. ## Setting context You can set context on the `createKnockToolkit` method. ```typescript const toolkit = await createKnockToolkit({ userId: "user_123", tenantId: "tenant_123", }); ``` ## Where context is used Once you've set the `userId` or `tenantId` context, it will be used to automatically scope requests to Knock. This means that you don't need to pass the `userId` or `tenantId` with every request, it will be set automatically. Many of the tools that the Knock Agent Toolkit exposes will use the context as a default value, making it possible to omit the this parameter from the LLM's tool call. For example, when executing a workflow trigger where we have a `userId` set, we can say: `trigger the welcome workflow for the current user` instead of `trigger the welcome workflow for user_123`. Or, as another example when updating a user's profile, we can say: `update the profile for the current user` instead of `update the profile for user_123`. ## Working with environments Learn how the Knock Agent Toolkit operates across multiple environments in your Knock account. --- title: Working with environments in the Knock Agent Toolkit description: Learn how the Knock Agent Toolkit operates across multiple environments in your Knock account. section: Developer tools --- The Knock Agent Toolkit allows you to work across multiple Knock environments. By default, the toolkit will operate in the `development` environment for all mutation operations. There are times however where you may want your agent to be able to operate across multiple environments, like when the agent has access to creating workflows in one environment and also executing those workflows in a different environment. ## Setting the environment on initialization If you want to constrain your agent to operate in a specific Knock environment, you can set the environment on initialization. By default this will be the `development` environment. ```typescript const toolkit = await createKnockToolkit({ environment: "production", }); ``` ## Setting the environment in a prompt If you want to allow the Agent to operate across multiple environments, you can also set the environment in a prompt: ```plaintext You are operating in the Knock production environment. Trigger the welcome workflow for the current user. ``` This will ensure that the `environment` parameter is passed to the trigger workflow tool call, forcing the workflow to be triggered in the `production` environment. ## Workflows-as-tools and environments If you're using workflows-as-tools, it's recommended that you set the environment on initialization of the toolkit such that the Agent can only find and trigger workflows in a specific environment. ## Tools reference Learn more about the tools available in the Knock Agent Toolkit. --- title: Tools reference description: Learn more about the tools available in the Knock Agent Toolkit. section: Developer tools --- The Knock Agent Toolkit exposes a suite of tools that your AI agents can invoke to power cross-channel user messaging, without any integration logic. You can find the full list of available tools below. ## Channels (`channels`) - `listChannels`: List all channels in your Knock account ## Commits (`commits`) - `listCommits`: List all commits in a single environment - `commitAllChanges`: Commit all changes in an environment - `promoteAllCommits`: Promote all commits from one environment to another ## Documentation (`documentation`) - `searchDocumentation`: Perform a search of the Knock documentation ## Email layouts (`emailLayouts`) - `listEmailLayouts`: List all email layouts in an environment - `getEmailLayout`: Get an email layout by its key - `createOrUpdateEmailLayout`: Create or update an email layout in an environment ## Environments (`environments`) - `listEnvironments`: List all environments in your Knock account ## Guides (`guides`) - `listGuides`: List all guides available for the given environment - `getGuide`: Get a guide by its key - `createOrUpdateGuide`: Create or update a guide ## Message types (`messageTypes`) - `listMessageTypes`: List all message types in an environment - `createOrUpdateMessageType`: Create or update a message type in an environment ## Messages (`messages`) - `getMessage`: Get a message by its ID - `getMessageContent`: Get the content of a sent message - `getMessageDeliveryLogs`: Get the delivery logs for a message - `getMessageEvents`: Get the event timeline for a message ## Objects (`objects`) - `listObjects`: List all objects in a collection in an environment - `getObject`: Get an object in a collection by its id - `createOrUpdateObject`: Create or update an object in a collection - `subscribeUsersToObject`: Subscribe one or more users to an object in a collection - `unsubscribeUsersFromObject`: Unsubscribe one or more users from an object in a collection ## Partials (`partials`) - `listPartials`: List all partials in an environment - `getPartial`: Get a partial by its key - `createOrUpdatePartial`: Create or update a partial in an environment ## Tenants (`tenants`) - `listTenants`: List all tenants in an environment - `getTenant`: Get a tenant by its key - `createOrUpdateTenant`: Create or update a tenant in an environment ## Users (`users`) - `getUser`: Get a user by their id - `createOrUpdateUser`: Create or update a user in an environment - `getUserPreferences`: Get the preferences for a user - `setUserPreferences`: Set the preferences for a user - `getUserMessages`: Get the messages for a user ## Workflows (`workflows`) - `listWorkflows`: List all workflows in an environment - `getWorkflow`: Get a workflow by its key - `triggerWorkflow`: Trigger a workflow by its key - `createWorkflow`: Create a new workflow - `createOneOffWorkflowSchedule`: Create a one-off schedule for a user to trigger a workflow ## Workflow steps (`workflows`) - `createOrUpdateEmailStepInWorkflow`: Create or update an email step in a workflow - `createOrUpdateDelayStepInWorkflow`: Create or update a delay step in a workflow - `createOrUpdateBatchStepInWorkflow`: Create or update a batch step in a workflow - `createOrUpdateInAppFeedStepInWorkflow`: Create or update an in-app feed step in a workflow - `createOrUpdatePushStepInWorkflow`: Create or update a push notification step in a workflow - `createOrUpdateSmsStepInWorkflow`: Create or update an SMS step in a workflow - `createOrUpdateChatStepInWorkflow`: Create or update a chat step in a workflow # Building with LLMs Use LLMs to assist in integrating Knock into your application. --- title: Building with LLMs description: Use LLMs to assist in integrating Knock into your application. section: Developer tools --- You can use large language models (LLMs) to assist in integrating Knock into your application. We provide a set of tools to help you if you're using an LLM in your integration, like when using an AI-assisted editor such as Cursor, VS Code with Copilot, or Windsurf. ## Plain text docs Every page on our docs site is accessible as a plain text file by appending a `.md` extension. For example, this page is accessible as [`building-with-llms.md`](/developer-tools/building-with-llms.md). Our plain text pages are useful to feed to an LLM when building your integration. We also host an [`/llms.txt`](https://docs.knock.app/llms.txt) and [`/llms-full.txt`](https://docs.knock.app/llms-full.txt) files which instructs AI tools and agents how to retrieve the plain text versions of our pages. ## Knock Model Context Protocol (MCP) Server We ship an MCP server that exposes the primitives of Knock to LLMs and AI agents via the Model Context Protocol. You can use the Knock MCP server to aid in building your Knock integration, and to also integrate Knock into any MCP client compatible agent applications. Learn more in our [MCP Server](/ai/mcp-server) docs. ## Skills Skills are packaged instructions and rules that extend AI agent capabilities with Knock-specific knowledge. Install a skill once and your agent automatically applies the right patterns when working on Knock-related tasks. Learn more in our [skills](/ai/skills) docs. ## Knock Agent Toolkit SDK We provide an Agent Toolkit that allows you to integrate Knock via function calling to AI agent workflows. Using Agent Toolkit gives your AI agents the ability to send cross-channel messaging to your customers, as well as powering human-in-the-loop interactions. Learn more in our [Agent Toolkit](/developer-tools/agent-toolkit/overview) docs. --- # Overview Tutorials for using Knock --- title: Tutorials description: Tutorials for using Knock tags: [ "alert", "alerts", "alerting", "tutorial", "quickstart", "implementation guide", "how to", ] section: Tutorials --- # Implementing Knock Learn how to plan and execute your Knock integration from start to finish. --- title: Implementing Knock description: Learn how to plan and execute your Knock integration from start to finish. tags: ["migrate", "migration", "implementation", "building", "planning"] section: Tutorials --- Knock’s APIs and developer tools make it easy to migrate your notification templates, delivery logic, recipient data, and user preferences into Knock. In this tutorial, we will walk you through planning and executing your Knock integration from start to finish. If you’re new to Knock, we recommend taking a look at our [What is Knock?](/getting-started/what-is-knock) page to learn more about our product and how it can be used. You may also want to take a closer look at various [Concepts](/concepts/overview) as you familiarize yourself with the product. ## Migrating data into Knock to power your notifications Using Knock as your notifications service means storing all of the logic for your notifications (the who, when, and where of delivery) in Knock. We offer APIs and developer tools that make your migration a smooth and efficient process: - A [Management API](/developer-tools/management-api) that allows you to work programmatically with the same resources that you create in your Knock dashboard (like [Workflows](/concepts/workflows) and their associated message templates, email [Layouts](/integrations/email/layouts), and [Translations](/template-editor/translations)). - A command line interface ([Knock CLI](/developer-tools/knock-cli)) that wraps the Management API, allowing you to work with your dashboard resources from the command line while you’re developing. - [Bulk endpoints](/api-reference/overview/bulk-endpoints) to upsert large amounts of data in a single API request (more on specific endpoints below). Knock uses the concept of logically-separated{" "} environments in order to ensure that development occurs separately from your production environment and that sensitive user data only exists in production. Your Knock account comes with two environments (Development and Production) by default, but you can add custom environments between them if you need additional environments in Knock to mirror your own development lifecycle. Keep the environment model in mind when planning the migration for each of the resources below. } /> While there is no one-size-fits-all approach to planning a migration to Knock, we recommend the following order as a starting point to ensure that any resources which are dependencies of other resources are migrated first: The first thing you’ll need to do in order to send notifications through Knock is to set up the downstream providers that deliver your messages. In Knock, we refer to these services as [Channels](/concepts/channels). Channels are configured under the **Channels and sources** page in your dashboard account settings. You can see a full list of supported channel types and providers [here](/integrations/overview). In addition to first-party integrations with message delivery platforms, Knock also offers convenient connections to customer data platforms (CDPs) and reverse ETL providers to bring your data into Knock ([Sources](/integrations/sources/overview)). You’ll want to configure your channels prior to building any workflows so that you can set the correct delivery methods for each of your notifications. Next, you’ll start building your [Workflows](/designing-workflows/overview). Workflows in Knock serve as containers for all of the logic and message templates associated with a given notification in your system. When you’re ready to start sending notifications, you’ll do so by [triggering](/send-notifications/triggering-workflows) these workflows. Sometimes circumstances will require an incremental migration of your notifications into Knock while maintaining your legacy notification system. In these cases, we recommend migrating your notifications by individual use cases/events rather than all use cases for a single delivery channel at a time. The latter approach will often require triggering both your legacy system and Knock for a single event in order to notify your users in the correct places, which can be difficult to maintain and iterate on as you work to complete your migration. } /> With Knock’s environment model, you can either create resources directly in your production environment, or create them in your development environment and promote them to production. Learn more about [environments](/version-control/environments). You can assign one or more [categories](/concepts/workflows#workflow-categories) to your workflows. These can be used to power recipient preferences (which we will cover in more detail below). Workflows are constructed from a trigger step, channel steps, and optional function steps. To learn more about creating the messages that are delivered by your workflows, you’ll want to become familiar with the building blocks of your notifications content: - [Message template editor](/template-editor/overview). The message template editor is where you’ll build the content of your notifications. You can use [Variables](/concepts/variables) to inject dynamic content at runtime. - [Partials](/template-editor/partials). Partials are content blocks that you can reuse across multiple templates. - [Layouts](/integrations/email/layouts). Layouts are the "frame" of your email notifications, where you define shared structure and styles. - [Translations](/template-editor/translations). Send message content in a user's preferred language with translations. Workflows and their building blocks can be created in your Knock dashboard or programmatically with our [Management API](/mapi). Once you’ve configured the logic of the notifications that you’d like to send, you’ll need to give Knock the necessary data about your users in order to deliver those notifications. The [User](/concepts/users) object in Knock has a number of (optional) reserved attributes like `name`, `email`, and `phone_number`, but it can also store any number of custom properties which can be used in the logic and templates of your workflow; for example, you might want to deliver different messages based on a user’s `role`. These attributes can be updated at any time with subsequent upserts. We offer a variety of ways to identify your users to Knock, so you'll want to look over our documentation on [identifying recipients](/managing-recipients/identifying-recipients) before you solidify your migration plan. If you're planning to send notifications to [push](/integrations/push/overview) or [chat](/integrations/chat/overview) channels, you'll also need to review our documentation on [setting channel data](/managing-recipients/setting-channel-data) to ensure that we can deliver your messages to the right place. Remember, the Knock environment model means that you’ll need to identify production users directly into your Knock Production environment. After you’ve planned the migration of users into Knock, you may need to consider advanced use cases for non-user recipients (like a public Slack channel) or a resource in your system (like an order that has been placed) that doesn’t fit neatly under the concept of a “user.” In Knock, these resources can be modeled as [Objects](/concepts/objects). [Subscriptions](/concepts/subscriptions) are an extension of objects and express the relationship between a [recipient](/concepts/recipients) (the subscriber) and an object. When you trigger a notification to an object recipient, Knock will also fan out individual workflow runs to **any recipients that are subscribed to the object**. This is especially useful for examples like the order placement use case mentioned above: you can trigger an “order updates” workflow with an object that represents the order as the recipient, and we’ll notify any subscribed Users about the change without you needing to resolve the list of recipients in your system. If your notifications should be scoped to a particular workspace or organization, you’ll need to implement [Tenants](/multi-tenancy/overview) in your Knock integration. A `tenant` can be applied as context to a workflow trigger in order to [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). Per-tenant branding and per-tenant preferences are features of our{" "} Enterprise plan . If you’d like to find out more information about Enterprise plan features and pricing, please contact us at sales@knock.app . } /> Once your recipients (both users and objects) and tenants have been migrated to Knock, you’ll want to consider delivery preferences for your notifications to give your users control of where and when they receive updates from your product. For more information on building a preference center in your app, check out the section on [Completing your client-side integration](#completing-your-client-side-integration) below. Knock’s powerful [Preferences](/preferences/overview) API allows your users to opt out of notifications based on the notification’s delivery `channel_type`, the `category` of the notification, the specific notification `workflow`, or a combination of these properties. You can also extend these preferences to be [tenant-specific](/multi-tenancy/per-tenant-preferences) or to [evaluate conditionally](/preferences/preference-conditions) at runtime. You can set environment-level default preferences (for example, maybe a given workflow should require a user to manually opt in to receive those notifications) as well as tenant-specific default preferences that will be overridden by a recipient’s individual preferences. Finally, for any notifications that you’ll send on a recurring basis or reminders that should be sent at a specific future date, our [Schedules](/concepts/schedules) feature should be considered before you’re ready to finalize your integration. Schedules can be set, updated, and deleted via API. Although they are recipient-specific, you can set the same schedule for up to 100 recipients at a time by providing a list of recipients. To set unique schedules for multiple recipients, you can create up to 1,000 schedules at a time with the bulk endpoint. For more information and in-depth guidance on specific use cases, please take a look at our [example apps](/getting-started/example-apps) and the following tutorials: - [Alerting](/tutorials/alerting) - [Customer-facing webhooks](/tutorials/customer-webhooks) - [Recurring digests](/tutorials/building-recurring-digests) - [Modeling Users, Objects, and Tenants](/tutorials/modeling-users-objects-and-tenants) ## Completing your client-side integration Certain implementations will require some client-side work to [build in-app UI](/in-app-ui/overview) in order to complete your integration. Knock provides [out of the box UI components](/in-app-ui/react/overview#pre-built-components) that you can use, but you can also [implement our in-app feed API and React hooks in a headless way](/in-app-ui/react/custom-notifications-ui) if you’d like to bring your own components and styles. Here are some use cases that will require additional client-side planning: - **In-app messaging.** If you’re planning to use Knock-powered in-app messaging (whether that’s a notification feed for web or mobile, other in-product notifications like modals and banners, or custom components powered by our [Message types](/in-app-ui/message-types) feature), you’ll need to build a way to display those messages to your users. For more information on your preferred language or framework, visit the **Building In-app UI** section of our navigation menu. - **User preferences.** To power [user preferences](/preferences/overview) in Knock, give your users a way to control the notifications they receive. You can [build a preference center](/preferences/custom-preference-center) in your application, or use Knock's [hosted preference center](/preferences/hosted-preference-center) as a no-code alternative. - **Chat app authentication.** Some delivery channels like Slack require a way for your users to authenticate your app or bot into their workspace. Knock provides drop-in React components ([SlackKit](/in-app-ui/react/slack-kit) and [TeamsKit](/in-app-ui/react/teams-kit)) for both Slack and Microsoft Teams that will help manage the process of authentication and storing the necessary information (access tokens, etc.) as [channel data](/managing-recipients/setting-channel-data) in Knock so that we can deliver your notifications. ## Testing and observability Testing your notifications and insight into any errors that occur are a key part of developing and deploying your Knock integration. Knock offers many tools and resources to help you ensure that your users are receiving the right messages at the right time: - Documentation on tools for [testing](/send-notifications/testing-workflows) and [debugging](/send-notifications/debugging-workflows) workflows. - A [Postman collection](/developer-tools/knock-and-postman) to test against our API. - An optional [sandbox mode](/integrations/overview#sandbox-mode) on each of your channels that allows you to generate and preview messages without sending them to your downstream provider for delivery. - [Delivery and engagement statuses](/send-notifications/message-statuses) for all of the notifications sent through Knock. - [Outbound webhooks](/developer-tools/outbound-webhooks/overview) for real-time updates on events that occur within Knock. - An **Analytics** page in your Knock dashboard for insight into the messages you’re sending across different workflows and delivery channels. In addition to the tools listed above,{" "} Enterprise plan {" "} customers have access to{" "} Extensions, our integrations with downstream providers that enable advanced observability and analytics for your notifications. If you’d like to find out more information about Enterprise plan features and pricing, please contact us at sales@knock.app. } /> ## Going to production You’re finally ready to move to production – congratulations! Before you flip the switch and sit back to admire your work, here are a few last-minute items to check off your list: - Be sure to update your application’s [API keys](/developer-tools/api-keys) to point to your Knock Production environment. - Ensure that all of your work has been [promoted](/concepts/commits#promoting-commits) to your Production environment. - If you’re using in-app messaging, [generate a signing key](/in-app-ui/security-and-authentication#1-generate-the-signing-key) and enable enhanced security mode for client-side requests in your Production environment. Both of these actions can be completed by navigating to **Platform** > **API Keys** in your Knock dashboard’s Production environment. As always, we’re here to help. If you have any questions or run into issues as you build with our product, we hope you’ll let us know! Email us at support@knock.app and we’ll be more than happy to assist you. Knock on. 🤘 # Integrating into CI/CD Learn how to add Knock to your deployment pipeline with our command line interface. --- title: Adding Knock to your CI/CD pipeline description: Learn how to add Knock to your deployment pipeline with our command line interface. tags: [ "CI/CD", "cicd", "integration", "deployment", "automation", "testing", "commit", "branches", "GitHub Actions", ] section: Tutorials --- With the [Knock CLI](/developer-tools/knock-cli), you can add Knock directly into your existing CI/CD pipeline to automate how notification changes move through your Knock environments alongside your application code. This tutorial walks through a GitHub Actions setup built around [Knock branches](/version-control/branches), Knock's way of isolating in-progress changes. The workflow mirrors a standard development cycle: 1. A feature branch in Git maps to a Knock branch. When you push changes to `.knock/`, they sync automatically to the matching Knock branch. 2. When a pull request is merged, the Knock branch merges into your development environment and changes are promoted to staging. 3. When you're ready to ship, a production deploy promotes changes from staging to production in Knock. This tutorial assumes you have [installed the Knock CLI](/cli/overview/installation) and have your Knock resources checked in to a `.knock` directory in your repository. It also assumes that you have [created](/version-control/environments#create-additional-environments) a "Staging" environment in Knock in addition to the Development and Production environments that are provided by default. ## Local development Use the `knock pull` [command](/cli/pull) to download your current Knock resources from the dashboard and develop them locally. When starting a new feature, create a matching Knock branch and switch to it: ```bash title="Switching to a new Knock branch" knock branch switch my-feature --create ``` As you make changes, push and commit them to your Knock branch: ```bash title="Committing changes to a Knock branch" knock push --branch=my-feature --commit -m "Update welcome email template" ``` As with working directly in your dashboard, any uncommitted changes pushed to Knock can be overwritten by another user working on the same resource. Always commit changes you want to persist, even when working on a branch. } /> ## Automating with GitHub Actions The following workflows automate your Knock updates throughout the full development lifecycle: syncing changes as you push, merging and promoting when a PR lands, promoting to production on deploy, and cleaning up branches when they're removed. ### Setting up credentials Each GitHub Action below requires a `KNOCK_SERVICE_TOKEN` secret. You can generate a service token in the Knock dashboard under **Settings > Service tokens**. Add it to your GitHub repository under **Settings > Secrets and variables > Actions**. ### Sync changes on push to a feature branch This workflow runs whenever you push changes to a non-`main` branch that touch your `.knock` directory. It creates the matching Knock branch if it doesn't already exist, then pushes and commits your changes to it. The Git commit SHA is included in the Knock commit message, so you can correlate Knock commits back to your Git history using [`knock commit list`](/cli/commit/list). ```yaml title="Sync changes to a Knock branch on push" name: Sync Knock branch on: push: branches-ignore: - main paths: - ".knock/**" jobs: sync-knock-branch: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" # The Knock CLI requires a Node.js version >= 20.19.0 - name: Install Knock CLI run: npm install -g @knocklabs/cli - name: Switch to Knock branch env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} run: knock branch switch ${{ github.ref_name }} --create --force - name: Push and commit changes env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} run: | knock push --branch=${{ github.ref_name }} --knock-dir=.knock --commit \ -m "Sync with git commit ${GITHUB_SHA:0:7}" ``` ### Merge to development and promote to staging on PR merge This workflow runs when a pull request targeting `main` that includes changes to `.knock` is merged. It merges the corresponding Knock branch into your development environment, then promotes those changes to staging in the same job. Running both steps sequentially in a single workflow ensures your Knock resources reach staging along with your application deployment. By default, knock branch merge deletes the Knock branch after merging. Alternatively, you can pass the --no-delete flag to preserve branches until the Git branch is explicitly removed, then use the{" "} cleanup workflow below to delete the Knock branch after it's merged. } /> ```yaml title="Merge Knock branch and promote changes to staging on PR merge" name: Merge Knock branch and promote to staging on: pull_request: types: - closed branches: - main paths: - ".knock/**" jobs: merge-and-promote: runs-on: ubuntu-latest if: github.event.pull_request.merged == true steps: - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" # The Knock CLI requires a Node.js version >= 20.19.0 - name: Install Knock CLI run: npm install -g @knocklabs/cli - name: Merge Knock branch into development env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} run: knock branch merge ${{ github.event.pull_request.head.ref }} --force - name: Promote to staging env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} run: knock commit promote --to=staging ``` ### Promote to production on deploy Once you've verified staging, promote your Knock changes to production. Tie this to whatever event represents a production deploy in your pipeline: a published release, a manual workflow dispatch, or a push to a release branch are all common choices. Using a deliberate trigger (rather than promoting automatically on PR merge) gives you a chance to verify staging before changes reach production. If you need to re-promote an unchanged resource across environments (for example, to unblock a stale promotion diff), you can create an [empty commit](/version-control/commits#empty-commits) before promoting: ```bash title="Create an empty commit for a workflow" knock commit \ --resource-type=workflow \ --resource-id=my-workflow \ --allow-empty \ -m "Empty touch for promotion" ``` ```yaml title="Promote changes to your Knock production environment on deploy" name: Promote Knock changes to production on: release: types: - published jobs: promote-to-production: runs-on: ubuntu-latest steps: - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" # The Knock CLI requires a Node.js version >= 20.19.0 - name: Install Knock CLI run: npm install -g @knocklabs/cli - name: Promote to production env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} run: knock commit promote --to=production ``` The knock commit promote{" "} command promotes all committed changes from the environment immediately below the target in your environment order. Keep this in mind if you have teams working in the dashboard in addition to your codebase. It's possible to scope promotion to a single commit using the --only flag.

To view the current order of your environments, navigate to Settings > Environments in your Knock dashboard. } /> ### Clean up deleted branches This workflow deletes a Knock branch when its corresponding Git branch is removed, keeping your Knock account in sync with your repository. ```yaml title="Delete Knock branch when a Git branch is removed" name: Delete Knock branch on: delete: jobs: delete-knock-branch: runs-on: ubuntu-latest # Only run for branch deletions, not tag deletions if: github.event.ref_type == 'branch' steps: - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" # The Knock CLI requires a Node.js version >= 20.19.0 - name: Install Knock CLI run: npm install -g @knocklabs/cli - name: Delete Knock branch env: KNOCK_SERVICE_TOKEN: ${{ secrets.KNOCK_SERVICE_TOKEN }} # Use `|| true` to avoid failing the workflow if the branch doesn't exist run: knock branch delete ${{ github.event.ref }} --force || true ``` ## Handling reverts The standard Git revert flow works naturally with the branch-based setup above. When a developer runs `git revert` and opens a pull request, the sync workflow pushes the reverted `.knock/` state to a new Knock branch. Merging that PR triggers the merge-and-promote workflow, landing the corrected state in development and staging automatically. For situations where something has already reached production and you need to recover before a revert PR can land, you can use Knock's [revert feature](/version-control/commits#reverting-a-commit) directly in the dashboard from the **Commits** page, then promote the resulting commit through your environments. Alternatively, you can force-push a known good state from the command line using the Git SHA recorded in your Knock commit messages: ```bash title="Reverting to a known good state" # Check out the last known good .knock/ state from git history git checkout -- .knock/ # Overwrite the current state in Knock and promote knock push --force --commit -m "Revert to git commit " knock commit promote --to=staging knock commit promote --to=production ``` # Alerting Learn how to power cross-channel, configurable alerts using Knock. --- title: Powering cross-channel configurable alerts with Knock description: Learn how to power cross-channel, configurable alerts using Knock. tags: ["alert", "alerts", "alerting"] section: Tutorials --- In this documentation, we’ll cover some best practices in creating alerting-style workflows in Knock. Alerts are common in many types of infrastructure tools, like Datadog or Honeycomb, as well as in product management and data tools like Amplitude or Mixpanel. Check out{" "} our example app {" "} to see how you can build configurable alerts with Knock. This app covers creating alerts, configuring recipients, and building a UI to allow users to manage their channels, events, and batching behavior. } /> Here are some assumptions we’ll make about configurable alerts for the purposes of this tutorial: 1. The user should be able to pick the channels (like email, SMS, and in-app) that the workflow will use to notify recipients. 2. The user can define the list of recipients who will receive these notifications. 3. The user can also create additional dimensions to determine whether an alert should be triggered, like a list of event types or a particular usage threshold. In the following steps, we’ll break down this process to help you understand how to create your own alerts. Since Knock can be used flexibly and adopted incrementally, we’ll examine two scenarios for how you can model alerts: modeling alerts in your system vs. modeling them in Knock. ## Modeling alerts in your system Let’s start with how you would power alerts modeled in your system using Knock to coordinate your cross-channel notification logic. In the code sample below, you can see an example `alert` entity that might be stored in your database. After querying that entity, you pass the `alert` configuration and `event` type in the `data` payload for your workflow trigger and specify any `recipients` stored on the `alert` itself. ```javascript title="Trigger an alert from your system" const alert = { id: "alert_1YQ4XR18", channels: ["email", "sms", "in_app"], events: ["sever:warn", "server:info"], recipients: [ { id: "user_391d92cd", }, ], batchWindow: { frequency: "weekly", days: ["fri"], hours: 17, }, }; await knock.workflows.trigger("alert", { data: { alert, event: "server:warn", }, recipients: alert.recipients.map((r) => r.id), }); ``` When you look at this code sample, you’ll see that there’s nothing fundamentally different about triggering an alert than triggering any other type of notification. You pass in data to be used in the workflow itself as well as all of the recipient references. You can then use the data in individual channel and function steps to drive the logic of your workflow. In the next step of this documentation, we’ll explore how you can offload more of this modeling to Knock, but it’s worth discussing the pros and cons of these two approaches. ### Benefits of modeling in your system **Pros** - Your alerting data model lives in your system and remains the source of truth around alerts. In some cases, this may be a more natural place for it, depending on the data you’re storing and how you’re using the alerts in your system - Less bookkeeping; the only call to Knock is when the alert is triggered **Cons** - Sending to a large number of recipients requires making multiple calls to Knock (recipients are capped at 1000 per trigger) - You have to fetch and pass the alert object to Knock on every alert invocation - More complex to send to a non-user recipient for a channel such as Slack or Microsoft Teams ### Benefits of modeling in Knock **Pros** - Knock manages all aspects of your alerting data model and becomes the source of truth for alerts and users subscribed to them - Object subscriptions work well when you have a large set of recipients and don’t want to send the full list to Knock - Simple to model non-user recipient channels (like Slack) that can be connected directly to the alert object itself **Cons** - More bookkeeping; you have to update Knock when a model changes or a user is added or removed from an alert Since there is no one-size-fits-all approach here, you’ll need to weigh these pros and cons for your own use case to determine how much of your data model you want to bring into Knock. Knock works best when it’s loosely coupled to your system, so it’s helpful to consider how often your entities will change and how tightly your recipient lists are coupled with other parts of your organizational model. ## Modeling alerts as Knock Objects The first step in creating an alerting system with Knock as the source of truth involves modeling your alert as an [Object](/concepts/objects). Objects allow you to represent non-user recipients and create a relationship with users through subscriptions, which we’ll cover in the next step. ```javascript title="Create an Object to represent the alert and config" const alert = await knock.objects.set("alert", alertId, { channels: ["email", "sms", "in_app"], events: ["maintenance", "compliance", "driver"], batchWindow: null, }); ``` Using custom properties, you can store additional information directly on the Object, like the `channels` that should be used in the alert, or the `events` that the alert is configured for. This allows you to encode the conditional logic of where and when a notification should send directly into Knock. Using the `batchWindow` property, you could even store [a custom window rule](/designing-workflows/batch-function#set-a-dynamic-batch-window-using-a-variable) on the alert object to represent how it should batch notifications. ### Subscribe Users to the alert Once you have an Object modeled to store your alert data, you can add individual recipient [subscriptions](/concepts/subscriptions) to your alert object. This allows you to fan out to an unlimited number of recipients while still triggering a single workflow. Subscriptions can also hold their [own unique properties](/concepts/subscriptions#subscribing-recipients-to-an-object) that can be accessed during a workflow run. For example, users could select their own `channels`, `events`, or `batchWindow` if they wanted to and store them as properties of the subscription. ```javascript title="Subscribe a user to an alert Object" knock.objects.addSubscriptions("alerts", alertId, { recipients: ["user_79bc96a9", "user_JG9NGAJQ", "user_391d92cd"], properties: { channels: ["email", "in_app"], events: ["maintenance", "compliance"], batchWindow: { frequency: "weekly", days: ["fri"], hours: 17, }, }, }); ``` Once you've stored these properties on a subscription, you can access them inside the workflow run on `recipient.subscription.channel` or `recipient.subscription.events` for use in either templates or step conditions. This allows you to create a highly configurable alerting system that can be customized on a per-user basis. ### Triggering an alert To trigger an alert workflow, your application would only need the `id` of the alert you want to trigger. From there, you would fetch the `alert` Object from Knock (we’re working on some ways to more easily reference objects inside of workflow runs). Then you trigger the alerting workflow using the Object as a recipient. You’ll also want to pass the entire `alert` in the `data` payload as well as an `event` type. ```javascript title="Trigger an alert workflow" //Fetch the alert and config, we're working on a way to resolve this in the workflow const alert = await knock.objects.get("alerts", "alertId"); //Trigger the workflow with the Object as recipient to fan out to subscribers await knock.workflows.trigger("alert", { recipients: [{ collection: "alerts", id: alert.id }], data: { alert, event: "maintenance", }, }); ``` When Knock processes this workflow run, it will run for the Object as a recipient and will also fan out to all of its subscribers and process a workflow run for each User. Since we passed in the `alert` and `event` as payload data, those values will be present on every recipient run of the workflow. That means we can access the properties stored on our `alert` object to make decisions about whether or not to send a notification on specific channels. ## Modeling a workflow for alerting Now let’s look at what a workflow might look like to support this use case. You can clone this workflow directly into your account from the workflow template gallery. The template gallery can be accessed from the "Create workflow" modal. You can find this workflow under the "Alerts" title under the "Monitoring" category. } /> A Knock workflow encapsulates _a single type of notification_ in your system and comprises the cross-channel orchestration logic and associated behavior for how a notification should be sent to a recipient. For our alert workflow, we find it’s best to have a single workflow that has all possible channels that an alert could be sent to. We can then use trigger conditions per-channel step to have a given alert's configuration determine whether a particular channel should be used for a given recipient. ### Deciding whether to run the workflow Since our application lets alerts subscribe to specific events, we’ll also want to ignore any events that the alert isn’t configured for. To do that, we can use a [Trigger Step Condition](/designing-workflows/step-conditions#trigger-step-conditions) to evaluate if `data.alerts.events` contains `data.event`. If it does, then we can continue to the next step. If not, we can halt the workflow immediately. Knock’s [conditions model](/concepts/conditions) is very expressive, so it’s also possible to represent conditions other than basic string matching. For example, if you had an alert property like `usageThreshold` you could create a greater than comparison to a piece of data in the payload. ### Deciding when to send notifications Next, we might want to either send notifications immediately or open a batch to catch multiple notifications of the same type. We can do that with [a branch step](/designing-workflows/branch-function), where we examine whether or not there is a value for the batch window in `data.alert.batchWindow`. If there is, we’ll proceed to a batch step. If not, we’ll execute channel steps immediately. ### Deciding how to batch notifications Let’s take a look at the batching example first. When using the batch function, Knock provides multiple ways to express when a batch window should close using [a dynamic batch window](/designing-workflows/batch-function#set-a-dynamic-batch-window-using-a-variable). All batches are automatically created on a per-recipient basis, so in the case of a fan-out like we have here, a batch will be created for each recipient subscribed to the alert. You might also want to create a particular batch per event type. You can do that by using a [batch key](/designing-workflows/batch-function#selecting-a-batch-key), which in this case could be `data.event`. As you trigger alerting events in your app, Knock will use this key to open a batch per-recipient and per-event. ### Deciding which channels to use Lastly, let’s look at how you would decide which channels to send notifications to. When you configured your `alert` you stored an array of `channels` on the Object. As the workflow processes each channel step you can use [a step condition](/designing-workflows/step-conditions) to evaluate whether the current channel type exists in the array of configured channels: `data.alert.channels contains "sms"`. If that condition doesn’t evaluate to `true`, that particular channel step is skipped. ### Interacting with user preferences Up until now, much of the configuration on when and where to send notifications has existed in the `alert` object we created. But since users have [preferences](/preferences/overview) as well, it’s possible that those preferences interact with some values configured in the `alert` itself. For example, even if the `alert` is configured to send email, if a user has opted out of the email channel, they will not get that notification. There are a few ways to look at preferences in this context: 1. Developers control [which preferences](/preferences/overview#how-preferences-work) are exposed to the user, so it’s worth considering how you want to create a preference center and at what level of granularity you want users to be able to opt out. For example, you may just never want to expose global `channel_type` preference settings and instead expose `workflow` or `category` settings. In this case, just omitting a setting for your alerting workflow means users can’t opt out. 2. Developers can always [override user preferences](/preferences/overview#advanced-concepts) at the workflow level in the dashboard. Enabling this option means that every message sent from that particular workflow will override preferences set by the user. 3. You can always [examine the preferences](/preferences/overview#preference-evaluation-rules) Knock evaluates on a given recipient in the workflow debugger. # Customer-facing webhooks Learn how to use Knock to send per-customer configurable webhooks as part of your notification workflows. --- title: Building customer-facing, configurable webhooks with Knock description: Learn how to use Knock to send per-customer configurable webhooks as part of your notification workflows. tags: ["webhooks", "webhooks as a service"] section: Tutorials --- In this tutorial, we'll walk through how Knock can be used to send per-customer configurable webhooks as part of your notification workflows. Check out{" "} our example app {" "} to see how you can build customer-facing webhooks UI with Knock. This app covers configuring webhooks connections, testing webhooks, and building UI to allow developers to debug webhook delivery. } /> ## Using objects and subscriptions to power webhooks Let's take a hypothetical application comprised of projects that users belong to. Our customers can configure webhooks for each of their project. We can express this as “a project can have one or more configured webhooks”. In Knock, we’ll model this as follows: - Every customer configured webhook will be an [object](/concepts/objects) in Knock that will house the webhook configuration. - All webhooks a customer configures will belong to a single `project` object using [object subscriptions](/concepts/subscriptions) to express the relationship between the project and the webhooks configured for that project. First we’ll upsert the project as an object in Knock. We’re putting the project under a `projects` collection. ```js title="Upsert our project object" const project = await knock.objects.set("projects", "project-1", { name: "My project", }); ``` Now, when a customer creates or updates a webhook in our system we’ll upsert a corresponding `project_webhooks` object and associate it back to the project object via a subscription. ```js title="Creating a webhook object and subscribing it to our project object" // Generate a webhook ID const webhookId = "some-unique-id"; // Create the webhook object with the configuration const projectWebhook = await knock.objects.set("project_webhooks", webhookId, { events: ["project:created"], url: "https://some-url.com/incoming/webhook", }); // Associate the webhook with the project await knock.objects.addSubscriptions("projects", "project-1", { recipients: [{ collection: "project_webhooks", id: webhookId }], }); ``` If at any point we need to list all of the configured webhooks for a given project, we can do so by listing the subscriptions for the project: ```js title="List webhooks for a given project" const { entries: webhookSubscriptions } = await knock.objects.listSubscriptions( "projects", "project-1", ); ``` ## Configuring an HTTP channel Now we’re going to configure our Webhook HTTP channel in Knock. You can learn how to configure a Webhook HTTP channel in our [webhook channel overview](/integrations/webhook/overview). Next, we’ll configure the webhook request for our new webhook channel. For the channel URL we’re going to configure it as `{{ recipient.url }}` which tells Knock to use the URL configured on the webhook recipient object: And for the body of the payload that we send per webhook event, we’ll use the following template: ```js title="Configure the webhook channel payload" { "type": "{{eventType}}", "payload": {{payload | json}}, "createdAt": "{{timestamp}}" } ``` If you are passing a JSON payload to the webhook, you can use the `json` filter to ensure that the payload is correctly serialized. ## Building your webhook notification workflow Now we can build our notification workflow that sends out a webhook when the customer has a webhook configured for the object. To get started: 1. Create a new workflow in Knock 2. Add a webhook channel step to your workflow 3. Configure the step to use your customer webhook channel 4. Save and commit your workflow We don't need to customize any of the data sent in the webhook channel, so we can keep everything as-is with the default configuration. ## Triggering your webhook notification workflow Now our workflow is configured, we can trigger it via the API. We’ll pass the `project` as a recipient to the workflow, which will automatically trigger our workflow to execute _for all webhooks configured_. ```js title="Trigger workflow for our project" await knock.workflows.trigger("workflow-with-webhook-step", { data: { event_type: eventType, payload: eventPayloadData, }, recipients: [{ collection: "projects", id: "project-1" }], }); ``` This works as Knock will automatically fan-out to all webhooks subscribed to the project as part of the execution, meaning that the workflow is invoked for the project and for each webhook as a recipient. ## Advanced topics ### Debugging failed webhook deliveries You can use the Message logs within Knock in order to [debug messages sent](/send-notifications/debugging-workflows), where you’ll see delivery logs about the request to the customer’s webhook channel. Since your customers won't have access to Knock, you can use the [Message delivery logs API](https://docs.knock.app/api-reference/messages/list_delivery_logs) to create UI in your application that lets developers inspect the request/response interaction between Knock and their downstream URL. ### Reporting on failed webhook deliveries You might want to provide a way to notify your customers that their webhooks are failing. You can do so by leveraging Knock’s [outbound webhooks](/developer-tools/outbound-webhooks/overview) features to create a webhook callback to your server that listens for `message.undelivered` events. ### Providing a dynamic signing key You will likely want to provide a way for your customers to [verify that the webhook request is coming from Knock](/integrations/webhook/overview#securing-your-webhooks). You can do so by providing a signing key in your webhook channel's configuration that Knock will use to sign the request with an HMAC/SHA256 signature. For customer-facing webhooks, you can provide a UI for your customers to configure a signing key; you'll want to configure this in Knock as a [dynamic signing key](/integrations/webhook/overview#using-a-dynamic-signing-key) using Liquid variables. ## Frequently asked questions The answer here really depends on how different the notifications around each event type need to be. You should consider structuring each event type as a separate workflow if the types of notifications sent in each event type differ a lot, or if the templates between event types are sufficiently different. If you need to limit the execution of the workflow steps you can add a trigger condition onto your webhook channel steps that will only send to: - `recipient.__typename` is equal to `Object` - `recipient.collection` is equal to `project_webhooks` You can store an `event_types` property on your webhook object and use a [step condition](/designing-workflows/step-conditions) to express whether or not the step should be executed (as an allow list). - `recipient.event_types` contains `some:event` # Recurring digests Learn how to build recurring, cross-channel digest notifications with Knock. --- title: Powering recurring digests with Knock description: Learn how to build recurring, cross-channel digest notifications with Knock. tags: ["weekly", "daily", "monthly", "recurrence"] section: Tutorials --- In this tutorial, we'll create a simple recurring digest notification for our customers that will execute a digest every Monday at 9am to summarize information that the users might have missed in the past week. To do so, we'll use the [schedules API](/concepts/schedules) and the [fetch function](/designing-workflows/fetch-function) to build a powerful, flexible notification workflow that is driven by dynamic data from your service. ## Creating your Knock workflow We're going to create a Knock workflow named "Recurring digest" from the Knock dashboard. To do so, navigate to your Development environment and click the "Create workflow" button in the top right corner. We'll then be prompted to name the workflow and have a key generated, which will be autogenerated as `recurring-digest` from the name by default. Next, let's configure our newly created workflow to do something. To do so we'll click the "Edit steps" button to be taken to the [workflow builder](/designing-workflows) which is where we can add steps to our workflow and configure the templates that will generate notifications. ### Fetching data for our digest In this digesting example, we'll use the [workflow fetch function](/designing-workflows/fetch-function) to retrieve information that we wish to digest as part of the notification. The fetch step will make a call _per recipient_ to an HTTP endpoint to retrieve this information. In our example, we'll implement this as a simple Node.js server that returns some static digest information per recipient, but you can return any information from any HTTP endpoint to power your digest. ```javascript title="The Node.js server we query for digest items" const express = require("express"); const app = express(); // Some dummy digest items by the recipient id const digestItems = { chris: [ { title: "Left a comment on Knock product roadmap", timestamp: "2023-05-17T12:00:00Z", }, { title: "Liked a comment by Sam Seely on Knock Onboarding", timestamp: "2023-05-17T12:00:00Z", }, ], sam: [ { title: "Left 3 new comments on Knock engineering roadmap", timestamp: "2023-05-17T12:00:00Z", }, ], }; // respond with the users digest app.get("/users/:userId/digest", (req, res) => { const items = digestItems[req.params.userId]; res.json(items); }); app.listen(3000, () => console.log(`⚡️ Server running`)); ``` Next, we'll add a fetch function to our workflow, and configure the fetch function to hit the endpoint. We'll do this by adding a 'Fetch function' to the workflow and clicking the 'Edit request' button. In this example, we're going to pass the current recipient's `id` as a parameter in the endpoint URL using liquid. We'll use ngrok to hit our local endpoint, but in production, we'd point this to a deployed, hosted service that's publically accessible. When the fetch step is executed the JSON response returned from the HTTP endpoint will be merged into the **current workflow run scope** making all of that data accessible to be used in your notification templates. in the future, Knock will allow Knock managed digest data, where the information can be summarized from triggered Knock workflows per recipient. } /> ### Designing your notification template The last step in building our workflow is to add a channel step to our Knock workflow to send a notification out. Here we'll send an email, but we could use any supported channel type in Knock, or even create a cross-channel digest notification if we wanted. Once we've added our email channel step, we can edit the underlying template associated by clicking "Edit template." We'll add a markdown block to the email builder and we can copy in the template below: ```markdown title="Digest email template" ## You have {{ items | size }} new {{ items | size | pluralize: "notification", "notifications" }} There are new notifications waiting for you to review. Please go into the app to review them.
    {%- for item in items %}
  • {{ item.title }} happened on {{ item.timestamp }}
  • {%- endfor %}
```
you'll need to configure an email channel via a provider in order to start sending emails with Knock. You can read more on{" "} configuring email channels here } /> Here we're using the `items` array that we returned from the fetch step to render each notification. Notice how each item corresponds to the data structure we're defining above in our node service. We can also set some preview data in Knock to help with seeing what our email template will look like. To do so, click "Edit preview data" in the left hand variable pane and add the following JSON: ```json title="Sample preview data" { "items": [ { "timestamp": "2023-05-17T12:00:00Z", "title": "A new notification" } ] } ``` At this point, it's probably a good idea to run a test of our workflow using the test runner to execute an end-to-end workflow run for a single recipient by clicking "Run a test" in the top right corner of the workflow builder. Finally, we'll need to **Commit our workflow** to the development environment by clicking the "Commit to development" button in the top right corner of the workflow page. We'll also need to **activate our workflow** before we can use it by marking the status as "Active." Once we're satisfied with the email notification, we can move on to running the scheduled digest for our recipients. ## Creating digest schedules Finally, we'll want to create a recurring schedule for our users. To do so we'll use the [Schedules API](/concepts/schedules) which lets us trigger a workflow for one or more recipients on a defined schedule. Each schedule is defined per recipient, but the `createSchedules` method lets us create a schedule for up to 100 recipients at a time. The schedule defines which workflow to trigger, as well as the rules for when to repeat the schedule. In our case, we'll create a weekly notification that goes out every Monday at 9am, for `chris` and `sam`. ```javascript title="Creating schedules for our recipients" await knock.workflows.createSchedules("recurring-digest", { recipients: ["chris", "sam"], repeats: [ { frequency: "weekly", days: ["mon"], hours: 9, }, ], }); ```
this example assumes that we've already{" "} identified the two users into Knock to synchronize their name and email address. } /> Once our schedules are created, they will start running on the next occurrence date. We can even see these in the Knock dashboard under **Workflows** > **Recurring digest** > **Schedules** and see when the schedules will run next for our recipients. ## Wrapping up That's it! We just created our first recurring notification for our users that will run every Monday at 9am. If you want to think about extending this example you could consider: - **Adding more supported channel types for the notification**. You could add an in-app notification or a Slack notification for your users. - **Making schedules configurable by your users**. Because each schedule is per-user, you can easily make schedules configurable at the user level using the [update schedule endpoint](/api-reference/schedules/update). - **Adding timezone support**. Schedules [natively support timezones per recipient](/concepts/schedules#executing-schedules-in-a-recipients-timezone), so we can easily allow our recurring digests to run at a specific time in the users timezone. # Migrate from Courier Learn how to migrate your notifications from Courier to Knock. --- title: Migrate from Courier to Knock description: Learn how to migrate your notifications from Courier to Knock. tags: ["migrate", "courier", "migration"] section: Tutorials --- Knock’s APIs and developer tools make it easy to migrate your notification templates and user data from other notifications platforms into Knock. In this tutorial, we will walk you through planning and executing a migration from Courier into Knock. ## Mapping Courier concepts to Knock concepts Before migrating any data into Knock, it’s helpful to understand how the resources in your Courier account map to concepts and resources in Knock. ### Integrations In order to deliver notifications with Courier, you installed one or more Integrations for downstream providers in your Courier dashboard. In Knock, we refer to these delivery platforms as [Channels](/concepts/channels). Channels are configured under the **Channels and sources** page in your Knock dashboard account settings. You can see a full list of supported Channel types and providers [here](/integrations/overview). In addition to first-party integrations with message delivery platforms, Knock also offers convenient connections to customer data platforms (CDPs) and reverse ETL providers to bring your data into Knock ([Sources](/integrations/sources/overview)), as well as to popular analytics and data warehousing tools to allow you to export important data out of Knock ([Extensions](/integrations/extensions/overview)). ### Automations and Notification Templates In Courier, the content of your notifications is contained in templates built in your dashboard, and the orchestration and logic of sending your notifications is contained in Automations. Knock combines both of these things into a single resource called a [Workflow](/concepts/workflows), which serves as a container for all of the logic and message templates associated with a given notification in your system. You can also create [Partials](/template-editor/partials), which are content blocks that can be used across multiple workflows. When you’re ready to start sending notifications, you’ll do so by [triggering](/send-notifications/triggering-workflows) your workflows. ### Users and Profiles Courier uses the concept of Users to represent the recipients of notifications, with optional Profile records associated with each user to hold data about that user. Knock combines these concepts under a single [User](/concepts/users) object on which you can store any number of custom properties related to your notifications’ recipients. ### Lists Courier allows you to build Lists of users that you would like to send a given notification to. In Knock, we call this concept [Subscriptions](/concepts/subscriptions). Subscriptions are an extension of [Objects](/concepts/objects) and express the relationship between a [Recipient](/concepts/recipients) (the subscriber) and an Object. When you trigger a notification to an Object recipient, Knock will fan out the workflow trigger to **all recipients that are subscribers**, automatically enqueuing a workflow run for each recipient subscriber on your behalf. ### Tenants and Brands If you’re currently using Tenants in Courier to scope your notifications to a particular workspace or organization (and optionally associating Brands with those Tenants), you can achieve similar functionality with Knock [Tenants](/multi-tenancy/overview). Unlike Courier, per-tenant branding attributes are stored directly on a Tenant in Knock rather than as a separate resource. Knock also does not directly associate Tenants with the recipients of a notification (no subscription logic necessary!); rather, a `tenant` is applied as context to a particular workflow trigger in order to [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). Per-tenant branding and per-tenant preferences are features of our{" "} Enterprise plan . If you’d like to find out more information about Enterprise plan features and pricing, please contact us at sales@knock.app . } /> ### User Preferences Courier’s User Preferences API allows you to set notifications preferences for a given user by a Topic categorization and a Preference Section (a group of multiple Topics), as well as by delivery channel. Knock’s [Preferences](/preferences/overview) model has a single high-level `category` property that can be assigned on a Workflow; because a given workflow can have more than one `category`, you can use this property to map both Topics and Preference Sections to your notifications in Knock. Our powerful Preferences API allows your users to opt out of notifications based on the notification’s delivery `channel_type`, the `category` of the notification, the specific notification `workflow`, or a combination of these properties. You can also extend these preferences to be [tenant-specific](/multi-tenancy/per-tenant-preferences) or to [evaluate conditionally](/preferences/preference-conditions). ### Translations Courier’s Translations API allows you to read and write translations to Courier in the form of `.po` files that are referenced using the handlebars templating language within a notification template. Knock helps you to power notifications in multiple locales and languages using Knock [Translations](/template-editor/translations). You can work with Translations directly in your dashboard or programmatically via API, and Knock supports both `json` and `.po` file formats. When using the `t` tag [method](/template-editor/translations#translation-methods-filter-vs-tag) of referencing translations in your message templates, Knock will automatically generate the associated translation files for each of your registered locales behind the scenes. ## Migrating your data into Knock Now that you have a good understanding of how the resources in your Courier account map to concepts and resources in Knock, you can start planning your migration. Knock offers APIs and developer tools that make a migration smooth and efficient: - A [Management API](/mapi) that allows you to work programmatically with the resources that you can also create directly in your Knock dashboard (like Workflows and their associated message templates, email [Layouts](/integrations/email/layouts), and Translations). - A command line interface ([Knock CLI](/developer-tools/knock-cli)) that wraps the Management API, allowing you to work with your dashboard resources from the command line. - [Bulk endpoints](/api-reference/overview/bulk-endpoints) that allow you to upsert large amounts of data in a single API request (more on specific endpoints below). While the following steps outline a suggested order for migrating individual resource types into Knock based on your existing Courier integration, it’s helpful to note that Knock also supports{" "} inline identification {" "} of recipients in order to allow you to upsert recipients as you are performing other actions like triggering a workflow or creating subscriptions. Your approach may vary depending on your specific requirements. } /> We recommend migrating data into Knock in the following order to ensure that certain resources which are dependencies of other resources are migrated first: You’ll want to configure your Channels prior to migrating any workflows so that you can set the correct delivery methods for each of your notifications. You can do this by navigating to the **Channels and sources** page in your Knock dashboard account settings. Next, you can begin migrating Automations and Notification Templates into Knock Workflows. While you cannot request Courier Automations definitions via API, you can access a JSON representation of each Automation by navigating to the Automations section of your Courier dashboard, selecting the relevant automation, then clicking the “Code” button in the top navigation bar. Unfortunately, you also cannot export the message content of your notification templates from Courier. However, you _can_ request the delivery routing logic for your notifications from the Courier API and use this information to reconstruct your Workflows in Knock. With Knock’s environment model, you can either upsert your workflows directly into your production environment, or into your development environment and then promote them to production. Learn more about [environments](/version-control/environments). You can assign one or more [categories](/concepts/workflows#workflow-categories) to your workflows. These can be used to power recipient preferences (which we will cover in more detail below) and are roughly similar to Courier’s subscription topics. Next, you can migrate any translation files that are required to power your notifications. If you're already using translations in Courier, you should be able to use the same `.po` files in Knock. The next step is to migrate Tenants and Brands from Courier to Knock. Remember that in Knock, tenant-specific branding is stored as a property of a Tenant rather than as a separate resource. Migrating your users and their data will be one of the most important parts of a transition from Courier to Knock. There are a few key points to be aware of as you plan this part of your migration: - Courier’s Profiles API does not include an endpoint to list all users, so you’ll need to export them one at a time. - Knock uses the concept of [environments](/concepts/environments) to ensure logical separation of your data between local, staging, and production environments. This means that recipients and preferences created in one environment are never accessible to another. Your data for production users should be migrated into your Production environment in Knock. - Knock offers several different ways of “identifying” user data into our systems, and the best approach for you may differ depending on your use case. You can read more about the various approaches [here](/managing-recipients/identifying-recipients). [Subscriptions](/concepts/subscriptions) in Knock are an extension of [Objects](/concepts/objects) (a special type of non-user notification recipient). In order to migrate your List subscriptions from Courier to Knock, you’ll need to: - Export all Lists and their subscribers from Courier. - Create a new Object in Knock for each one of these Lists. Objects are organized into [collections](/concepts/objects#sending-object-data-to-knock) that represent the category or type of resource that they’ll be notifying. - Subscribe the appropriate users to each of these new Objects. Knock offers several bulk endpoints that can be used to optimize this data upsert with only a few API calls. At this point, you’re ready to migrate all of your users’ notification [preferences](/preferences/overview) to Knock. If you’re currently using User Preferences in Courier, you should be able to map your users’ settings to your new Knock resources in order to power your preference center. # Migrate from Braze Learn how to migrate your notifications from Braze to Knock. --- title: Migrate from Braze to Knock description: Learn how to migrate your notifications from Braze to Knock. tags: ["migrate", "braze", "migration"] section: Tutorials --- Knock's APIs and developer tools make it easy to migrate your notification templates and user data from other notifications platforms into Knock. In this tutorial, we will walk you through planning and executing a migration from Braze into Knock, focusing on transactional messaging workflows. ## Mapping Braze concepts to Knock concepts Before migrating any data into Knock, it's helpful to understand how the resources in your Braze account map to concepts and resources in Knock. ### Integrations In Braze, you configured messaging channels (email, SMS, push, etc.) to deliver notifications through various providers. In Knock, we refer to these delivery platforms as [channels](/concepts/channels). Channels are configured under the **Channels and sources** page in your Knock dashboard account settings. You can see a full list of supported channel types and providers [here](/integrations/overview). In addition to first-party integrations with message delivery platforms, Knock also offers convenient connections to customer data platforms (CDPs) and reverse ETL providers to bring your data into Knock ([sources](/integrations/sources/overview)), as well as to popular analytics and data warehousing tools to enable you to export important data out of Knock ([extensions](/integrations/extensions/overview)). ### Users Braze uses the concept of users to represent the recipients of notifications, with custom attributes and profile data associated with each user. Knock uses a similar concept of [user](/concepts/users) objects on which you can store any number of custom properties related to your notifications' recipients. ### Campaigns and workflows In Braze, your transactional messaging is handled through transactional email campaigns, API-triggered campaigns, and Canvases. Knock combines these into a single resource called a [workflow](/concepts/workflows), which serves as a container for all of the logic and message templates associated with a given notification in your system. When you’re ready to start sending notifications, you’ll do so by [triggering](/send-notifications/triggering-workflows) your workflows, similarly to how you trigger Braze campaigns. ### Template management Braze uses Content Blocks for reusable content and Liquid templating for personalization within campaigns. Knock provides similar functionality through [partials](/template-editor/partials) for reusable content blocks and also supports [Liquid templating](/template-editor/reference-liquid-helpers) for personalization within workflow [templates](/template-editor/overview). ### Multi-language support and translations Braze provides multi-language settings that enable you to target users with messages in different languages within a single email campaign, based on their locale. Braze keeps every translation file inside the campaign (or Canvas/email template) that uses it. download a translation template CSV, fill it in, and re-upload each time that specific campaign's copy changes. Braze also supports managing translations via API endpoints. Knock helps you to power notifications in multiple locales and languages using [translations](/template-editor/translations). You can work with translations directly in your dashboard or programmatically via API, and Knock supports both `json` and `.po` file formats. When using the `t` tag [method](/template-editor/translations#translation-methods-filter-vs-tag) of referencing translations in your message templates, Knock will automatically generate the associated translation files for each of your registered locales behind the scenes. You can then translate the default content into additional locales by manually editing your translation files or programmatically updating them using the Knock API and a translation service. ### Multi-tenancy In Braze, there isn't a direct equivalent to Knock's tenant functionality. Most Braze customers handle customer/organization segmentation through custom attributes (like `organization_id`, `account_id`, `company_name`) combined with segments built on those attributes. This approach requires manual campaign targeting, complex segmentation logic, and offers no native support for per-organization branding, preferences, or scoped in-app feeds. In Knock, [tenants](/multi-tenancy/overview) provide native multi-tenancy support that eliminates these workarounds. Tenants represent organizations your users belong to—what you might call "accounts" or "workspaces." Per-tenant branding attributes are stored directly on a tenant in Knock rather than requiring separate campaigns or complex attribute management. Knock tenants are applied as context to workflow triggers to automatically [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [manage per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). Key advantages of Knock's tenant approach: - **Single workflow, multiple tenants.** One workflow can serve all organizations with tenant-specific customizations. - **Native branding support.** Per-tenant logos, colors, and styling without template duplication. - **In-app feed scoping.** In-app notifications can be filtered by tenant context. - **Simplified preference management.** Per-user, per-tenant preferences without complex attribute juggling. Per-tenant branding and per-tenant preferences are features of our{" "} Enterprise plan . If you’d like to find out more information about Enterprise plan features and pricing, please contact us at sales@knock.app . } /> ### Subscriptions and preferences Braze manages user subscription preferences through subscription groups and global subscription states (`email_subscribe` and `push_subscribe`). Users can be subscribed, unsubscribed, or opted-in to different messaging channels and specific subscription groups. See Braze's documentation on email subscription management and SMS subscription groups for more details. Knock provides two features that together replace Braze subscription groups: - [Preferences](/preferences/overview) handle communication opt-outs similarly to Braze's marketing-focused subscription groups, but with enhanced flexibility for channel-specific, category-based, or workflow-specific preferences that can be [tenant-specific](/multi-tenancy/per-tenant-preferences) or [conditional](/preferences/preference-conditions). - [Subscriptions](/concepts/subscriptions) express relationships between [recipients](/concepts/recipients) and [objects](/concepts/objects) in your [data model](/tutorials/modeling-users-objects-and-tenants), enabling you to notify large numbers of recipients by triggering workflows for a single object recipient and letting Knock handle the recipient fanout for you rather than resolving recipient lists in your system when you trigger your notifications. At a high level, Braze subscription groups migrated to Knock could look like: - Braze global subscription states (`email_subscribe`, `push_subscribe`) → Knock channel-type preferences - Braze subscription groups for communication types (newsletters, promotions) → Knock category and/or workflow-level preferences - Braze subscription groups for specific entities (project alerts, account updates) → Knock object subscriptions ## Migrating your data into Knock Now that you have a good understanding of how the resources in your Braze account map to concepts and resources in Knock, you can start planning your migration. Knock offers APIs and developer tools that make a migration smooth and efficient: - A [Management API](/mapi) that enables you to work programmatically with the resources that you can also create directly in your Knock dashboard (like workflows and their associated message templates, email [layouts](/integrations/email/layouts), and translations.) - A command line interface ([Knock CLI](/developer-tools/knock-cli)) that wraps the Management API, enabling you to work with your dashboard resources from the command line. - [Bulk endpoints](/api-reference/overview/bulk-endpoints) that enable you to upsert large amounts of data in a single API request (more on specific endpoints below.) - [Knock MCP server](/ai/mcp-server) that enables AI assistants to help migrate workflows and templates from other platforms. We recommend migrating data into Knock in the following order to ensure that certain resources which are dependencies of other resources are migrated first: You'll want to configure your channels prior to migrating any workflows so that you can set the correct delivery methods for each of your notifications. You can do this by navigating to the **Channels and sources** page in your Knock dashboard account settings. Unlike Braze, integrations in Knock are created once at the account level, then configured per environment with your provider credentials and settings. This enables you to use different API keys for development/production, enable sandbox mode for testing, and set environment-specific conditions without duplicating your entire setup. Next, you can begin migrating your transactional email campaigns, API-triggered campaigns, and Canvases into Knock workflows. To export your existing Braze campaign content, you can either: - Use the Braze export campaign details API to programmatically retrieve campaign templates and content. - Access your campaign content through the Braze dashboard by navigating to your campaigns. You can then upsert your message content into Knock workflows by either: - Using the [Knock MCP server](/ai/mcp-server) to help automate the template conversion process, including assisting in updating Braze-specific Liquid variables to Knock Liquid syntax. - Manually recreating your message content in Knock workflows with the dashboard template editor. With Knock’s environment model, you can either upsert your workflows directly into your production environment, or into your development environment and then promote them to production. Learn more about [environments](/version-control/environments). You can assign one or more [categories](/concepts/workflows#workflow-categories) to your workflows. These can be used to power recipient preferences (which we will cover in more detail below) and are roughly similar to Braze's subscription groups. If you're using multi-language settings in Braze, you can extract your translation content and import it into Knock's translation system. Key considerations to remember: - In Braze, translation files are stored at the campaign level. - In Knock, translations are managed globally at the environment level, and are reusable across workflows. Suggested approach: 1. Export a list of all Braze campaigns, Canvases, and email templates that include translation tags and note which locales they cover. 2. Use Braze’s bulk‑translation APIs to pull down the raw locale data for those assets. 3. Each Braze export is grouped by message variant. You'll need to decide on a universal key pattern for Knock and convert the Braze JSON/CSV into locale‑key‑value maps that match this pattern. Be sure to check pluralization, as Braze sometimes stores counts as separate rows, but Knock supports `zero`/`one`/`other` [rules](/template-editor/translations#pluralization) out‑of‑the‑box. 4. For each locale, call Knock's Management API to upsert the translation file into your Knock environment. Once the translations are in place, templates can reference the strings using the `t` filter (e.g., `{{ "welcome_message" | t }}`) where you specify the exact translation keys you created. If your Braze implementation uses custom attributes to associate users with organization, account, or workspace, you can migrate this data to create proper tenant structures in Knock. This migration will replace complex attribute-based segmentation with Knock's native tenant functionality. Suggested approach: 1. Extract organization or account identifiers (such as `organization_id`, `account_id`, `company_name`, or `workspace_id`) from your Braze custom attributes. 2. Gather any organization-specific metadata, including company names, branding preferences, or settings that may currently be stored as user attributes or managed externally. 3. Use the Knock tenant API to create tenant objects with appropriate IDs and any relevant custom properties. 4. Update your workflow trigger logic to include the `tenant` parameter when applicable. Migrating your users and their data will be one of the most important parts of a transition from Braze to Knock. There are several key considerations and steps to ensure a smooth migration: - Braze provides user export endpoints for exporting user data in batches. - Knock uses the concept of [environments](/concepts/environments) to ensure logical separation of your data between local, staging, and production environments. This means that recipients and preferences created in one environment are never accessible to another. Your data for production users should be migrated into your Production environment in Knock. While we're showing user migration as a discrete step, it’s helpful to note that Knock also supports{" "} inline identification {" "} of recipients in order to enable you to upsert recipients as you are performing other actions like triggering a workflow or creating subscriptions. Your approach may vary depending on your specific requirements. } /> If applicable, you can now migrate entity-specific subscription groups where users follow particular business objects. [Subscriptions](/concepts/subscriptions) in Knock express relationships between users and objects in your data model, enabling you to trigger workflows for objects and automatically notify all subscribers. Suggested approach: - Identify Braze subscription groups tied to specific entities (project alerts, account updates, team notifications.) - Create objects in Knock for each entity, organized into [collections](/concepts/objects#sending-object-data-to-knock) that represent the category or type of resource. - Subscribe the appropriate users to each object using the bulk subscription endpoints. Knock offers several bulk endpoints that can be used to optimize this data upsert with only a few API calls. At this point, you're ready to migrate all of your users' notification [preferences](/preferences/overview) to Knock. Key considerations: - Braze's global subscription states (`email_subscribe`, `push_subscribe`) should map to Knock's channel-level preferences. - Braze subscription groups should map to Knock workflow categories or specific workflows. Suggested approach: 1. Bulk extract user data from Braze using the segment export endpoint. 2. Map subscription groups to Knock workflow categories or specific workflows. 3. Transform global subscription states to Knock's channel-type preferences (`email`, `push`, `SMS`, etc.) 4. Bulk import preferences using Knock's bulk preferences endpoint (1,000 users per batch.) # Modeling Users, Objects, and Tenants Learn how to map your application's data model into Knock. --- title: Modeling Users, Objects, and Tenants in Knock description: Learn how to map your application's data model into Knock. section: Tutorials --- In this tutorial, we'll cover some best practices in modeling users, tenants, and objects in Knock. Since Knock is a set of flexible abstractions, there are many possible ways to map these concepts in Knock to entities in your own application, but this tutorial will use examples to help you with this decision-making process. To do this, we’ll use an example collaboration app called Collab.io that consists of users, workspaces, projects, and alerts. ## Users in Knock Users in your application will map directly to users in Knock. Users in Knock are [identified](/managing-recipients/identifying-recipients) with a unique `id`, which in most cases should be the same `id` that you use to identify them in your application. Users can have any number of custom properties associated with them, and [Knock reserves a number of optional properties](/concepts/users#optional-attributes) like `email`, `name`, `phone_number`, `timezone`, and `avatar` that are used as defaults across different message delivery channels. ```javascript title="Example user modeled as an Knock User" { "id": "user_1234567890", "name": "Dummy User", "email": "dummy@example.com", "plan_type": "professional_2024", "updated_at": "2021-03-07T12:00:00.000Z", "created_at": null, "__typename": "User" } ``` You can sync these properties with Knock through a process called [identification](https://docs.knock.app/managing-recipients/identifying-recipients). ## Objects in Knock [Objects](/concepts/objects) in Knock are a flexible abstraction that you can use to send notifications to non-user recipients. You can also represent relationships between these non-user recipients and users via [Subscriptions](/concepts/subscriptions). Let’s look at some non-user recipient use cases first. ### Non-user recipients These non-user recipients can include things like a Slack integration or a webhook destination. Objects are like a NoSQL data store that allows you to map resources from your application into Knock. Objects in Knock live inside of collections and are identified with an id that’s unique to that collection. #### A webhook destination The `alert` entities inside of Collab.io exist to send webhooks to a downstream service when certain events are triggered inside of a project. To store this entity in Knock, you can create an Object with the id of `alert_URJXQKT1` inside of the `alerts` collection. Since an Object can store any number of custom properties, you can also include values for `url` and `signingKey` along with any other data you might need when sending a webhook event or listing these alerts in your application’s UI. ```javascript title="Example alert modeled as an Object" { "__typename": "Object", "collection": "alerts", "created_at": null, "id": "alert_URJXQKT1", "properties": { "description": "Project event destination", "events": [ "project:info", "project:alert" ], "name": "Project alerts", "signingKey": "1888e28c-67ce-4f4c-be74-9ab8896785e4", "url": "https://hkdk.events/m3vdn670twnfs7" }, "updated_at": "2024-07-01T21:20:10.538Z" } ``` When you trigger a workflow with this object as a recipient, you can use the `url` and `signingKey` properties to generate a secure webhook request. Using the `events` array, you can store event subscriptions directly on the Object and filter out webhook events using a [step condition](https://docs.knock.app/designing-workflows/step-conditions). To learn more about using Objects and webhooks, you can read [our tutorial on creating customer facing webhooks](https://docs.knock.app/tutorials/customer-webhooks). #### A Slack integration The `project` entities in Collab.io are the main surface areas for collaboration in the application and generate several types of notifications. Let’s assume that users can map individual projects to Slack channels in a shared workspace so that new comments get sent to a particular channel. To map this entity to Knock, you would create an Object with the id of `project_1YQ4XR18` inside of the `projects` collection. The Object which represents that `project` might look like this: ```javascript title="Example project modeled as an Object" { "__typename": "Object", "collection": "projects", "created_at": null, "id": "project_1YQ4XR18", "properties": { "name": "New product launch" }, "updated_at": "2024-06-20T18:37:43.500Z" } ``` Unlike the webhook example, where we used custom properties on the Object to power a downstream notification using the webhook channel, channels like Slack look for connection information in a special property called [channel data](https://docs.knock.app/managing-recipients/setting-channel-data). Details like the `channel_id` and `access_token` are stored on the Object’s channel data, which you can see in the example below: ```javascript title="Example channel data for a Slack channel" { "__typename": "ChannelData", "channel_id": "e8bbc2cc-5195-4a41-a247-f44ffcdc874f", "data": { "connections": [ { "access_token": "xoxo-bdade7f76ad767ad676gg6767", "channel_id": "C06GCKH3E68", "incoming_webhook": null, "user_id": null } ], "token": null } } ``` When you use the `project_1YQ4XR18` object as a recipient in a workflow with a Slack channel step, Knock automatically looks for channel data on the object to provide the necessary details to deliver the notification. ```javascript title="Triggering a workflow for an object recipient" await knock.workflows.trigger("new-comment", { recipients: [{ collection: "projects", id: "project_1YQ4XR18" }], actor: "user_FE5WFU3D" data: { comment: "Does anyone have an update on this product launch?" }, }); ``` ### Subscriptions In addition to acting as non-user recipients, Objects also allow you to express a relationship to groups of users via [Subscriptions](https://docs.knock.app/concepts/subscriptions). Object subscriptions are great for use cases where you want to notify a group of users in bulk, like a contact list, or fan out to all the subscribers or a particular topic. In more advanced implementations, you can also [model hierarchies using objects and subscriptions](https://docs.knock.app/concepts/subscriptions#modeling-nested-subscription-hierarchies), which should give you more flexibility in fan-out operations. Let’s look at an example of how you can subscribe a Collab.io user to updates on a `project`. First, you need to create a subscription between the `user` and the `project`, and add any custom properties you want stored on the subscription. You can access subscription properties for recipients in your message templates using the `recipient.subscription` property: ```javascript title="Creating a subscription between a user and a project" await knock.objects.addSubscriptions("projects", "project_1YQ4XR18", { recipients: ["user_JG9NGAJQ"], properties: { // Optionally set other properties on the subscription for each recipient }, }); ``` Finally, to send a message to all recipients, you trigger a workflow using the `project_1YQ4XR18` object as a recipient: ```javascript title="Triggering a fan out to object subscribers" await knock.workflows.trigger("new-comment", { recipients: [{ collection: "projects", id: "project_1YQ4XR18" }], actor: "user_FE5WFU3D" data: { comment: "Does anyone have an update on this product launch?" }, }); ``` When this workflow is triggered, Knock will generate individual workflow runs for the object itself AND for each of the object’s subscribers. If you recall from the previous step, there is also some Slack channel data stored on `project_1YQ4XR18` in Knock, and the `workflow.trigger` code snippet above is also used in that example. This is why Knock can be so powerful in simplifying notification logic. In practice, that means the first workflow run using the `project` object can generate a Slack notification, and all of the following workflow runs can notify individual recipients on another channel like email or in-app messaging. ## Tenants in Knock [Tenants](/multi-tenancy/overview) in Knock are a concept that allow you to segment your users and their messages. Most SaaS applications have some concept that is similar to “accounts,” “organizations,” “workspaces,” or “groups.” Under the hood, Tenants are a system-level Object collection called `$tenants`, so you can operate on them the same way you would an Object. You can set custom properties and subscribe users to them. In Collab.io, tenants are modeled as `workspaces`, which contain `projects` and `alerts`. You can create a corresponding Tenant in Knock using the same id that you use in your application: Tenants in Knock are loosely coupled to your users and objects, which means Knock does not know anything about the relationship between your users and tenants. Instead, you need to tell Knock that a particular workflow run belongs to a particular tenant when triggering a workflow. This means that you have less data to synchronize to Knock, and the risk of drift between what's current in your system and what's reflected in Knock is reduced. ```javascript title="Tagging a workflow run with a tenant" await knock.workflows.trigger("new-comment", { recipients: [{ collection: "projects", id: "project_1YQ4XR18" }], actor: "user_FE5WFU3D" data: { comment: "Does anyone have an update on this product launch?" }, tenant: "workspace_B80E71BI" }); ``` Tagging messages with a particular tenant can help you segment your notifications and apply [per-tenant branding](/multi-tenancy/per-tenant-branding) and [preferences](/multi-tenancy/per-tenant-preferences). Tenants are also useful for helping you [scope the in-app feed](/multi-tenancy/tenant-scoping) to messages about a certain workspace or organization. # Using LaunchDarkly with Knock to A/B test messaging Learn how to use LaunchDarkly with Knock to A/B test messaging workflows and templates. --- title: Using LaunchDarkly with Knock to A/B test messaging description: Learn how to use LaunchDarkly with Knock to A/B test messaging workflows and templates. tags: ["launchdarkly", "experiments", "ab-testing"] section: Tutorials --- In this tutorial we'll walk through how to use LaunchDarkly segments and experiments to power A/B testing across Knock's cross-channel messaging. ## Overview A/B testing your messaging can improve engagement rates, conversion, and user experience. By testing different message variants, channels, timing, and targeting, you can optimize your notification strategy based on real user behavior data. This tutorial shows you how to combine LaunchDarkly's feature flags and experimentation platform with Knock's notification infrastructure to run A/B tests on your workflow messaging. What you'll learn: - How to use LaunchDarkly flags to control message variants - How to leverage LaunchDarkly segments for targeted messaging experiments - How to implement A/B testing logic in Knock workflows using branch steps - How to measure and analyze messaging experiment results - Best practices for messaging experimentation If you're interested in using LaunchDarkly with Knock guides or broadcasts, please reach out at{" "} support@knock.app. } /> ## Integration architecture The LaunchDarkly + Knock integration follows this flow: 1. **LaunchDarkly** defines your experiment parameters (flags, segments, variations) and manages statistical analysis. 2. **Your application** evaluates flags and passes results to Knock via trigger data. 3. **Knock workflows** use branch steps to deliver different messaging experiences based on flag variations. 4. **Knock** captures engagement events (message delivery, opens, clicks) and forwards them to LaunchDarkly. 5. **LaunchDarkly** correlates engagement events with flag variations to measure experiment success and statistical significance. ## LaunchDarkly concepts and setup ### Flags LaunchDarkly Flags serve as the control mechanism for your messaging experiments. They determine which message variant each user receives. We'll be using the example flag below in our tutorial. You could also create multi-variant flags to run more complex experiments with different message content, channels, and timing. Simple A/B test flag: - Key: `messaging-AB-test` - Variations: `control`, `treatment` - Default: `control` ### Segments LaunchDarkly Segments enable you to define test populations and ensure consistent user experiences across multiple flags. ```markdown title='Example LaunchDarkly user segments' premium-users: user.tier == "premium" free-users: user.tier == "free" trial-users: user.tier == "trial" ``` ### Experiments LaunchDarkly experiments connect your messaging flags to business metrics, enabling you to measure the effectiveness of different messaging strategies. ```markdown title='Example LaunchDarkly experiment' Experiment: "Welcome Email Optimization" Flag: messaging-AB-test Metric: signup_completion_rate Allocation: 50% control, 50% treatment Duration: 2 weeks Sample size: 10,000 users ``` ## Using LaunchDarkly with Knock workflows You can use LaunchDarkly with Knock workflows to orchestrate different cross-channel messaging experiences based on the flags and experiments you've defined in LaunchDarkly. We'll cover two methods for using LaunchDarkly with Knock workflows: flag-controlled workflow selection and branch-based message variants. ### Method 1: Flag-controlled workflow selection This approach uses LaunchDarkly flags to determine which Knock workflow to trigger, enabling you to test different notification strategies. You can use this approach to test different messaging approaches, compare email vs. SMS vs. push notification strategies, or test different workflow timing and sequences. First, evaluate the LaunchDarkly flag in your application. ```javascript title="LaunchDarkly flag evaluation" const messagingVariant = await ldClient.variation( "messaging-AB-test", user, "control", ); ``` Next, trigger the appropriate Knock workflow based on the flag value. ```javascript title="Trigger Knock workflow" const workflowKey = messagingVariant === "treatment" ? "messaging-AB-test-treatment" : "messaging-AB-test-control"; await knock.workflows.trigger(workflowKey, { recipients: [user.id], data: { // Add any data you want to pass to the workflow }, }); ``` ### Method 2: Branch-based message variants This approach uses a single workflow with branch steps to deliver different messaging experiences based on LaunchDarkly data passed via trigger data. You can use this approach to A/B test different templates within the same workflow, personalize message content based on user segments, or run other kinds of flag-based experiments. First, evaluate the LaunchDarkly flags in your application and pass the results to Knock via trigger data. ```javascript title="Evaluate multiple flags and pass to Knock" const variation = await ldClient.variation( "messaging-AB-test", user, "control", ); await knock.workflows.trigger("onboarding-sequence", { recipients: [user.id], data: { user_ab_test_variation: variation, // Any other data you want to pass to the workflow }, }); ``` In your Knock workflow, add a branch step with these conditions: - **Control branch**: `data.user_ab_test_variation == "control"` - **Treatment branch**: `data.user_ab_test_variation == "treatment"` - **Default branch**: Fallback for any unmatched conditions ## Analytics and analyzing results To measure messaging experiment success, forward Knock's engagement events to LaunchDarkly. This allows LaunchDarkly to correlate message engagement with flag variations and calculate proper conversion rates. Three integration approaches are available: 1. **Direct webhook integration.** Forward events in real-time via Knock's [outbound webhooks](/developer-tools/outbound-webhooks/overview). 2. **Segment integration.** Use Knock's [Segment extension](/integrations/extensions/segment) to route events through Segment to LaunchDarkly. 3. **Data warehouse integration.** Use Knock's [warehouse sync](/integrations/extensions/data-sync) to bring data into your warehouse for analysis. ### Method 1: Direct webhook integration Set up a webhook endpoint to receive Knock events and forward them to LaunchDarkly. Create an endpoint to receive Knock webhook events and forward them to LaunchDarkly: ```javascript title="Webhook endpoint to forward events to LaunchDarkly" app.post("/knock-webhook", async (req, res) => { const { type, data } = req.body; // Forward engagement events to LaunchDarkly if (type === "message.read" || type === "message.link_clicked") { await ldClient.track({ eventName: type === "message.read" ? "email_opened" : "email_clicked", user: { key: data.recipient.id }, data: { message_id: data.id, workflow_key: data.workflow.key, channel: data.channel_id, }, }); } res.status(200).send("OK"); }); ``` In your Knock dashboard: 1. Go to Developer → Webhooks 2. Add your webhook endpoint URL 3. Select events: `message.read`, `message.link_clicked` 4. Enable webhook Learn more about [outbound webhooks](/developer-tools/outbound-webhooks/overview). ### Method 2: Segment integration Use Knock's [Segment extension](/integrations/extensions/segment) to automatically forward events through Segment to LaunchDarkly. Configure the Segment extension in your Knock dashboard with your Segment write key. Knock will automatically forward engagement events to Segment. In your Segment dashboard: 1. Add LaunchDarkly as a destination. 2. Configure event mapping from Knock events to LaunchDarkly metrics. 3. Map events like `Notification read` → `email_opened` in LaunchDarkly. ### Method 3: Data warehouse integration For comprehensive analytics, combine Knock's [data warehouse sync](/integrations/extensions/data-sync) with LaunchDarkly's data export. Configure warehouse sync to your data warehouse. This provides detailed message and engagement data. Use LaunchDarkly's data export to get flag variation data in your warehouse, then join with Knock's engagement data for analysis. ## Implementation example: welcome email A/B test This example shows a complete implementation of A/B testing welcome emails with different messaging styles. First, create the flag and experiment in LaunchDarkly. **Create flag in LaunchDarkly:** ```json title="Create flag in LaunchDarkly" { "key": "welcome-email-style", "name": "Welcome Email Style Test", "variations": [ { "value": "friendly", "name": "Friendly" }, { "value": "professional", "name": "Professional" }, { "value": "casual", "name": "Casual" } ], "defaultVariation": "friendly" } ``` **Set up experiment:** ```json title="Set up experiment in LaunchDarkly" { "name": "Welcome Email Optimization", "flagKey": "welcome-email-style", "primaryMetric": "email_opened", "allocation": { "friendly": 34, "professional": 33, "casual": 33 } } ``` **Create metrics for the experiment:** ```json title="Create metrics in LaunchDarkly" { "email_opened": { "eventName": "email_opened", "unitOfMeasurement": "conversion", "description": "Measures email open rate of users who received the message" }, "email_clicked": { "eventName": "email_clicked", "unitOfMeasurement": "conversion", "description": "Measures click-through rate of users who received the message" } } ``` Integrate the LaunchDarkly flag evaluation into your user registration flow. ```javascript title="User registration flow with LaunchDarkly integration" // In your user registration flow async function sendWelcomeEmail(user) { // Evaluate LaunchDarkly flag const emailStyle = await ldClient.variation( "welcome-email-style", user, "friendly", ); // Trigger Knock workflow await knock.workflows.trigger("welcome-sequence", { recipients: [user.id], data: { email_style: emailStyle, experiment_name: "welcome-email-optimization", }, }); } ``` Create a workflow with branch steps and configure email templates for each variant. **Create workflow with branch step:** - Workflow key: `welcome-sequence` - Add branch step with conditions: - **Friendly branch**: `data.email_style == "friendly"` - **Professional branch**: `data.email_style == "professional"` - **Casual branch**: `data.email_style == "casual"` Configure email templates for each variant. Set up conversion tracking and monitor experiment results. **Set up conversion tracking:** ```javascript title="Conversion tracking setup" // Track when users complete onboarding app.post("/onboarding-complete", async (req, res) => { const { user_id, style } = req.body; // Track conversion in LaunchDarkly await ldClient.track("onboarding-completion", { user: { key: user_id }, data: { experiment_name: "welcome-email-optimization", variant: style, conversion_value: 1, }, }); res.status(200).send("OK"); }); ``` # Send web push with FCM Learn how to use FCM and Knock to deliver web push messages to a React application. --- title: Sending web push notifications with Firebase Cloud Messaging (FCM) description: Learn how to use FCM and Knock to deliver web push messages to a React application. section: Tutorials --- In this tutorial, we'll walk through how to configure Knock and Firebase Cloud Messaging (FCM) to send web push notifications and customize their display using the Firebase SDK. This tutorial assumes you are using a React application and have already implemented other [Knock-specific components](/in-app-ui/react/overview), like the `KnockProvider`. Examples in this tutorial have been written using the Next.js framework. ## Configuring FCM resources Knock does not have a first-party web push channel. Instead, you'll use FCM to send messages to a user's browser. There are multiple steps to this configuration, both within Knock and inside your application. ### Add FCM as a push channel The first step is to add FCM as a push channel integration in the Knock dashboard using [the default instructions](https://docs.knock.app/integrations/push/firebase). Once you have this channel configured, keep track of the `channel_id` for use in subsequent steps. ### Install the Firebase SDK Many of the steps you'll need to take to successfully register for and receive web push messages will happen inside your application. The Firebase SDK is responsible for generating a `token` that registers the user's browser and displaying certain types of notifications to the user. Run the following command to install the SDK in your client-side application: ```bash title="Install Firebase SDK" npm install --save firebase ``` You will use both the `app` and `messaging` modules of the `firebase` package in the following steps. ### Create environment variables in your application You'll need several environment variables for this integration. Here's a quick breakdown of what you'll need and why: - Firebase config values to initialize the SDK. - your public VAPID key from the Firebase dashboard to generate a push token. - the ID of your Knock FCM channel to store channel data The environment variables for the Firebase config will also be needed inside of a service worker file, which may not have access to your application or framework's public variables. You may choose to hard-code those values, which you can see in this example of the Firebase `config` object. ```bash title="Store environment variables" # The `channel_id` from earlier NEXT_PUBLIC_KNOCK_FCM_CHANNEL_ID=uuid_knock_fcm_channel # This value comes from the Firebase dashboard NEXT_PUBLIC_FIREBASE_VAPID_KEY=public_vapid_key_fcm_dashboard # Firebase configuration values # You can optionally hard code these in service worker NEXT_PUBLIC_FIREBASE_API_KEY=firebase_api_key NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=knock-demo.firebaseapp.com NEXT_PUBLIC_FIREBASE_PROJECT_ID=knock-demo NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=knock-demo.firebasestorage.app NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=firebase_message_sender_id NEXT_PUBLIC_FIREBASE_APP_ID=firebase_app_id ``` ## Add Firebase to your app In the following steps, we'll use the Firebase SDK inside your application to register for and receive web push messages. ### Create a service worker file First, let's create a service worker file. FCM requires a service worker file to handle displaying push notifications. Create a file called `firebase-messaging-sw.js` in your `public` directory and include the following contents: ```javascript title="Creating a service worker file" console.log("[Firebase SW] Service Worker Loaded"); importScripts( "https://www.gstatic.com/firebasejs/12.2.1/firebase-app-compat.js", ); importScripts( "https://www.gstatic.com/firebasejs/12.2.1/firebase-messaging-compat.js", ); const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, }; console.log("[Firebase SW] Firebase Config:", firebaseConfig); const app = firebase.initializeApp(firebaseConfig); const messaging = firebase.messaging(app); self.addEventListener("install", (event) => { console.log("[Firebase SW] Installing..."); event.waitUntil(self.skipWaiting()); // Force the new SW to activate immediately }); self.addEventListener("activate", (event) => { console.log("[Firebase SW] Activating..."); event.waitUntil( self.clients.claim(), // Take control over all open pages ); }); ``` This file loads the Firebase client libraries and adds some debugging logs to indicate its status. The important part of this file is that a new Firebase `app` is initialized and a new instance of `messaging` is created to automatically handle background notifications. Since service workers run in their own thread, separate from the JavaScript that powers your app, you'll need to bring Firebase into your application as well and pass a reference to this service worker. ### Create a FirebaseProvider component In the next step, we'll create a React component that initializes the Firebase SDK in the app and registers the service worker, providing both the `messaging` instance and the service worker to other components through React context. You can use the code in the following component as a starting point, but we'll walk through what's happening here step-by-step below: ```javascript title="Create a context provider to share Firebase state" "use client"; import React, { createContext, PropsWithChildren, useEffect, useState, } from "react"; import { getMessaging, Messaging, onMessage } from "firebase/messaging"; import { initializeApp } from "firebase/app"; // Your web app's Firebase configuration const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, }; interface FirebaseContextType { messaging: Messaging | undefined; serviceWorkerRegistration: ServiceWorkerRegistration | undefined; } export const FirebaseContext = (createContext < FirebaseContextType) | (undefined > undefined); const FirebaseProvider: React.FC = ({ children }) => { const [isClient, setIsClient] = useState(false); const [messaging, setMessaging] = (useState < Messaging) | (undefined > undefined); const [serviceWorkerRegistration, setServiceWorkerRegistration] = (useState < ServiceWorkerRegistration) | (undefined > undefined); useEffect(() => { setIsClient(true); }, []); useEffect(() => { const initializeFirebase = async () => { try { if ( isClient && "serviceWorker" in navigator && "Notification" in window ) { // Initialize Firebase const app = initializeApp(firebaseConfig); const messagingInstance = getMessaging(app); console.log("Firebase messaging initialized:", messagingInstance); setMessaging(messagingInstance); // Register service worker let registration = await navigator.serviceWorker.getRegistration("/"); if (!registration) { console.log("Registering service worker..."); registration = await navigator.serviceWorker.register( "/firebase-messaging-sw.js", { scope: "/", }, ); } await navigator.serviceWorker.ready; console.log("Service worker ready:", registration); setServiceWorkerRegistration(registration); // Set up foreground message handler const unsubscribe = onMessage(messagingInstance, async (payload) => { console.log("Foreground message received:", payload); //handle foreground message here }); return () => unsubscribe(); } else { console.log("Firebase not supported in this environment"); } } catch (error) { console.error("Error initializing Firebase:", error); alert(`Error initializing Firebase: ${error}`); } }; initializeFirebase(); }, [isClient]); if (!isClient) { return <>{children}; } return ( {children} ); }; export default FirebaseProvider; ``` Now, let's break down what's happening. First, we define the `firebaseConfig` values needed to create a new `app` and `messaging` instance. Then we create a new context object called `FirebaseContext` and create an interface to define its properties. This context object is what we'll pass down to other components so they can access the values from the `messaging` package and the `serviceWorkerRegistration`. ```javascript title="Create context with FirebaseContext" // Your web app's Firebase configuration const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, }; interface FirebaseContextType { messaging: Messaging | undefined; serviceWorkerRegistration: ServiceWorkerRegistration | undefined; } export const FirebaseContext = (createContext < FirebaseContextType) | (undefined > undefined); ``` Inside the body of the `FirebaseProvider` component, we'll create `useState` variables to store the `messaging` object and service worker `registration` before running a series of `useEffect` calls to ensure that our code is loading correctly on the client and the browser context supports service workers. Inside of the `initializeFirebase` function we'll create a new `app` and `messaging` instance and store that `messaging` value in state: ```javascript title="Initialize Firebase and store in state" // Initialize Firebase const app = initializeApp(firebaseConfig); const messagingInstance = getMessaging(app); console.log("Firebase messaging initialized:", messagingInstance); setMessaging(messagingInstance); ``` Next, we'll register the service worker we created in the previous step and store that in state as well: ```javascript title="Register service worker and store in state" // Register service worker let registration = await navigator.serviceWorker.getRegistration("/"); if (!registration) { console.log("Registering service worker..."); registration = await navigator.serviceWorker.register( "/firebase-messaging-sw.js", { scope: "/", }, ); } await navigator.serviceWorker.ready; console.log("Service worker ready:", registration); setServiceWorkerRegistration(registration); ``` The `FirebaseProvider` component is also a great place to centralize your logic for handling foreground messages if you want to use them. In this file, you'll see an `onMessage` callback that handles showing foreground notifications using browser APIs. We'll revisit this section of code later when discussing the default behavior of push notifications, e.g. foreground vs. background. Next, we'll return `FirebaseContext.Provider` from `FirebaseProvider` with values for `messaging` and `serviceWorkerRegistration` so we can use them in the following steps: ```javascript title="Export our shared context" {children} ``` Finally, you'll also need to implement this provider component in your app and should consider locating it close to where you have implemented the `KnockProvider` or any other providers that you may need: ```javascript title="Implement the FirebaseProvider underneath KnockProvider" {children} ``` Next, we'll register the user's browser to receive web push notifications and store the `token` in Knock. ## Registering for web push notifications Browser push notifications require user consent for registration, so you'll typically want to create UI in your app to trigger the browser's default notification permission UI based on user input. For this section, we'll create two UI buttons that subscribe and unsubscribe a user from web push notifications. ### Retrieve and save a push token The first component will be responsible for retrieving a push token from the browser using the Firebase SDK and then storing that push token on the current user's Knock profile as channel data. Here is the full code for an example `PushSubscribeButton` component. We'll break this down in the next step: ```javascript title="Creating a component to get push token" "use client"; import { getToken } from "firebase/messaging"; import { useKnockClient } from "@knocklabs/react"; import { useContext, useState } from "react"; import { FirebaseContext } from "../providers/firebase-provider"; import { DeviceTypes, isPWA, useDevice } from "../../../utils/use-device"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; export const PushSubscribeButton: React.FC = () => { const firebaseContext = useContext(FirebaseContext); const device = useDevice(); const [isLoading, setIsLoading] = useState(false); const knock = useKnockClient(); const handleTokenSubmit = async () => { try { if (device === DeviceTypes.IOS && !isPWA()) throw new Error( 'In iPhone you must install the app first, by clicking on the Share button in browser and selecting "Add to Home Screen"' ); if ( !firebaseContext?.messaging || !firebaseContext?.serviceWorkerRegistration ) { throw new Error("Firebase not initialized. Please wait and try again."); } setIsLoading(true); // Request notification permission if (Notification.permission !== "granted") { const result = await Notification.requestPermission(); if (result !== "granted") throw new Error("Notifications are not allowed."); } // Get Firebase token using the registration from context const firebaseToken = await getToken(firebaseContext.messaging, { vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY, serviceWorkerRegistration: firebaseContext.serviceWorkerRegistration, }); // Set up Knock channel data const channelData = await knock.user.setChannelData({ channelId: process.env.NEXT_PUBLIC_KNOCK_FCM_CHANNEL_ID, channelData: { tokens: [firebaseToken] }, }); console.log("Knock channel data set:", channelData); setIsLoading(false); } catch (err: any) { console.error("Subscription error:", err.message); setIsLoading(false); } }; return (
); }; ``` After making some checks to see what type of device we're on and ensuring that the Firebase SDK has loaded, this component uses browser APIs to enable push notifications in the browser. ```javascript title="Trigger browser permission UI" // Request notification permission if (Notification.permission !== "granted") { const result = await Notification.requestPermission(); if (result !== "granted") throw new Error("Notifications are not allowed."); } ``` The `Notification.requestPermission` method will trigger the browser's default UI to let you allow or deny notifications for this domain. Once the browser has notifications enabled for this host, we can use the Firebase SDK to request a token specific to this browser. We call the `getToken` method, and pass in the Firebase context `messaging` and `serviceWorkerRegistration` values, as well as the `process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY` as parameters: ```javascript title="Get browser token using Firebase" // Get Firebase token using the registration from context const firebaseToken = await getToken(firebaseContext.messaging, { vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY, serviceWorkerRegistration: firebaseContext.serviceWorkerRegistration, }); ``` The return value will be a `token` that we can use to send this user push messages on this device and browser. Next we need to store this `token` as [channel data](https://docs.knock.app/managing-recipients/setting-channel-data) on a user in Knock so that we can use it inside of workflows. To do this, we'll use the Knock JavaScript client directly using the `useKnockClient` React hook. The `KnockProvider` exposes this hook to all child components, so it's important that your UI components are nested inside of that provider. Here's an abbreviated code sample showing just the Knock-specific code needed to create a new client and save a user's channel data: ```javascript title="Store token in Knock as channel data" const knock = useKnockClient(); // Set up Knock channel data const channelData = await knock.user.setChannelData({ channelId: process.env.NEXT_PUBLIC_KNOCK_FCM_CHANNEL_ID, channelData: { tokens: [firebaseToken] }, }); console.log("Knock channel data set:", channelData); ``` With this channel data successfully stored you should be ready to use an FCM channel step inside of a workflow. ### Revoking notification permission If a user wants to unsubscribe from push notifications, they generally need to use the UI contained in the browser to do so. Accessing this behavior with JavaScript directly is not allowed for security reasons. However, you can offer users the ability to remove a browser push token from Knock, so that any FCM channel steps they encounter during workflows will be skipped: ```javascript title="Removing token from Knock channel data" import { useKnockClient } from "@knocklabs/react"; const knock = useKnockClient(); const channelData = await knock.user.setChannelData({ channelId: process.env.NEXT_PUBLIC_KNOCK_FCM_CHANNEL_ID, channelData: { tokens: [] }, }); ``` Here, we'll use the `knock.user.setChannelData` method to overwrite the channel data for the FCM channel to an empty array. It's possible to store a push token per device for a user, so if you're using mobile push as well, it may be better to refetch the browser push token and filter it from the `tokens` array before resetting a user's channel data. } /> ### Test a push workflow Before proceeding any further with this guide, you should test your push channel by triggering a workflow with an FCM channel step for the recipient with valid channel data. ## Customizing push notification display Using the Firebase SDK and [Knock payload overrides](https://docs.knock.app/integrations/push/firebase#using-overrides-to-customize-notifications), there are a number of different ways to customize the behavior of your web push notifications. ### Data vs. notification message types One of the common differences you'll see in web push messages is data vs. notification messages. #### Notification messages By default, Knock sends `notification` message type payloads to FCM, which already contain a set of predefined key/value pairs. All `notification` messages will contain a top-level `notification` key, but may also contain an optional `data` payload. With `notification` messages, the FCM SDK and the browser automatically handle displaying messages when the web app is in the background, e.g. not in the current tab, window is minimized, etc. Developers can customize the foreground behavior of web push messages using the Firebase SDK. You can also use payload overrides to send web push platform-specific customization options. Using the `webpush` key in your FCM payload, you can override options on the `Notification` browser API or send any other values allowed by the WebPushConfig object. Here's an example of a payload override JSON object using the default merge strategy: ```json title="Override browser API properties on Notification" { "webpush": { "notification": { "badge": "/assets/logo.png", "icon": "/assets/logo.png" } } } ``` #### Data messages In contrast, `data` messages allow the client application to handle the processing and display of all incoming messages, both in the foreground and background. Unlike `notification` type messages, the key/value pairs for `data` messages are entirely customizable. These key/value pairs are then mapped to `Notification` properties in your code. To send a `data` message in Knock, you'll need to use a payload override using the `__replace__` strategy to override the default `notification` values: ```json title="Replace notification key to send data message" { "__strategy__": "replace", "data": { "title": "Hey from Knock", "body": "Hey {{ recipient.name | default: 'there' }} 👋 - there's a new activity in your workspace." }, "notification": {} } ``` As you can see in this JSON sample, you can still use Liquid to generate dynamic or personalized content, but this payload override is not automatically connected to the live preview functionality in the template editor. Just be aware that it's easy for messaging to drift when using `data` messages. ### Custom foreground and background handling The Firebase SDK provides methods for manually handling both foreground and background messages depending on the type of message you're using. #### Handling foreground messages To handle foreground messages of any type, you'll need to use the `onMessage` handler to show a notification using browser APIs. This code needs access to both the `firebase.messaging` module and your service worker registration: ```javascript title="Handle foreground messages in your app" onMessage(messagingInstance, async (payload) => { console.log("Foreground message received:", payload); if (Notification.permission === "granted" && payload.notification) { const url = payload.data?.url || "/"; await registration!.showNotification( payload?.notification?.title || "", { body: payload.notification?.body, icon: "/assets/logo.png", data: { url }, } ); } }); ``` #### Handling background messages Messages with the `notification` type will be handled in the background automatically by the browser and the FCM SDK. Messages with the `data` type can be handled using the `onBackgroundMessage` handler in the service worker. ```javascript title="Handle background messages with custom behavior" messaging.onBackgroundMessage(async (payload) => { const { data } = payload; //data options const notificationOptions = { body: data?.body, icon: data?.icon || "/assets/logo.png", // Default icon data: { url: data?.url || "/" }, // Store URL in notification data }; self.registration.showNotification(payload.data.title, notificationOptions); }); ``` ## Frequently asked questions Yes, FCM web push is supported in all major modern browsers. This notification is displayed by the browser to let a user know that a site has received a push notification. You may need to update how you handle background messages. # Email template migration Learn how to quickly migrate your email templates to Knock using our MCP server. --- title: Migrate email templates using Knock's MCP server description: Learn how to quickly migrate your email templates to Knock using our MCP server. tags: [ "migrate", "email", "migration", "templates", "MJML", "components", "MCP server", "React Email", ] section: Tutorials --- Storing your email templates in Knock comes with [many advantages](/template-editor/overview#frequently-asked-questions). In this tutorial, we'll walk you through how to leverage Knock's [MCP server](/ai/mcp-server) with the large language model (LLM) of your choice as a low-effort solution to quickly migrate your email templates to Knock. ## Prerequisites Before getting started, you'll need access to the following: - A [Knock account](https://dashboard.knock.app/). - A configured [email provider](/integrations/email/overview) in your Knock account. Navigate to **Integrations** > **Channels** in your dashboard to set this up if you haven't already done so. - An MCP-compatible client (such as Cursor, Claude, or Claude Desktop). We recommend Cursor for working with a large collection of HTML files. - Your existing email template files. Your templates will be upserted to Knock as HTML, so if you're using React Email or another format, you'll want to convert them to HTML before you begin. Knock supports [MJML](/integrations/email/mjml) natively, so MJML templates do not need to be converted. For the purposes of this tutorial, we will assume that you're familiar with prompting an agentic LLM and understand how to provide access to files as context for your prompts. If you're not sure what this means, we recommend checking out this guide on working with context from Cursor. For more information on setting up the Knock MCP server, see the [Get started](/ai/mcp-server#get-started) section of the MCP documentation. ### MCP permissions You'll need to enable the following permissions to complete the steps in this tutorial. | Permission | Requirement | Purpose | | ---------------- | ----------- | ------------------------------------------------------------------ | | Manage resources | Required | Create partials, email layouts, and workflows with email templates | | Commits | Required | Commit your changes to your development environment | | Debug | Optional | View environment logs and inspect environments while testing | | Manage data | Optional | Create test users | | Documentation | Required | Search Knock documentation | ## Background While not required reading, you may want to familiarize yourself with the following Knock concepts before beginning your migration so that you can understand more about the prompts you'll be providing to your LLM and the resources that you'll be creating: - [Workflows](/concepts/workflows) - [Channel steps](/designing-workflows/channel-step) - [Email layouts](/integrations/email/layouts) - [Partials](/template-editor/partials) - Referencing [variables](/template-editor/variables) and [your Knock data](/template-editor/referencing-data) in templates - [Knock liquid helpers](/template-editor/reference-liquid-helpers)

  • We recommend leveraging a model with the largest context window available. In Cursor, you'll likely have the best results if you enable "MAX mode" on your agent. Note that this may have billing implications depending on your Cursor plan.
  • If you have a large number of templates, you will need to repeat the first step below multiple times with a subset of your templates to avoid exceeding the context window limit.{" "} We recommend providing no more than 5-10 template files per prompt. {" "} The prompt is designed so that you can run it multiple times and it will add to the existing template analysis if it has already been run.
  • Cursor will sometimes show a "⚠️" icon on the files that you add to chat context, with a tooltip that says "This file has been significantly condensed to fit in the context limit." If you see this, you should reduce the number of files you are providing as context to your prompt, as proceeding will result in migrated templates that are not pixel-perfect to your original templates.
  • As you work through the migration, start a new chat session to clear your context window if you notice that you are approaching the context window limit for the current session.
  • As always, remember that working with LLMs is non-deterministic. In our testing, we've found that the following prompts can get you very close to a pixel-perfect migration for most use cases, but the final step to test your workflows is crucial to ensure that your migrated templates look the way you want them to. Small adjustments are easy to do via prompt with the MCP server.
} /> ## Migration steps Now that you have everything you need for your migration, you're ready to get started! Each step below contains a prompt that you can provide to your LLM. These prompts are designed to be run one at a time in order to provide the LLM with discrete tasks, thus improving the accuracy of the output. Before creating any resources in Knock, you'll need to analyze your existing email templates to extract layouts and shared components. Copy the prompt below and provide it to your LLM along with access to your template files. We recommend limiting your prompt to 5-10 template files at a time to avoid exceeding the context window limit. Repeat this prompt as many times as necessary until you've analyzed all of your templates. **Prompt:** ```markdown title="LLM prompt to analyze your existing email templates" I need to analyze my existing email templates to prepare for migration to Knock. The final migrated templates need to be pixel-perfect replicas of the original templates. I have provided access to my HTML email template files. Only reference the files that I've explicitly provided as context. Examine each template one at a time and complete the following steps. Reference the Knock documentation with the Knock MCP server as needed. 1. **Extract the email layout**: Identify header, footer, and structural elements that appear in the template. For each identified layout pattern: - Extract the exact HTML structure including , , and tags. - Include all CSS styles from the section - Mark the area where template-specific content will be injected with {{ content }} - Add the layout to a "layouts.md" file in the root of the project, noting which template uses this layout. - Note a key for the layout (use kebab-case based on its purpose) 2. **Extract reusable content blocks for partials**: From the remaining content in the template, identify any content sections that might be appropriately mapped to a Knock partial, which is a reusable block of HTML content that can be used in multiple templates. For each reusable block identified: - Extract the exact HTML content (including any inline styles) and add it to a "partials.md" file in the root of the project, noting which template uses this partial. - Identify any variables within the content that should be converted to Liquid syntax. - Note the key of the partial (use kebab-case based on its purpose) 3. **Extract template-specific content**: - Extract the exact HTML content (including any inline styles) of the template's remaining content and add it to a "templates.md" file in the root of the project, noting which template uses this content. If any partials were identified in step 2, reference them with Knock's Liquid syntax (i.e. {% render 'partial-key' %}) in the extracted content rather than copying the partial's HTML content directly. - Replace any dynamic variables with Liquid syntax. You can use Knock-specific Liquid helpers if needed. - Document the email subject line - Note the key of the layout it should use (from step 1) - List which partials it references (from step 2) - Identify all variables used - Note its name and purpose Important: Extract and document the actual HTML content, not just descriptions. Only suggest creating layouts and partials for content that actually exists in my templates. Do not suggest any additional components that aren't present in the original templates. ``` Based on your analysis, you'll now create the identified partials. Copy the prompt below and provide it to your LLM along with access to the `partials.md` file generated in the previous step. **Prompt:** ```markdown title="LLM prompt to create partials" Using the Knock MCP server, I need to create the partials identified in my "partials.md" file. Be sure you understand all of the following instructions before proceeding. Reference the Knock documentation as needed. Please create each partial from the file one by one, using: 1. The key from the file 2. The type set as "html" 3. The exact HTML content 4. Any dynamic content converted to Liquid syntax with appropriate variable names Once all partials are created, compare the partials in Knock to the list of partials in the "partials.md" file. If there are any differences, update the partials in Knock to match the file. Once they are complete, commit the partials to the development environment. ``` Now you'll create the email layouts identified in your analysis. We'll do this in two parts; first we'll identify any duplicated layouts and determine which of the layouts should be used as your account's "default" layout, then we'll create all of the layouts in Knock. Provide the prompts below to your LLM one at a time, along with access to the `layouts.md` file generated in step 1. **Prompt:** ```markdown title="LLM prompt to identify duplicate and default layouts" I need to identify any duplicate layouts in my "layouts.md" file and determine which of the layouts should be used as my account's "default" layout. Be sure you understand all of the following instructions before proceeding. Reference the Knock documentation as needed. 1. Identify any layouts that are identical to each other in the file. For any identical layouts, consolidate them into a single layout and update the list of email templates that use this layout. 2. Determine which layout should be used as the "default" layout. The default layout is the one that is used most often in the file. Update its key to "default". 3. For any consolidated or modified layouts, update the key referenced in "templates.md" to use the correct layout key. ``` ```markdown title="LLM prompt to create email layouts" Using the Knock MCP server, I need to create the email layouts identified in my "layouts.md" file. Be sure you understand all of the following instructions before proceeding. Reference the Knock documentation as needed. First, check if the analysis identified a single default layout that is used more often than others. If so: 1. Update the existing "default" layout in Knock with that HTML structure, replacing the template-specific content area with {{ content }} 2. Convert any dynamic variables to Liquid syntax 3. Preserve all original styles from the analysis 4. If any of the previously created partials should be used in this layout, reference them using {% render 'partial-key' %} 5. Include all CSS styles that were documented in the analysis 6. Create a text version that includes {{ content }} appropriately 7. Ensure the layout is a complete, valid HTML document Then, follow the same steps to create a new layout for each additional layout in the analysis, using an appropriate unique key (use kebab-case based on its purpose) and the HTML structure from the analysis. Repeat this process until all layouts are created. Important: Use the exact HTML structure from the analysis without modifications beyond what's necessary for the {{ content }} insertion. Once all layouts are created, compare the layouts in Knock to the list of layouts in the analysis report. Once they are complete, commit the layouts to the development environment. ``` For each email template, you'll create a workflow with an email step whose template leverages the layouts and partials noted in our analysis. **Prompt:** ```markdown title="LLM prompt to create templates in workflows" Using the Knock MCP server, I need to create workflows for each of the templates identified in "templates.md". Be sure you understand all of the following instructions before proceeding. Reference the Knock documentation as needed. Please create a workflow for each template, one at a time: 1. Create a new workflow with a key based on the template's purpose (use kebab-case) 2. Add a descriptive name and description based on the template's function 3. Add appropriate categories if identified in the analysis 4. Create an email step in the workflow with: - The original subject line from the template - Any variables converted to Liquid syntax - The layout key that was mapped to this template in the analysis - Any partials that were identified for this template in the analysis, referencing them with correct keys - The exact template-specific HTML content from the analysis report 5. Ensure no styling is added beyond what was in the original template 6. IMPORTANT: Preserve all HTML structure exactly as documented in the analysis; do not reference the original template files. After creating each workflow, provide: - The workflow key used - Confirmation that the email step was configured correctly Start with the first template and proceed one by one. Repeat this process until all workflows are created. Commit the workflows to the development environment. ``` After creating all resources, you need to verify that the migration was successful. We'll ask our LLM to provide a summary report of the migration to ensure that nothing was missed. Provide the prompt below to your LLM along with access to the `templates.md` file. **Prompt:** ```markdown title="LLM prompt to verify the migration" I've completed migrating my email templates to Knock. Please help me verify the migration was successful by generating a summary report that includes the following: 1. **List all created resources**: List all the partials, layouts, and workflows that were created in my development environment. Cross-reference "templates.md" to ensure nothing was missed. 2. **Check resource relationships**: Based on the mappings in the analysis report, verify that: - Each workflow is using the correct layout as specified - Partials are properly referenced where the analysis indicated they should be used - All resources are in a valid state 3. **Validate completeness**: Check against the original analysis to ensure: - All templates listed have corresponding workflows - All partials identified were created and used correctly - All layouts documented were implemented Please provide a summary report showing: - A list of all workflows created - Comparison of analysis vs. actual implementation - Any discrepancies found ``` Once you've summarized the migration and verified that you've created all of the resources identified, you can use the Knock MCP server to test the migrated templates. Make sure that you update the prompt to target the correct test user's ID. You can use the MCP server to create a user that you'd like to send the test emails to, if you don't already have one in your development environment. You should update the example prompt to use a real email address and ID for the user so that you can view the test emails in your inbox. **Prompt:** ```markdown title="Example LLM prompt to create a user" I need to create a user in my Knock development environment. Please help me create a user with the following information: - Email: test@example.com - Name: Test User - ID: test-user ``` **Prompt:** ```markdown title="LLM prompt to test your workflows" Using the Knock MCP server, I need to test the workflows in my Knock development environment. Be sure you understand all of the following instructions before proceeding. Reference the Knock documentation as needed. First, create a document for our testing process. Next, list all of the workflows in my Knock development environment. Add them to the testing document. Please help me test the workflows one at a time. For each workflow, I want to: 1. **Generate test data**: Generate and document valid test data for the workflow using the workflow's schema. 2. **Trigger the workflow**: Trigger the workflow with the Knock MCP server, using the test data generated in the previous step. The recipient should be my test user with ID "FILL IN TEST USER ID HERE". 3. **Wait for confirmation**: Wait for me to confirm that the workflow was triggered successfully and that the test email was received. If I have feedback on the email that I received, note it in the summary report. 4. **Mark the workflow as tested**: If the workflow was triggered successfully and I do not have any further feedback, mark the workflow as tested in the summary report. 5. **Stop**: Ask me to confirm that I'm ready to test the next workflow before proceeding. We'll proceed through the list of workflows one at a time. Once we've tested all of them, please provide a final summary of the test results, including any outstanding feedback that needs to be addressed. ``` # Sender domain migration Learn how to warm up a new sending domain and protect deliverability by routing traffic gradually with channel groups. --- title: "Sender domain migration" description: "Learn how to warm up a new sending domain and protect deliverability by routing traffic gradually with channel groups." tags: [ "email", "sender domain", "domain warming", "warming", "warmup", "channel groups", "migration", "deliverability", ] section: Tutorials --- This page covers how to migrate to a new sending domain in Knock, warming it gradually to avoid putting your email deliverability at risk. You'll set up the new domain as a new email [channel](/integrations/overview) in your Knock account, create a [channel group](/integrations/overview#channel-groups) containing both your new and existing email channels, and set conditions on the channel group to route traffic between the two. You'll use the channel group to raise the new domain's share of traffic on a warming schedule, retiring the old domain once deliverability is confirmed. ## Prerequisites This tutorial assumes you already have a Knock account with a configured [email provider](/integrations/email/overview) sending your production traffic, and that your new sending domain is set up and verified with a provider. SPF, DKIM, and DMARC should already be configured and passing for the new domain. You'll also need access to the [Management API](/developer-tools/management-api) to create and configure your channel group, and the [Knock CLI](/cli/overview) to update your workflows in bulk. ## Domain warming schedule Plan out your warming schedule before you start routing any traffic. Use Knock's [email domain warmup calculator](https://knock.app/tools/email-domain-warmup-calculator) to generate a schedule sized to your volume. As a general rule, plan for roughly 30 days to reach full volume, starting at a low daily volume and increasing it gradually as deliverability holds up at each stage. Spread each day's volume evenly across your sending window rather than sending it all in a single burst. You can implement your channel group routing before your domain warming schedule begins. Keep your channel group conditions set to route 100% of traffic to your existing domain's channel until you're ready to begin migrating. ## Traffic routing mechanisms When planning your migration, decide which controls you'll use to route each workflow recipient run between domains. We recommend combining percentage-based or recipient-based routing with a workflow allowlist, giving you fine-grained control over what moves and when. - **Route a percentage of traffic.** Use the `is_in_random_cohort` [operator](/integrations/overview#random-cohorts) in your channel group conditions to route a share of recipients to the new domain, raising the percentage as your warming schedule progresses. Knock hashes the recipient ID against the channel group to decide which cohort a recipient lands in, so recipients move to the new domain consistently rather than bouncing between domains run to run. Raising the percentage only adds recipients to the new domain's cohort, so anyone already routed there stays there. - **Route traffic based on recipient attributes.** Target your most engaged recipients early in the warming process by segmenting them into an [audience](/concepts/audiences), either a static one you add to over time or a dynamic one based on user properties. Reference this audience in your channel group conditions to route their emails through the new domain. - **Route traffic on a per-workflow basis.** To keep all traffic for your most critical workflows like password resets routed to your established domain, include a workflow allowlist in your channel group configuration. Gate critical workflows until you've built confidence in the new domain's reputation. Referencing an [environment variable](/concepts/variables) as the allowlist rather than hardcoding workflow keys into the condition enables you to update which workflows are included without editing the channel group itself. We recommend considering a combination of engagement and criticality when deciding the order in which to migrate your workflows to the new domain: 1. **High-engagement, low-criticality workflows first.** For example, an alert or digest recipients open often, where a delayed or missed message doesn't interrupt a critical business process. 2. **High-engagement, high-criticality workflows next.** Recipients reliably engage with password resets and account notifications, but their criticality means you should wait until you've validated the new domain's reputation on lower-stakes traffic first. 3. **High-volume, low-engagement workflows last.** Digests or notifications with a large recipient base but low open rates carry the most reputation risk per message and benefit most from a domain that's already established. ## Set up traffic routing Navigate to Integrations > Channels in your dashboard and add a new email channel configured with your new domain as the sender. The new domain should be configured with your provider and passing SPF, DKIM, and DMARC checks before sending any production traffic through it. Before creating your channel group, set up the [controls](#traffic-routing-mechanisms) you'll use to manage the flow of traffic between domains. This might include creating a rollout audience or saving an environment variable to hold your workflow allowlist. You'll reference these in the channel group's conditions, updating your control mechanism (e.g., add users to an audience) over time to increase traffic. Percentage-based routing is configured directly on the channel group, with no prerequisite setup. Regardless of approach, we recommend starting with values that keep production traffic routed to your existing domain, ensuring the new domain remains inactive until you're ready. For example, start with an empty allowlist, an empty audience, or a cohort percentage of `0`. Using the [Management API](/mapi-reference/channel_groups), create a [channel group](/integrations/overview#channel-groups) that contains both your existing domain's channel and your new domain's channel. Set the group's `operator` to `any` so each recipient routes through a single channel, then add [channel rules](/integrations/overview#channel-rules) in this order: 1. An `unless` rule pointing at your existing domain's channel, conditioned on the workflow key appearing in your allowlist variable. Add this rule only if you're gating on a workflow allowlist. An `unless` rule matches when its condition fails, so workflows outside the allowlist route to your existing domain before Knock evaluates the rules below. 2. An `if` rule pointing at the new domain's channel, conditioned on the rollout criteria you set up in the previous step. If you're routing by percentage, use a [cohort rule](/integrations/overview#random-cohorts) and start its percentage at `0` so nothing routes to the new domain until you're ready to raise it. 3. An `always` rule pointing at your existing domain's channel, which catches every recipient the rules above didn't match. Throughout your warming schedule, these rules will control how much of your email traffic reaches the new domain. After creating your channel group, update each workflow's [email step](/designing-workflows/channel-step) to reference the channel group instead of your original email channel. With the channel group in place, all email steps will defer to your channel group conditions for routing. No other updates are required at the workflow level. This is an account-wide change across every workflow that sends email, so we recommend making the update programmatically with the Knock CLI. Use [knock workflow pull](/cli/workflow/pull) to download your workflows locally, update each email step to reference the channel group's key, then use [knock workflow push](/cli/workflow/push) to apply the change across all of them. Note that any traffic routing enabled on the channel group will immediately go into effect after this update. We recommend keeping all traffic routed to your existing domain's channel while you make this change, so you can validate it before any behavior shifts. When you're ready to begin routing traffic to the new domain, expand your rollout using the mechanism you set up in the "Implement control mechanisms" step. If you're routing a random cohort by percentage, raise the argument on the cohort condition to match the share of volume your warming schedule calls for. As deliverability holds up at each stage of your warming schedule, continue expanding it to route a larger share of traffic to the new domain. After each traffic increase, check spam complaint and bounce rates. If performance softens, hold at the current volume for a few days before continuing. If it deteriorates materially, scale back the rollout the same way, routing a larger share of traffic back to your existing domain. ## Frequently asked questions Not to route them. The channel group sits between your workflow and the underlying channels, so the same template renders and sends regardless of which domain the channel group routes to. If your templates reference the sending domain directly (for example, in a template-level `from` field), review those references before starting the migration. A rebrand alongside the migration will also require a branding update anywhere you're using a brand name, logo, or color. Your [branding](/template-editor/branding) properties live at the account level and render through the `vars.branding.*` namespace, so updating them in your account settings changes every message you send at once. If you want to stage the change ahead of time, gate your new branding on an environment variable and update your [layouts](/integrations/email/layouts) wherever branding renders: ```liquid title="Gate layout branding on an environment variable" {% if vars.use_new_branding %} Company logo {% else %} Company logo {% endif %} ``` When you begin routing traffic to the new sending domain, flip the variable in production so every message carries the new branding. Flipping it back reverts your branding if you need to roll back. Once the migration is complete, update your branding properties in your account settings to your new assets and remove the conditions from your layouts. Your layouts go back to reading `vars.branding.*` directly, with no leftover migration logic. Knock's [custom domains](/manage-your-account/custom-domains) feature controls the domain used for link tracking and the hosted preference center, and is configured independently of your sending channels. Changing your sending domain has no effect on either one until you reassign the custom domain yourself. Move your tracking domain when you start routing production traffic to the new sending domain. A custom tracking domain is assigned to an environment as a single value, so it applies to every message that environment sends. There's no way to split tracking between two domains the way a channel group splits sending, which means the cutover is all or nothing. Inbox providers weigh how closely your sending domain and the domains in your links align, so a mismatch works against the domain you're trying to warm. Your existing domain already has an established reputation and is on its way out, so favor the new domain: set up a tracking subdomain of it, verify it, and assign it to your production environment as your first traffic moves over. Messages already sent through the old tracking domain keep working as long as that domain stays verified in Knock with its DNS record intact. Yes. Because your workflows reference the channel group rather than a specific channel, you can adjust the routing rules to stop sending traffic to your new domain at any time. All email steps will fall back to your original domain's channel. No. Cohort assignment is deterministic, so a given recipient lands in the same cohort on every workflow run and raising the percentage only widens the cohort routed to the new domain. Assignment is scoped to the channel group itself, so deleting and recreating the channel group would disrupt which recipients land in the cohort. Edit the percentage on your existing channel group rather than recreating it mid-migration. Recipients will be removed from the cohort in the reverse of the order they entered it. After being removed from the cohort, they will be routed to the existing domain via the fallback rule. # Guides in Vue.js Learn how to implement Knock guides in a Vue.js application using the Knock JavaScript SDK. --- title: Implementing Knock guides in Vue.js description: Learn how to implement Knock guides in a Vue.js application using the Knock JavaScript SDK. tags: ["guides", "vue", "in-app"] section: Tutorials --- In this tutorial we'll walk through how to implement [Knock guides](/in-app-ui/guides/overview) in a Vue.js application. Knock's [guide provider and hooks](/in-app-ui/guides/render-guides#client-side-sdks) are built for React, so we'll use the underlying [Knock JavaScript SDK](/in-app-ui/javascript/sdk/reference) directly to recreate the same behavior using Vue composables. ## Prerequisites You'll need: - A Vue.js 3 project - A Knock account with at least one [guide created](/in-app-ui/guides/create-guides) - Your [Knock public API key](/developer-tools/api-keys) - Your [guide channel ID](/in-app-ui/guides/render-guides#guide-identifiers) ## Architecture overview The [Knock React SDK](/in-app-ui/react/sdk/overview) uses a provider/hook pattern (`KnockProvider`, `KnockGuideProvider`, `useGuide`). In Vue.js, you replicate this with `provide`/`inject` and composables. Here's a comparison of Knock's React components and their Vue equivalents. | React | Vue | | -------------------- | -------------------------------------------------------------------- | | `KnockProvider` | A `KnockProvider.vue` component that uses Vue's `provide`/`inject`. | | `KnockGuideProvider` | `useKnockGuideClient()` composable, used inside `KnockProvider.vue`. | | `useGuide()` hook | A `useGuide()` composable. | The Knock client must initialize in a specific sequence before guides are available: the Knock client initializes first, the user authenticates, then the guide client initializes and fetches guides. ## Implementation ```bash title="Install the JavaScript SDK and URL pattern polyfill" npm install @knocklabs/client urlpattern-polyfill ``` The Knock guides client uses the [`URLPattern` API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) to evaluate guide activation rules based on the current URL. `URLPattern` is not yet available in all browsers, so the polyfill ensures consistent behavior across environments. Import the polyfill at the top of your app entry point (e.g. `main.ts`): ```typescript title="main.ts" import "urlpattern-polyfill"; // ... rest of your app setup ``` This composable initializes the Knock client and authenticates the user. It uses `shallowRef` for the client instance — rather than `reactive()` — to avoid TypeScript errors from Vue's deep unwrapping of class internals. The client instance is shared via the returned `knockClient` ref, which `useKnockGuideClient` takes as a parameter. ```typescript title="composables/useKnock.ts" import { shallowRef } from "vue"; import Knock from "@knocklabs/client"; export function useKnock() { // shallowRef avoids Vue unwrapping class internals, which causes TS errors with reactive() const knockClient = shallowRef(null); const initializeKnock = async (apiKey: string): Promise => { if (!apiKey) { throw new Error("API key is required to initialize Knock"); } if (knockClient.value) { return knockClient.value; } knockClient.value = new Knock(apiKey); return knockClient.value; }; const authenticate = (user: { id: string }): void => { if (!knockClient.value) { throw new Error("Knock client must be initialized before authentication"); } knockClient.value.authenticate(user); }; return { knockClient, initializeKnock, authenticate, }; } ``` This composable initializes the guide client, subscribes to guide updates, and exposes methods for fetching and selecting guides. The store subscription keeps Vue's reactive state in sync with the guide client's internal store. ```typescript title="composables/useKnockGuideClient.ts" import { shallowRef, reactive, computed, type Ref } from "vue"; import { KnockGuideClient, type KnockGuide } from "@knocklabs/client"; import type Knock from "@knocklabs/client"; export function useKnockGuideClient(knockClient: Ref) { // shallowRef avoids Vue unwrapping KnockGuideClient class internals — // the same reason useKnock uses shallowRef for the Knock client const guideClient = shallowRef(null); const state = reactive<{ guides: KnockGuide[]; loading: boolean; error: Error | null; }>({ guides: [], loading: false, error: null, }); const isReady = computed( () => guideClient.value && knockClient.value && !state.loading, ); const initializeGuideClient = (channelId: string): void => { if (!knockClient.value) { throw new Error("Knock client is required"); } guideClient.value = new KnockGuideClient(knockClient.value, channelId); // Subscribe to store changes to keep guide state reactive guideClient.value.store.subscribe(() => { if (guideClient.value) { const storeState = guideClient.value.store.state; state.guides = guideClient.value.selectGuides(storeState); } }); // Join the socket channel for live guide updates (synchronous, returns void) guideClient.value.subscribe(); }; const fetchGuides = async (): Promise => { if (!guideClient.value) return; state.loading = true; try { await guideClient.value.fetch(); } catch (err) { state.error = err instanceof Error ? err : new Error(String(err)); } finally { state.loading = false; } }; return { guideClient, guides: computed(() => state.guides), loading: computed(() => state.loading), error: computed(() => state.error), isReady, initializeGuideClient, fetchGuides, }; } ``` The provider component initializes Knock, authenticates the user, and makes the guide client available to all child components via `provide`. ```vue title="components/KnockProvider.vue" ``` Use the provider at the root of any view that needs guide functionality: ```vue title="Using the provider" ``` The `useGuide` composable mirrors the React [`useGuide`](/in-app-ui/react/sdk/hooks/use-guide) hook — it fetches a single guide by `type` or `key`. It takes the `knockGuides` instance as a parameter, which the calling component retrieves via `inject` from `KnockProvider`. ```typescript title="composables/useGuide.ts" import { computed } from "vue"; interface GuideFilters { type?: string; key?: string; } export function useGuide( knockGuides: ReturnType< typeof import("./useKnockGuideClient").useKnockGuideClient > | null, options: GuideFilters = {}, ) { const { type, key } = options; if (!knockGuides) { console.warn("knockGuides instance is required for guide functionality"); return { step: computed(() => null), guides: computed(() => []), loading: computed(() => false), error: computed(() => null), isReady: computed(() => false), }; } // Filter from the reactive guides ref so step recomputes whenever the // store subscription updates state.guides in useKnockGuideClient const step = computed(() => { const match = knockGuides.guides.value.find((g) => { if (type && g.type !== type) return false; if (key && g.key !== key) return false; return true; }); // getStep() applies the SDK's display-ordering logic rather than // indexing steps directly return match?.getStep() ?? null; }); return { step, guides: knockGuides.guides, loading: knockGuides.loading, error: knockGuides.error, isReady: knockGuides.isReady, }; } ``` Each guide step exposes a `content` object whose shape is determined by your [message type schema](/in-app-ui/message-types#schemas). Common properties include: | Property | Type | Description | | ------------------ | ---------------------------------- | ----------------------------------- | | `title` | `string` | Guide title text. | | `body` | `string` | Guide body content (supports HTML). | | `primary_button` | `{ text: string; action: string }` | Primary action button. | | `secondary_button` | `{ text: string; action: string }` | Secondary action button. | | `dismissible` | `boolean` | Whether the guide can be dismissed. | Each step also provides these methods for [tracking engagement](/in-app-ui/guides/handling-engagement): | Method | Description | | -------------------- | --------------------------------------------------------------------------------------------------------- | | `markAsSeen()` | Marks the guide as seen. | | `markAsInteracted()` | Marks the guide as interacted, recording a click event or other user interaction, with optional metadata. | | `markAsArchived()` | Marks the guide as archived, removing the guide from eligibility for the user. | With the composables in place, you can build components to render guides. This example shows a banner component that renders a guide, tracks engagement, and handles dismissal. ```vue title="components/GuideBanner.vue" ``` The implementation above authenticates users by `id` only. For production use, you should also pass a [user token](/in-app-ui/security-and-authentication) — a short-lived JWT signed by your backend — to verify that the client is authorized to act on behalf of the user. ## Troubleshooting See [debugging guides](/in-app-ui/guides/debugging-guides) for general troubleshooting guidance related to guide activation and targeting. You may also encounter these Vue-specific issues: | Issue | Solution | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Guides are not appearing after authentication. | Check that `knock.knockClient.value` exists before calling `initializeGuideClient`. Ensure `authenticate` is called before `initializeGuideClient` in `onMounted`. | | The store subscription is not updating components. | Confirm that `guideClient.value.store.subscribe` is being called inside `initializeGuideClient`, before `guideClient.value.subscribe()`. | | [Guide activation rules](/in-app-ui/guides/create-guides#activation) aren't working. | Ensure `urlpattern-polyfill` is imported at the top of your app entry point before initializing Knock: `import "urlpattern-polyfill"` | ## Related docs - [Knock guides overview](/in-app-ui/guides/overview) - [Knock JavaScript SDK reference](/in-app-ui/javascript/sdk/reference) - [Message types and schemas](/in-app-ui/message-types)