diff --git a/components/ui/MultiLangCodeBlock.tsx b/components/ui/MultiLangCodeBlock.tsx index 730a13dca..d1de508fb 100644 --- a/components/ui/MultiLangCodeBlock.tsx +++ b/components/ui/MultiLangCodeBlock.tsx @@ -43,6 +43,8 @@ const snippets = { .default, "users.setChannelData": require("../../data/code/users/set-channel-data") .default, + "users.setChannelDataDevices": + require("../../data/code/users/set-channel-data-devices").default, "users.setChannelData-push": require("../../data/code/users/set-channel-data-push").default, "users.setChannelData-one-signal": diff --git a/content/integrations/push/apns.mdx b/content/integrations/push/apns.mdx index 4305c2210..1112cb712 100644 --- a/content/integrations/push/apns.mdx +++ b/content/integrations/push/apns.mdx @@ -81,6 +81,9 @@ Overrides are merged into the notification payload sent to APNs. See the Apple developer documentation. -| Property | Type | Description | -| -------- | -------- | ------------------------- | -| tokens\* | string[] | One or more device tokens | +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`. | diff --git a/content/integrations/push/aws-sns.mdx b/content/integrations/push/aws-sns.mdx index 49012476f..8fe1e97da 100644 --- a/content/integrations/push/aws-sns.mdx +++ b/content/integrations/push/aws-sns.mdx @@ -305,6 +305,9 @@ See Setting up an Amazon SNS platform endpoint for mobile notifications for more details on creating platform endpoints. -| Property | Type | Description | -| ------------- | -------- | -------------------------------------------------------------------------------------------- | -| target_arns\* | string[] | One or more platform endpoint ARNs associated with a platform application and a device token | +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`. | diff --git a/content/integrations/push/device-metadata.mdx b/content/integrations/push/device-metadata.mdx new file mode 100644 index 000000000..2f514a990 --- /dev/null +++ b/content/integrations/push/device-metadata.mdx @@ -0,0 +1,97 @@ +--- +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](/concepts/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"] + } +} +``` diff --git a/content/integrations/push/expo.mdx b/content/integrations/push/expo.mdx index 28c414822..9b5207884 100644 --- a/content/integrations/push/expo.mdx +++ b/content/integrations/push/expo.mdx @@ -60,6 +60,9 @@ Overrides are merged into the notification payload sent to Expo. See the Expo developer documentation. -| Property | Type | Description | -| -------- | -------- | ------------------------- | -| tokens\* | string[] | One or more device tokens | +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`. | diff --git a/content/integrations/push/firebase.mdx b/content/integrations/push/firebase.mdx index effeb0093..2e5940311 100644 --- a/content/integrations/push/firebase.mdx +++ b/content/integrations/push/firebase.mdx @@ -142,8 +142,11 @@ The following are common FCM errors you may see in your message delivery logs: ## 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 and deliver a notification for each unique token. +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. -| Property | Type | Description | -| -------- | -------- | ------------------------- | -| tokens\* | string[] | One or more device tokens | +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`. | diff --git a/content/integrations/push/overview.mdx b/content/integrations/push/overview.mdx index e0d7ef434..829fc7ee1 100644 --- a/content/integrations/push/overview.mdx +++ b/content/integrations/push/overview.mdx @@ -12,7 +12,8 @@ Knock supports sending push notifications directly to native services such as Ap - **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**: if a recipient's token(s) is invalid resulting in a `bounced` message when attempting to send, remove the token. +- [**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 diff --git a/content/integrations/push/token-deregistration.mdx b/content/integrations/push/token-deregistration.mdx index 5adffc1c1..cb2cb9bc6 100644 --- a/content/integrations/push/token-deregistration.mdx +++ b/content/integrations/push/token-deregistration.mdx @@ -6,7 +6,7 @@ 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 from a recipient's corresponding channel data if that token results in a `bounced` message on send. +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. diff --git a/content/managing-recipients/setting-channel-data.mdx b/content/managing-recipients/setting-channel-data.mdx index 37388c2ca..4e5b1b3cf 100644 --- a/content/managing-recipients/setting-channel-data.mdx +++ b/content/managing-recipients/setting-channel-data.mdx @@ -13,7 +13,7 @@ At Knock we call this concept ` - 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. +- 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 @@ -134,27 +134,63 @@ Any previously set channel data can be cleared by issuing an `unsetChannelData` ## Provider data requirements -Channel data requirements for each provider are listed below. Typically `channel_data` comprises a `token` or other value that is used to uniquely identify a user's device. +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 - - - | Property | Type | Description | - | -------- | ---------- | ------------------------- | - | tokens\* | `string[]` | One or more device tokens | +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](/concepts/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](/concepts/translations#supported-locales) of the device. | +| timezone | `string` | The timezone of the device. Must be a valid [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | + +#### 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. | + - | Property | Type | Description | - | -------- | ---------- | ------------------------- | - | tokens\* | `string[]` | One or more device tokens | + 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. | - | Property | Type | Description | - | -------- | ---------- | ------------------------- | - | tokens\* | `string[]` | One or more device tokens | + 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. | diff --git a/data/code/users/set-channel-data-devices.ts b/data/code/users/set-channel-data-devices.ts new file mode 100644 index 000000000..8ad1dcde3 --- /dev/null +++ b/data/code/users/set-channel-data-devices.ts @@ -0,0 +1,170 @@ +const languages = { + curl: ` +# Find the channel_id in your Knock dashboard under Integrations > Channels +curl -X PUT https://api.knock.app/v1/users/1/channel_data/8209f26c-62a5-461d-95e2-a5716a26e652 \\ + -H "Content-Type: application/json" \\ + -H "Authorization: Bearer sk_test_12345" \\ + -d '{ + "data": { + "devices": [ + { "token": "user_device_token_1", "locale": "en-US", "timezone": "America/New_York" }, + { "token": "user_device_token_2" } + ] + } + }' +`, + node: ` +import Knock from "@knocklabs/node"; +const knockClient = new Knock({ apiKey: process.env.KNOCK_API_KEY }); + +// Find this value in your Knock dashboard under Integrations > Channels +const APNS_CHANNEL_ID = "8209f26c-62a5-461d-95e2-a5716a26e652"; + +await knockClient.users.setChannelData(user.id, APNS_CHANNEL_ID, { + data: { + devices: [ + { token: userDeviceToken1, locale: "en-US", timezone: "America/New_York" }, + { token: userDeviceToken2 }, + ], + }, +}); +`, + python: ` +from knockapi import Knock +client = Knock(api_key="sk_12345") + +# Find this value in your Knock dashboard under Integrations > Channels +apns_channel_id = "8209f26c-62a5-461d-95e2-a5716a26e652" + +client.users.set_channel_data( + user_id=user.id, + channel_id=apns_channel_id, + data={ + "devices": [ + {"token": "user_device_token_1", "locale": "en-US", "timezone": "America/New_York"}, + {"token": "user_device_token_2"} + ] + } +) +`, + ruby: ` +require "knockapi" + +client = Knockapi::Client.new(api_key: "sk_12345") + +# Find this value in your Knock dashboard under Integrations > Channels +apns_channel_id = "8209f26c-62a5-461d-95e2-a5716a26e652" + +client.users.set_channel_data(user.id, apns_channel_id, { + devices: [ + { token: "user_device_token_1", locale: "en-US", timezone: "America/New_York" }, + { token: "user_device_token_2" } + ] +}) +`, + csharp: ` +var knockClient = new KnockClient( + new KnockOptions { ApiKey = "sk_12345" }); + +var devices = new List> { + new() { { "token", "user_device_token_1" }, { "locale", "en-US" }, { "timezone", "America/New_York" } }, + new() { { "token", "user_device_token_2" } } +}; + +// Find this value in your Knock dashboard under Integrations > Channels +var apnsChannelId = "8209f26c-62a5-461d-95e2-a5716a26e652"; + +var channelData = new Dictionary{ + { "devices", devices } +}; + +await knockClient.Users.SetChannelData(user.Id, apnsChannelId, channelData); +`, + elixir: ` +knock_client = MyApp.Knock.client() + +# Find this value in your Knock dashboard under Integrations > Channels +apns_channel_id = "8209f26c-62a5-461d-95e2-a5716a26e652" + +Knock.Users.set_channel_data(knock_client, user.id, apns_channel_id, %{ + devices: [ + %{token: "user_device_token_1", locale: "en-US", timezone: "America/New_York"}, + %{token: "user_device_token_2"} + ] +}) +`, + php: ` +use Knock\\KnockSdk\\Client; + +$client = new Client('sk_12345'); + +// Find this value in your Knock dashboard under Integrations > Channels +$apns_channel_id = "8209f26c-62a5-461d-95e2-a5716a26e652"; + +$client->users()->setChannelData($user->id(), $apns_channel_id, [ + 'devices' => [ + ['token' => 'user_device_token_1', 'locale' => 'en-US', 'timezone' => 'America/New_York'], + ['token' => 'user_device_token_2'] + ] +]); +`, + go: ` +import ( + "context" + + "github.com/knocklabs/knock-go" + "github.com/knocklabs/knock-go/option" + "github.com/knocklabs/knock-go/param" +) + +ctx := context.Background() +knockClient := knock.NewClient(option.WithAPIKey("sk_12345")) + +// Find this value in your Knock dashboard under Integrations > Channels +apnsChannelId := "8209f26c-62a5-461d-95e2-a5716a26e652" + +_, _ = knockClient.Users.SetChannelData(ctx, user.ID, apnsChannelId, knock.UserSetChannelDataParams{ + ChannelDataRequest: knock.ChannelDataRequestParam{ + Data: param.Raw(map[string]interface{}{ + "devices": []map[string]interface{}{ + {"token": "user_device_token_1", "locale": "en-US", "timezone": "America/New_York"}, + {"token": "user_device_token_2"}, + }, + }), + }, +}) +`, + java: ` +import app.knock.api.client.KnockClient; +import app.knock.api.client.okhttp.KnockOkHttpClient; +import app.knock.api.models.users.ChannelData; +import app.knock.api.models.users.UserSetChannelDataParams; +import app.knock.api.core.JsonValue; +import java.util.List; +import java.util.Map; + +KnockClient client = KnockOkHttpClient.builder() + .apiKey("sk_12345") + .build(); + +// Find this value in your Knock dashboard under Integrations > Channels +String apnsChannelId = "8209f26c-62a5-461d-95e2-a5716a26e652"; + +List> devices = List.of( + Map.of("token", "user_device_token_1", "locale", "en-US", "timezone", "America/New_York"), + Map.of("token", "user_device_token_2") +); + +UserSetChannelDataParams params = UserSetChannelDataParams.builder() + .userId(user.getId()) + .channelId(apnsChannelId) + .data(UserSetChannelDataParams.Data.builder() + .putAdditionalProperty("devices", JsonValue.from(devices)) + .build()) + .build(); + +ChannelData channelData = client.users().setChannelData(params); +`, +}; + +export default languages; diff --git a/data/sidebars/integrationsSidebar.ts b/data/sidebars/integrationsSidebar.ts index f4ae8dd38..a1e6b8b65 100644 --- a/data/sidebars/integrationsSidebar.ts +++ b/data/sidebars/integrationsSidebar.ts @@ -109,6 +109,7 @@ export const INTEGRATIONS_SIDEBAR: SidebarContent[] = [ pages: [ { slug: "/overview", title: "Overview" }, { slug: "/token-deregistration", title: "Token deregistration" }, + { slug: "/device-metadata", title: "Device metadata" }, { slug: "/aws-sns", title: "Amazon SNS" }, { slug: "/apns", title: "Apple (APNS)" }, { slug: "/expo", title: "Expo (React Native)" }, diff --git a/data/specs/api/customizations.yml b/data/specs/api/customizations.yml index 2e5b83179..d9e190268 100644 --- a/data/specs/api/customizations.yml +++ b/data/specs/api/customizations.yml @@ -82,7 +82,7 @@ resources: description: |- [Channel data](/managing-recipients/setting-channel-data) is channel-specific information stored on a Knock [user](/api-reference/users) or [object](/api-reference/objects) that's needed to deliver a notification to an end provider. - For a push channel, this includes device-specific tokens that map the recipient to the device they use. For chat apps, such as Slack, this includes the access token used to send notifications to a customer's Slack channel. + For a push channel, this includes device-specific tokens that map the recipient to the device they use, as well as [device metadata](/integrations/push/device-metadata) for supported providers. For chat apps, such as Slack, this includes the access token used to send notifications to a customer's Slack channel. The shape of the `data` payload varies depending on the channel type; you can learn more about channel data schemas [here](/send-notifications/setting-channel-data#provider-data-requirements). schedules: