# Create An Activity
Source: https://docs.zixflow.com/api-reference/activity-list/create
POST https://api.zixflow.com/api/v1/collection-records/activity-list
This endpoint creates an activity or task within a collection.
#### Description
This API endpoint facilitates the creation of an activity or task within a specified collection. The activity can be associated with a collection record and is defined by various parameters provided in the request body.
#### Body
Specifies the type of icon associated with the activity. Possible values include 'emoji', 'interaction', 'messaging\_app', etc.
Defines the specific value of the icon based on the iconType.
* For 'emoji', provide any emoji.
* For 'interaction', valid values include 'call', 'meeting', 'message', 'coffee', 'lunch', 'event', 'drink', etc.
* For 'messaging\_app', valid values include 'whatsapp', 'twitter', 'linkedin', 'hangout', 'skype', 'slack', 'imessage', 'facebook\_messenger', 'signal', 'discord', 'wechat', 'telegram', 'viber', etc.
The name or title of the activity.
Specifies the scheduled time for the activity in the format "YYYY-MM-DDTHH:mm:ss.SSSZ".
A description providing additional details about the activity.
The ID of the collection record associated with the activity.
The ID of the status attribute indicating the current status of the activity.
#### Response
Indicates the success or failure of the activity creation. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful creation of the activity with the message "Record created successfully!"
The unique identifier for the created activity.
An object containing details of the created activity.
The unique identifier for the activity.
The name or title of the activity.
The scheduled time for the activity in the format "YYYY-MM-DDTHH:mm:ss.SSSZ".
Additional details or description of the activity.
The ID of the associated collection record, if any.
Details of the assignee for the activity.
The unique identifier for the assignee.
The name of the assignee.
The avatar or profile picture of the assignee.
The email address of the assignee.
Details of the status attribute associated with the activity.
```json 200-Success theme={null}
{
"status": true,
"message": "Record created successfully!",
"_id": "655c081eb3b96854c04e621a",
"data": {
"_id": "655c081eb3b96854c04e621a",
"name": "Test Activity",
"scheduleAt": "2023-12-01T00:00:00.000Z",
"description": "Testing",
"associated": null,
"assignee": null,
"status": {
"_id": "65338d9cf781c59be3859c62",
"color": "#9898fa",
"name": "Open",
"celebrationEnabled": false,
"isArchived": false,
"order": 1,
"statusType": "normal",
"timeInStatus": 0
},
"sourceDetails": "",
"source": {
"_id": "65338d9cf781c59be3859c6b",
"name": "API",
"color": "#dbeddb",
"isArchived": false,
"order": 2
},
"createdBy": {
"_id": "65338d9a4d3b7a624a8d63ab",
"name": "Test user",
"avatar": "",
"email": "user@zixflow.com"
},
"completedTime": false,
"activityLostReason": null,
"createdAt": "2023-11-21T01:30:06.242Z"
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Delete Activity By ID
Source: https://docs.zixflow.com/api-reference/activity-list/delete
DELETE https://api.zixflow.com/api/v1/collection-records/activity-list/{activityId}
This endpoint allows the deletion of selected activity details.
#### Description
This API endpoint facilitates the removal of a specific activity by providing its unique identifier.
#### Path
A unique identifier for the activity to be deleted.
#### Response
Indicates the success or failure of the activity deletion. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful deletion of the activity with the message "Record
deleted successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Record Deleted Successfully!"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update An Activity
Source: https://docs.zixflow.com/api-reference/activity-list/edit
PATCH https://api.zixflow.com/api/v1/collection-records/activity-list/{activityId}
This endpoint updates an existing activity or task within a collection.
#### Description
This API endpoint allows for the update of an existing activity or task within a specified collection. The activity to be updated is identified by the unique identifier (activityId) provided in the path parameter.
#### Path
The unique identifier for the activity to be updated.
#### Body
Specifies the type of icon associated with the activity. Possible values include 'emoji', 'interaction', 'messaging\_app', etc.
Defines the specific value of the icon based on the iconType.
* For 'emoji', provide any emoji.
* For 'interaction', valid values include 'call', 'meeting', 'message', 'coffee', 'lunch', 'event', 'drink', etc.
* For 'messaging\_app', valid values include 'whatsapp', 'twitter', 'linkedin', 'hangout', 'skype', 'slack', 'imessage', 'facebook\_messenger', 'signal', 'discord', 'wechat', 'telegram', 'viber', etc.
The updated name or title of the activity.
Specifies the updated scheduled time for the activity in the format "YYYY-MM-DDTHH:mm:ss.SSSZ".
The updated description providing additional details about the activity.
The updated ID of the collection record associated with the activity.
The updated ID of the status attribute indicating the current status of the activity.
#### Response
Indicates the success or failure of the activity update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the activity with the message "Record updated successfully!"
```json 200-Success theme={null}
{
"status": true,
"message": "Record updated successfully!"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List of Activities
Source: https://docs.zixflow.com/api-reference/activity-list/get
POST https://api.zixflow.com/api/v1/collection-records/activity-list/query
This endpoint returns all activity data
#### Body
An array that will eventually allow users to define specific criteria for
filtering data. Currently, it is an empty array, indicating that no filtering
is applied at this time.
An array that will eventually enable users to specify sorting criteria for the
data. Like the filter array, it is currently empty, implying that no sorting
is applied in the current context.
he number of records to be returned, set to 10 in this instance. This
parameter restricts the response to a specific quantity of records.
The starting point from which the records are to be fetched within the entire
dataset. In this case, it is set to 0, indicating that retrieval should
commence from the beginning of the dataset.
#### Response
Indicates the success or failure of the record retrieval. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of records with the message "Records
fetched successfully."
An array containing details of the fetched records. For each record
in the data array:
A unique identifier for the record, allowing for precise referencing.
The name associated with the record.
The scheduled time for the record in ISO 8601 format.
Additional information or details about the record.
Details about any associated entity, if applicable.
Details about the assignee of the record:
Unique identifier for the assignee.
The name of the assignee.
URL or information about the assignee's avatar.
Email address of the assignee.
Details about the status of the record:
Unique identifier for the status.
Hex color code representing the status color.
The name of the status.
Additional details about the source of the record.
Details about the source of the record:
Unique identifier for the source.
The name of the source.
Hex color code representing the source color.
Details about the creator of the record:
Unique identifier for the creator.
The name of the creator.
URL or information about the creator's avatar.
Email address of the creator.
The timestamp when the record was completed in ISO 8601 format.
Reason for any lost activity, if applicable.
The timestamp when the record was created in ISO 8601 format.
```json 200-Success theme={null}
{
"status": true,
"message": "Records fetched successfully",
"data": [
{
"_id": "654b3231448888ff7d161a59",
"name": "Call HOH",
"scheduleAt": "2023-11-08T07:01:00.000Z",
"description": "",
"associated": null,
"assignee": {
"_id": "65338d9a4d3b7a624a8d63ab",
"name": "Test User",
"avatar": "",
"email": "user@zixflow.com"
},
"status": {
"_id": "65338d9cf781c59be3859c63",
"color": "#99ff93",
"name": "Completed",
"celebrationEnabled": false,
"isArchived": false,
"order": 2,
"statusType": "normal",
"timeInStatus": 0
},
"sourceDetails": "",
"source": {
"_id": "65338d9cf781c59be3859c6a",
"name": "Manually created",
"color": "#efe0da",
"isArchived": false,
"order": 1
},
"createdBy": {
"_id": "65338d9a4d3b7a624a8d63ab",
"name": "Test User",
"avatar": "",
"email": "user@zixflow.com"
},
"completedTime": "2023-11-08T07:01:30.450Z",
"activityLostReason": null,
"createdAt": "2023-11-08T07:01:05.770Z"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get Activity By Id
Source: https://docs.zixflow.com/api-reference/activity-list/get-by-id
GET https://api.zixflow.com/api/v1/collection-records/activity-list/{activityId}
This endpoint returns all activity data
#### Path
A unique identifier for the activity data.
#### Response
Indicates the success or failure of the record retrieval. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of records with the message "Records
fetched successfully."
An array containing details of the fetched records. For each record
in the data array:
A unique identifier for the record, allowing for precise referencing.
The name associated with the record.
The scheduled time for the record in ISO 8601 format.
Additional information or details about the record.
Details about any associated entity, if applicable.
Details about the assignee of the record:
Unique identifier for the assignee.
The name of the assignee.
URL or information about the assignee's avatar.
Email address of the assignee.
Details about the status of the record:
Unique identifier for the status.
Hex color code representing the status color.
The name of the status.
Additional details about the source of the record.
Details about the source of the record:
Unique identifier for the source.
The name of the source.
Hex color code representing the source color.
Details about the creator of the record:
Unique identifier for the creator.
The name of the creator.
URL or information about the creator's avatar.
Email address of the creator.
The timestamp when the record was completed in ISO 8601 format.
Reason for any lost activity, if applicable.
The timestamp when the record was created in ISO 8601 format.
```json 200-Success theme={null}
{
"status": true,
"message": "Records fetched successfully",
"data": {
"_id": "654b3231448888ff7d161a59",
"name": "Call HOH",
"scheduleAt": "2023-11-08T07:01:00.000Z",
"description": "",
"associated": null,
"assignee": {
"_id": "65338d9a4d3b7a624a8d63ab",
"name": "Test User",
"avatar": "",
"email": "user@zixflow.com"
},
"status": {
"_id": "65338d9cf781c59be3859c63",
"color": "#99ff93",
"name": "Completed",
"celebrationEnabled": false,
"isArchived": false,
"order": 2,
"statusType": "normal",
"timeInStatus": 0
},
"sourceDetails": "",
"source": {
"_id": "65338d9cf781c59be3859c6a",
"name": "Manually created",
"color": "#efe0da",
"isArchived": false,
"order": 1
},
"createdBy": {
"_id": "65338d9a4d3b7a624a8d63ab",
"name": "Test User",
"avatar": "",
"email": "user@zixflow.com"
},
"completedTime": "2023-11-08T07:01:30.450Z",
"activityLostReason": null,
"createdAt": "2023-11-08T07:01:05.770Z"
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Create Custom Attribute
Source: https://docs.zixflow.com/api-reference/attributes/create
POST https://api.zixflow.com/api/v1/attributes/{target}/{targetId}
This endpoint creates a new attribute for a specified target (collection or list).
#### Description
This API endpoint allows the creation of a new attribute within a specified target, either a collection or a list. Attributes define the nature and characteristics of the data stored within the target.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
#### Body
The API key name used to store data in the collection or list for this field.
Defines the input type or nature of the field. Possible values include text, number, checkbox, currency, date, timestamp, rating, status, select, multiselect, record-reference, actor-reference, domain, file, ai-wizard, email-address, phone-number.
The human-readable name of the field.
Configuration settings for the attribute, including currency display type, currency code, record reference, AI wizard, and date display type.
Accepted values: "code," "name," "narrowSymbol," "symbol." Specifies how currency should be displayed.
The currency code (e.g., "EUR," "GBP," "USD," "CAD," "AUD").
An array of collection IDs connected to this attribute.
The AI wizard field prompt.
Defines how to display the date field.
The default value for the attribute.
A description providing additional information about the attribute.
Indicates whether the attribute is editable (true/false).
Indicates whether the attribute supports multiple selections (true/false).
Indicates whether the attribute is required (true/false).
Indicates whether the attribute values must be unique (true/false).
The validation type for the attribute. Possible values include "none," "email," "url," "phone," "customRegex."
#### Response
Indicates the success or failure of the attribute creation. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful creation of the attribute.
An object containing details of the newly created attribute:
A unique identifier for the attribute, allowing for precise referencing.
The API key name used to store data in the collection or list for this field.
Defines the input type or nature of the field.
The human-readable name of the field.
Configuration settings for the attribute.
The default value for the attribute.
A description providing additional information about the attribute.
Indicates whether the attribute is editable.
Indicates whether the attribute supports multiple selections.
Indicates whether the attribute is required.
Indicates whether the attribute values must be unique.
The validation type for the attribute.
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": {
"_id": "64ad815b273e66dae1afe10f",
"apiKeyName": "name",
"inputType": "text",
"name": "Name",
"config": {
"currencyDisplayType": null,
"currencyCode": null,
"recordReference": [],
"aiWizard": null,
"dateDisplayType": null
},
"defaultValue": null,
"description": "",
"isEditable": true,
"isMultiSelect": false,
"isRequired": false,
"isUnique": false,
"validation": "none"
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Delete Custom Attribute
Source: https://docs.zixflow.com/api-reference/attributes/delete
DELETE https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}
This endpoint allows the deletion of a custom attribute.
#### Description
This API endpoint facilitates the removal of a specific custom attribute by providing the necessary identifiers based on the target, whether it's a collection or a list.
#### Path
Specifies the target entity as either "collection" or "list."
The unique identifier for the target entity (collection or list), allowing for
precise referencing. (e.g Collection Id / List Id)
The unique identifier for the attribute to be deleted.
#### Response
Indicates the success or failure of the attribute deletion. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful deletion of the attribute with the message
"Attribute deleted successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute deleted successfully."
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get Attribute By ID
Source: https://docs.zixflow.com/api-reference/attributes/get
GET https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}
This endpoint retrieves the details of a specific attribute by ID.
#### Description
This API endpoint allows the retrieval of details for a specific attribute identified by its unique ID. The attribute provides information about the nature and characteristics of data stored within a collection or list.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID,
depending on the target type.
The unique identifier for the attribute, allowing for precise referencing.
#### Response
Indicates the success or failure of the attribute retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the attribute details.
An object containing details of the retrieved attribute:
A unique identifier for the attribute, allowing for precise referencing.
The API key name used to store data in the collection or list for this
field.
Defines the input type or nature of the field. Possible values include
text, number, checkbox, currency, date, timestamp, rating, status, select,
multiselect, record-reference, actor-reference, domain, file, ai-wizard,
email-address, phone-number.
The human-readable name of the field.
Configuration settings for the attribute, including currency display type,
currency code, record reference, AI wizard, and date display type.
Accepted values: "code," "name," "narrowSymbol," "symbol." Specifies
how currency should be displayed.
The currency code (e.g., "EUR," "GBP," "USD," "CAD," "AUD").
An array of collection ids connected to this attribute.
The AI wizard field prompt.
Defines how to display the date field.
The default value for the attribute.
A description providing additional information about the attribute.
Indicates whether the attribute is archived (true/false).
Indicates whether the default value is enabled for the attribute
(true/false).
Indicates whether the attribute is editable (true/false).
Indicates whether the attribute supports multiple selections (true/false).
Indicates whether the attribute is required (true/false).
Indicates whether the attribute is a system attribute (true/false).
Indicates whether the attribute values must be unique (true/false).
The validation type for the attribute. Possible values include "none,"
"email," "url," "phone," "customRegex."
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": {
"_id": "64ad815b273e66dae1afe10f",
"apiKeyName": "name",
"inputType": "text",
"name": "Name",
"config": {
"currencyDisplayType": null,
"currencyCode": null,
"recordReference": [],
"aiWizard": null,
"dateDisplayType": null
},
"defaultValue": null,
"description": "",
"isArchived": false,
"isDefaultValueEnabled": false,
"isEditable": true,
"isMultiSelect": false,
"isRequired": false,
"isSystemAttribute": true,
"isUniuqe": false,
"validation": "none",
"isUnique": false
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Of Attributes
Source: https://docs.zixflow.com/api-reference/attributes/get-list-of-attributes
GET https://api.zixflow.com/api/v1/attributes/{target}/{targetId}
This endpoint retrieves the list of attributes associated with a collection or list.
#### Description
This API endpoint allows the retrieval of attributes associated with a specified target, which can be either a "collection" or "list." Attributes provide information about the nature and characteristics of data stored within a collection or list.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID,
depending on the target type.
#### Response
Indicates the success or failure of the attribute retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of attributes with the message "success."
An array containing details of the retrieved attributes. For each attribute in
the data array:
A unique identifier for the attribute, allowing for precise referencing.
The API key name used to store data in the collection or list for this
field.
Defines the input type or nature of the field. Possible values include
text, number, checkbox, currency, date, timestamp, rating, status, select,
multiselect, record-reference, actor-reference, domain, file, ai-wizard,
email-address, phone-number.
The human-readable name of the field.
Configuration settings for the attribute, including currency display type,
currency code, record reference, AI wizard, and date display type.
Accepted values: "code," "name," "narrowSymbol," "symbol." Specifies
how currency should be displayed.
The currency code (e.g., "EUR," "GBP," "USD," "CAD," "AUD").
An array of collection ids connected to this attribute.
The AI wizard field prompt.
Defines how to display the date field.
The default value for the attribute.
A description providing additional information about the attribute.
Indicates whether the attribute is archived (true/false).
Indicates whether the default value is enabled for the attribute
(true/false).
Indicates whether the attribute is editable (true/false).
Indicates whether the attribute supports multiple selections (true/false).
Indicates whether the attribute is required (true/false).
Indicates whether the attribute is a system attribute (true/false).
Indicates whether the attribute values must be unique (true/false).
The validation type for the attribute. Possible values include "none,"
"email," "url," "phone," "customRegex."
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": [
{
"_id": "64ad815b273e66dae1afe10f",
"apiKeyName": "name",
"inputType": "text",
"name": "Name",
"config": {
"currencyDisplayType": null,
"currencyCode": null,
"recordReference": [],
"aiWizard": null,
"dateDisplayType": null
},
"defaultValue": null,
"description": "",
"isArchived": false,
"isDefaultValueEnabled": false,
"isEditable": true,
"isMultiSelect": false,
"isRequired": false,
"isSystemAttribute": true,
"isUniuqe": false,
"validation": "none",
"isUnique": false
},
// Additional attributes...
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Delete Attribute Option
Source: https://docs.zixflow.com/api-reference/attributes/options/delete
DELETE https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/options/{optionId}
This endpoint allows the deletion of a option within a select/multiselect attribute.
#### Description
This API endpoint facilitates the removal of a specific option within a select/multiselect attribute by providing the necessary identifiers based on the target, whether it's a collection or a list.
#### Path
Specifies the target entity as either "collection" or "list."
The unique identifier for the target entity (collection or list), allowing for
precise referencing.
The unique identifier for the select/multiselect attribute containing the option to
be deleted.
The unique identifier for the option to be deleted.
#### Response
Indicates the success or failure of the option deletion. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful deletion of the option with the message
"Attribute option deleted successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute option deleted successfully."
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Of Attribute Options
Source: https://docs.zixflow.com/api-reference/attributes/options/get
GET https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/options
This endpoint retrieves the list of options for select/multiselect attributes.
#### Description
This API endpoint allows the retrieval of options associated with select or multiselect attributes. The options provide additional information or categorization for the specified attribute.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
The ID of the select or multiselect attribute for which options are to be fetched.
#### Response
Indicates the success or failure of the option retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of attribute options with the message
"Attribute option list fetched successfully."
An array containing details of the fetched attribute options. For each option
in the data array:
A unique identifier for the attribute option, allowing for precise referencing.
The ID of the associated attribute to which the option belongs.
Hex color code representing the color associated with the option.
The name of the attribute option.
Indicates whether the option is archived (true/false).
The order or ranking of the option.
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute option list fetched successfully",
"data": [
{
"_id": "64ad815b273e66dae1afe124",
"attributeId": "64ad815b273e66dae1afe121",
"color": "#dbeddb",
"name": "Deliverable",
"isArchived": false,
"order": 2
},
{
"_id": "64ad815b273e66dae1afe125",
"attributeId": "64ad815b273e66dae1afe121",
"color": "#ffe2dd",
"name": "Invalid",
"isArchived": false,
"order": 3
},
{
"_id": "64ad815b273e66dae1afe126",
"attributeId": "64ad815b273e66dae1afe121",
"color": "#fdecc8",
"name": "Risky",
"isArchived": false,
"order": 4
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update Attribute Option
Source: https://docs.zixflow.com/api-reference/attributes/options/update
PATCH https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/options/{optionId}
This endpoint updates a select/multiselect attribute option.
#### Description
This API endpoint allows the update of a select/multiselect attribute option. Select/multiselect attribute options are used to define various choices available for a select/multiselect attribute associated with a collection or list.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
The ID of the select/multiselect attribute to which the option belongs.
The ID of the select/multiselect attribute option to be updated.
#### Request Body
The name of the select/multiselect option.
Hex color code representing the color associated with the option.
Indicates whether the option is archived (true/false).
The order or ranking of the option.
#### Response
Indicates the success or failure of the select/multiselect option update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the attribute select/multiselect option with the message
"Attribute option updated successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute option updated successfully"
}
```
```
{
"status": false,
"message": "No token provided"
}
```
# Delete Attribute Status Option
Source: https://docs.zixflow.com/api-reference/attributes/statuses/delete
DELETE https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/status/{statusId}
This endpoint allows the deletion of a status option within a status attribute.
#### Description
This API endpoint facilitates the removal of a specific status option within a status attribute by providing the necessary identifiers based on the target, whether it's a collection or a list.
#### Path
Specifies the target entity as either "collection" or "list."
The unique identifier for the target entity (collection or list), allowing for
precise referencing.
The unique identifier for the status attribute containing the status option to
be deleted.
The unique identifier for the status option to be deleted.
#### Response
Indicates the success or failure of the status option deletion. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful deletion of the status option with the message
"Attribute status deleted successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute status deleted successfully."
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Of Attribute Status Options
Source: https://docs.zixflow.com/api-reference/attributes/statuses/get
GET https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/status
This endpoint retrieves the list of options for status attributes.
#### Description
This API endpoint allows the retrieval of options associated with status attributes. Status attributes are used to define different stages or states that an item can go through in a collection or list.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
The ID of the status attribute for which options are to be fetched.
#### Response
Indicates the success or failure of the status option retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of attribute status options with the message
"Attribute status list fetched successfully."
An array containing details of the fetched attribute status options. For each option
in the data array:
A unique identifier for the attribute status option, allowing for precise referencing.
The ID of the associated status attribute to which the option belongs.
The name of the attribute status option.
Hex color code representing the color associated with the status option.
Indicates whether the status option is archived (true/false).
The time, in seconds, that items spend in this status option.
Indicates whether celebration is enabled for this status option (true/false).
Specifies the type of status. It can be "normal" or other applicable types.
The order or ranking of the status option.
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute status list fetched successfully",
"data": [
{
"_id": "64ae57b3a3325d9749556eee",
"attributeId": "64ae57b3a3325d9749556eeb",
"name": "Stage 1",
"color": "#e57b00",
"isArchived": false,
"timeInStatus": 0,
"celebrationEnabled": false,
"statusType": "normal",
"__v": 0,
"order": 1
},
{
"_id": "6520b88e4f7b6dea01cf9b7b",
"attributeId": "64ae57b3a3325d9749556eeb",
"name": "Stage 2",
"color": "#02b55c",
"order": 2,
"isArchived": false,
"timeInStatus": null,
"celebrationEnabled": false,
"statusType": "normal",
"__v": 0
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update Attribute Status Option
Source: https://docs.zixflow.com/api-reference/attributes/statuses/update
PATCH https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}/status/{optionId}
This endpoint updates a status attribute option.
#### Description
This API endpoint allows the update of a status attribute option. Status attribute options are used to define different stages or states that an item can go through in a collection or list.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
The ID of the status attribute to which the option belongs.
The ID of the status attribute option to be updated.
#### Request Body
The name of the status option.
Hex color code representing the color associated with the option.
Indicates whether the option is archived (true/false).
Indicates whether celebration is enabled for the option (true/false).
#### Response
Indicates the success or failure of the status option update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the attribute status option with the message
"Attribute status updated successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "Attribute status updated successfully"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update Custom Attribute
Source: https://docs.zixflow.com/api-reference/attributes/update
PATCH https://api.zixflow.com/api/v1/attributes/{target}/{targetId}/{attributeId}
This endpoint updates an existing attribute for a specified target (collection or list).
#### Description
This API endpoint allows the update of an existing attribute within a specified target, either a collection or a list. Attributes define the nature and characteristics of the data stored within the target.
#### Path
Specifies the target type, which can be either "collection" or "list."
A unique identifier for the target, either a collection ID or a list ID, depending on the target type.
A unique identifier for the attribute to be updated.
#### Body
The updated API key name used to store data in the collection or list for this field.
The updated input type or nature of the field. Possible values include text, number, checkbox, currency, date, timestamp, rating, status, select, multiselect, record-reference, actor-reference, domain, file, ai-wizard, email-address, phone-number.
The updated human-readable name of the field.
Updated configuration settings for the attribute, including currency display type, currency code, record reference, AI wizard, and date display type.
Accepted values: "code," "name," "narrowSymbol," "symbol." Specifies how currency should be displayed.
The updated currency code (e.g., "EUR," "GBP," "USD," "CAD," "AUD").
An updated array of collection IDs connected to this attribute.
The updated AI wizard field prompt.
Defines how to display the updated date field.
The updated default value for the attribute.
An updated description providing additional information about the attribute.
Indicates whether the attribute is editable (true/false).
Indicates whether the attribute supports multiple selections (true/false).
Indicates whether the attribute is required (true/false).
Indicates whether the attribute values must be unique (true/false).
The updated validation type for the attribute. Possible values include "none," "email," "url," "phone," "customRegex."
#### Response
Indicates the success or failure of the attribute update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the attribute.
An object containing details of the updated attribute:
A unique identifier for the updated attribute, allowing for precise referencing.
The API key name used to store data in the collection or list for this field.
Defines the updated input type or nature of the field.
The updated human-readable name of the field.
Updated configuration settings for the attribute.
The updated default value for the attribute.
An updated description providing additional information about the attribute.
Indicates whether the updated attribute is editable.
Indicates whether the updated attribute supports multiple selections.
Indicates whether the updated attribute is required.
Indicates whether the updated attribute values must be unique.
The updated validation type for the attribute.
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": {
"_id": "64ad815b273e66dae1afe10f",
"apiKeyName": "name",
"inputType": "text",
"name": "Name",
"config": {
"currencyDisplayType": null,
"currencyCode": null,
"recordReference": [],
"aiWizard": null,
"dateDisplayType": null
},
"defaultValue": null,
"description": "",
"isEditable": true,
"isMultiSelect": false,
"isRequired": false,
"isUnique": false,
"validation": "none"
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# API Key Authentication
Source: https://docs.zixflow.com/api-reference/authentication
API Key Authentication is a straightforward method for securing access to the Zixflow API's. You can generate an API key through Zixflow's dashboard, allowing you to define permissions, set expiration dates, and specify IP restrictions if necessary.
#### Obtaining an API Key
To obtain an API key for accessing the Zixflow API's, follow these steps:
1. **Access the Dashboard:**
* Log in to your Zixflow dashboard.
2. **Generate an API Key:**
* Navigate to **Settings** > **Workspace Settings** > **Developer** > **API Key**.
3. **Configure API Key Settings:**
* When generating an API key, you can configure the following settings:
* **Permissions:**
* Specify the level of access you want to grant to the API key. You can define which API endpoints and actions the key is allowed to perform.
* **Expiry (Optional):**
* Optionally, set an expiration date for the API key. If set, the key will no longer be valid after the specified date.
* **IP Restriction (Optional):**
* Optionally, restrict API key usage to specific IP addresses. This enhances security by only allowing requests from designated IPs.
4. **Generate the API Key:**
* After configuring your desired settings, click the "Generate" or "Create" button to generate the API key.
5. **Receive the API Key:**
* Once generated, you will receive your API key, which you will use to authenticate your API requests.
#### Using the API Key as a Bearer Token
To authenticate your API requests using the API key as a Bearer token, include it in the `Authorization` header as follows:
```js theme={null}
Authorization: Bearer your_api_key
```
Replace `your_api_key` with the actual API key you generated.
#### Managing API Key Access
You can manage and monitor API key access through your Zixflow dashboard. This includes revoking access and configuring permissions, but keep in mind that the API key will be treated as a Bearer token for authorization.
# Introduction
Source: https://docs.zixflow.com/api-reference/campaign/email/introduction
Campaign - Send Email
#### The Leading Email API
Every business requires an email service to handle essential transactional emails and marketing messages, but not all need the extensive marketing features that Zixflow offers. For businesses seeking a more technically-focused email solution at scale, there's the Zixflow API.
#### To Send Messages, Generate the API Key
1. Go to Developers under Admin settings at Zixflow Platform - [https://app.zixflow.com/settings/workspace/developer](https://app.zixflow.com/settings/workspace/developer)
2. Now assign the respective permissions to the channel which you want to give specific or all channels (all channels recommended so you don't have to change the API in the future if you want to use other channels)
#### Setting Up on Zixflow Platform
To send emails via API, verify your domain and add your footer address details at [https://app.zixflow.com/campaign/settings/email](https://app.zixflow.com/campaign/settings/email). Once setup is complete, you can begin testing the email API.
# Send Email
Source: https://docs.zixflow.com/api-reference/campaign/email/send-email
POST https://api.zixflow.com/api/v1/campaign/email/send
This endpoint allows you to send an email.
#### Body
This is to contain email ids where you need to send emails. you can add
multiple email ids where you need to send emails. Max 10 emails at one api.
This contains email subject
This is a from email id. you can assigned only verified email id. you can
verify from email id from zixflow dashboard by below-specified section.
dashboard - campaign -> settings -> email -> domain
Display name for from email address
This field used to send html content in email either bodyHtml or bodyText is
required
This field used to send normal text content in email either bodyHtml or
bodyText is required
Should the click be tracked? If no value has been provided, Account's default
setting will be used.
Should the opens be tracked? If no value has been provided, Account's default
setting will be used
Email address to reply to
Name to use when replying to the email.
An array of ID's of attachments. Note: you can get attachment id from
uploading attachment by upload attachment API
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```bash cUrl theme={null}
curl --location --request POST 'https://api.zixflow.com/api/v1/campaign/email/send' \
--header 'Authorization: Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c' \
--header 'Content-Type: application/json' \
--data-raw '{
"to": [
"sample@gmail.com"
],
"subject": "API test 1",
"from": "test@domain.com",
"fromName": "sahil",
"bodyHtml": "Test 1
",
"trackClicks": true,
"trackOpens": true,
"replyToEmail": "test2@domain2.com",
"attachments": ["63a98c9c2b6df936ac930c17"],
"replyToName": "sam",
"bodyText": "Just Text Email",
"reportUrl": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5"
}'
```
```js Nodejs theme={null}
var axios = require("axios");
var data = JSON.stringify({
to: ["sample@gmail.com"],
subject: "API test 1",
from: "test@domain.com",
fromName: "sahil",
bodyHtml: "Test 1
",
trackClicks: true,
trackOpens: true,
replyToEmail: "test2@domain2.com",
attachments: ["63a98c9c2b6df936ac930c17"],
replyToName: "sam",
bodyText: "Just Text Email",
reportUrl: "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5",
});
var config = {
method: "post",
url: "https://api.zixflow.com/api/v1/campaign/email/send",
headers: {
Authorization:
"Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c",
"Content-Type": "application/json",
},
data: data,
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```php PHP theme={null}
setUrl('https://api.zixflow.com/api/v1/campaign/email/send');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
'follow_redirects' => TRUE
));
$request->setHeader(array(
'Authorization' => 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
'Content-Type' => 'application/json'
));
$request->setBody('{\n "to": [\n "sample@gmail.com"\n ],\n "subject": "API test 1",\n "from": "test@domain.com",\n "fromName": "sahil",\n "bodyHtml": "Test 1
",\n "trackClicks": true,\n "trackOpens": true,\n "replyToEmail": "test2@domain2.com",\n "attachments": ["63a98c9c2b6df936ac930c17"],\n "replyToName": "sam",\n "bodyText": "Just Text Email",\n "reportUrl": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5"\n}');
try {
$response = $request->send();
if ($response->getStatus() == 200) {
echo $response->getBody();
}
else {
echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
$response->getReasonPhrase();
}
}
catch(HTTP_Request2_Exception $e) {
echo 'Error: ' . $e->getMessage();
}
```
```py Python theme={null}
import requests
import json
url = "https://api.zixflow.com/api/v1/campaign/email/send"
payload = json.dumps({
"to": [
"sample@gmail.com"
],
"subject": "API test 1",
"from": "test@domain.com",
"fromName": "sahil",
"bodyHtml": "Test 1
",
"trackClicks": True,
"trackOpens": True,
"replyToEmail": "test2@domain2.com",
"attachments": [
"63a98c9c2b6df936ac930c17"
],
"replyToName": "sam",
"bodyText": "Just Text Email",
"reportUrl": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5"
})
headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"to\": [\n \"sample@gmail.com\"\n ],\n \"subject\": \"API test 1\",\n \"from\": \"test@domain.com\",\n \"fromName\": \"sahil\",\n \"bodyHtml\": \"Test 1
\",\n \"trackClicks\": true,\n \"trackOpens\": true,\n \"replyToEmail\": \"test2@domain2.com\",\n \"attachments\": [\"63a98c9c2b6df936ac930c17\"],\n \"replyToName\": \"sam\",\n \"bodyText\": \"Just Text Email\",\n \"reportUrl\": \"https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5\"\n}");
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/v1/campaign/email/send")
.method("POST", body)
.addHeader("Authorization", "Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c")
.addHeader("Content-Type", "application/json")
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
'Content-Type': 'application/json'
};
var request = http.Request('POST', Uri.parse('https://api.zixflow.com/api/v1/campaign/email/send'));
request.body = json.encode({
"to": [
"sample@gmail.com"
],
"subject": "API test 1",
"from": "test@domain.com",
"fromName": "sahil",
"bodyHtml": "Test 1
",
"trackClicks": true,
"trackOpens": true,
"replyToEmail": "test2@domain2.com",
"attachments": [
"63a98c9c2b6df936ac930c17"
],
"replyToName": "sam",
"bodyText": "Just Text Email",
"reportUrl": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5"
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"message": "Email sent successfully!"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "to[1] must be a valid email"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Upload Attachment
Source: https://docs.zixflow.com/api-reference/campaign/email/upload-attachment
POST https://api.zixflow.com/api/v1/campaign/email/upload
This API used to upload attachments in zixflow platform. Purpose of API is whenever the user wants to send an attachment in the email. user need to upload attachment in zixflow platform first. zixflow will provide id represents to attachment. and the user can send this id in send email API's attachment field to send attachment along with the email
#### Body
The "File" field refers to an attachment file that must be included in an
email campaign. The file can be in any format, and its size should not exceed
25 megabytes (MB).
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
The name of the file.
The size of the file in bytes.
The date and time the file was added.
The expiration date of the file (if specified).
The unique identifier of the file.
The content type of the file.
The path to the medium-sized screenshot.
The path to the thumbnail.
```bash cUrl theme={null}
curl --location --request POST 'https://api.zixflow.com/api/v1/campaign/email/upload' \
--header 'Authorization: Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c' \
--form 'file=@""'
```
```js Nodejs theme={null}
var axios = require('axios');
var FormData = require('form-data');
var fs = require('fs');
var data = new FormData();
data.append('file', fs.createReadStream('/home/sahil/Downloads/welcome-screen.png'));
var config = {
method: 'post',
url: 'https://api.zixflow.com/api/v1/campaign/email/upload',
headers: {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
...data.getHeaders()
},
data : data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```py Python theme={null}
import requests
url = "https://api.zixflow.com/api/v1/campaign/email/upload"
payload={}
files=[
('file',('welcome-screen.png',open('/home/sahil/Downloads/welcome-screen.png','rb'),'image/png'))
]
headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c'
}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("file","welcome-screen.png",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("/home/sahil/Downloads/welcome-screen.png")))
.build();
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/v1/campaign/email/upload")
.method("POST", body)
.addHeader("Authorization", "Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c")
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c'
};
var request = http.MultipartRequest('POST', Uri.parse('https://api.zixflow.com/api/v1/campaign/email/upload'));
request.files.add(await http.MultipartFile.fromPath('file', '/home/sahil/Downloads/welcome-screen.png'));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"data": {
"filename": "file-welcome-screen-1672054415115.png",
"size": 7343,
"dateadded": "2022-12-26T11:33:36.2633971Z",
"expirationdate": null,
"fileid": "63a98690b0ec6c81460f4f1d",
"contenttype": "image/png",
"mediumscreenshotpath": "93b7fbda-4306-4ab7-bb80-e4375750ea76/mediumscreenshots/file-welcome-screen-1672054415115.png",
"thumbnailpath": "93b7fbda-4306-4ab7-bb80-e4375750ea76/thumbnails/file-welcome-screen-1672054415115.png"
},
"message": "Successfully uploaded file"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "file size exceeded"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# OTPflow API
Source: https://docs.zixflow.com/api-reference/campaign/otp-flow/otp-flow-api
POST https://api.zixflow.com/api/v1/campaign/otp-flow
AI-powered OTPflow API designed to maximize message delivery success by automatically routing messages through multiple channels on autopilot.
We’ve worked with hundreds of clients, and one common question is: **How can we achieve 100% message delivery?**
Over the years, we’ve optimized every channel using AI-powered real-time dynamic routing and automated retries at Zixflow. While we outperform competitors in delivery rates, achieving 100% delivery isn’t always possible due to limitations in operator infrastructure, which is beyond our control. However, ensuring critical use cases like OTPs or mandatory messages reach users on time is essential, as missed messages can lead to lost business opportunities.
To solve this, we’re introducing the **world’s first OTPflow API.** With this, we can achieve **99.99% delivery for valid numbers.**
Here’s how it works:
* The system uses AI to optimize delivery across all channels with priority routing, not just at Zixflow but also at the operator level.
* If a message fails or isn’t delivered in time, it automatically switches to another channel.
* You can pass potential messages to all activated channels in your account, and the system will handle delivery optimization on autopilot.
This approach ensures faster and more reliable message delivery, reducing drop-offs and maximizing business impact.
#### How does it work?
1. **Sequential Delivery**: Messages are sent in the defined order (e.g., SMS → WhatsApp → RCS). You can customize it on request.
2. **Timeout Handling**:
* If a message is delivered within the timeout window, the next steps are skipped.
* If a message fails, it will switch to the next channel (e.g., if the message fails within 2 seconds, it will move to the next channel immediately without waiting for the timeout).
* If we don’t receive a delivery report (success or failure) within the timeout window, it will switch to the next channel.
3. **Multi-Channel Reliability**: Combining SMS, WhatsApp, and RCS ensures high delivery success.
#### Example Flow
* **Scenario**: Send via SMS (timeout 10s), then WhatsApp (timeout 10s), then RCS (timeout 10s).
* **Process**:
1. SMS message sent first.
2. If delivered, stops. If failed or no response, moves to WhatsApp.
3. Same logic applies to RCS.
#### Benefits:
* **Higher Delivery Rates**: Ensures at least one channel succeeds.
* **Efficient Use of Resources**: No redundant attempts and reduces wait-time for users.
* **Customizable**: Control timeouts and delivery order.
* **Save Costs**: Instead of retrying on the same channel multiple times or sending messages to all channels simultaneously, this approach triggers the second channel only if the first one fails, reducing unnecessary usage and cost.
* **Real-time updates**: When a switch occurs to the next channel, we can trigger a webhook to your system. This allows you to optimize the user experience. For example, if SMS couldn’t be delivered, we’ll notify you that the message has been sent via the next channel ex. WhatsApp.
#### Limitation:
1. **Timeout Limitation**: The maximum timeout window for all channels combined is 5 minutes (300 seconds). After this, the entire request is skipped.
2. **Total Messages**: In one request, you can send a minimum of 1 message and a maximum of 5 messages. You can use the same channel multiple times or use different channels as per your need (e.g., SMS → SMS → WhatsApp → RCS → Email).
3. **Fallback Channels**: In some cases, delivery reports may arrive late, even if the message was delivered on time. Despite optimizations, this may result in messages being sent on multiple channels if the report isn't received within the timeout window.
**How to Test**: To check if all information or messages are being sent correctly, set the timeout to 0. This will trigger all messages at once, allowing you to verify if they’re received on the test number from all channels. Once confirmed, increase the timeout for the production environment. We recommend adding a 10-second delay between each message for optimal performance.
**No Additional Cost**: There are no extra charges for this service. You’ll only be charged for the messages sent, based on the pricing of each channel.
Number Verification: For OTP or number verification, use only SMS, WhatsApp,
and RCS. Avoid using email, as users may receive the OTP via email, but it
won’t verify the phone number.
### Request Body Parameters
#### Root Fields
| Field | Type | Description | Required |
| ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| messages | array | A list of `message` objects to be sent. It explained below. | Yes |
| webhookurl | string | **Important:** Pass the webhook URL if you want to display a message to the user when a channel switch happens. eg. If a message switches from SMS to RCS, a webhook will be triggered. You can ignore if you don’t want it.
This is **not** a message delivery webhook. That you need to pass it only in the respective message body. The report URL is separate and used for reporting purposes like delivered. | No |
| callbackData | string | Metadata or identifiers passed for callbacks. | No |
#### Message Object
Each message object in the `messages` array contains the following fields:
| Field | Type | Description | Required |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| channel | string | The channel through which the message is sent (e.g., `sms`/`whatsapp`/`rcs`/`email`). | Yes |
| timeout | number | Timeout value for the message in seconds. it range from 0-300. | Yes |
| messageType | string | Type of the message. Empty for email and sms channel. For rcs values can be `text`, `message`, `video`, `audio`, `document`, `template`. For whatsapp value can be `template`, `custom`. | Yes |
| data | object | The message data containing detailed information with respective channel. | Yes |
#### Message Data Object
The `data` object contains the following api request body payload:
| | Channel | Message Type | Payload |
| ---------------------------- | ---------- | ------------ | ----------------------------------------------------------------------- |
| Send SMS | `sms` | | [Data Payload](/api-reference/campaign/sms/send-sms-post) |
| Send Whatsapp Template | `whatsapp` | `template` | [Data Payload](/api-reference/campaign/whatsapp/send-whatsapp-message) |
| Send Direct Whatsapp Message | `whatsapp` | `custom` | [Data Payload](/api-reference/campaign/whatsapp/messages/text-messages) |
| Send RCS Text Message | `rcs` | `text` | [Data Payload](/api-reference/campaign/rcs/messages/text-message) |
| Send RCS Image Message | `rcs` | `image` | [Data Payload](/api-reference/campaign/rcs/messages/image-message) |
| Send RCS Video Message | `rcs` | `video` | [Data Payload](/api-reference/campaign/rcs/messages/video-message) |
| Send RCS Audio Message | `rcs` | `audio` | [Data Payload](/api-reference/campaign/rcs/messages/audio-message) |
| Send RCS Document Message | `rcs` | `document` | [Data Payload](/api-reference/campaign/rcs/messages/document-message) |
| Send RCS Template Message | `rcs` | `template` | [Data Payload](/api-reference/campaign/rcs/send-rcs-template) |
| Send Email | `email` | | [Data Payload](/api-reference/campaign/email/send-email) |
***
If you're using WhatsApp for authentication or OTP, we recommend using the [WhatsApp Authentication template](/api-reference/campaign/whatsapp/whatsapp-auth-template).
### Request Body Schema
A list of `message` objects to be sent. It explained below.
The channel through which the message is sent (e.g.,
`sms`/`whatsapp`/`rcs`/`email`).
Timeout value for the message in seconds. it range from 0-300.
Type of the message. Empty for email and sms channel. For rcs values can
be `text`, `message`, `video`, `audio`, `document`, `template`. For
whatsapp value can be `template`, `custom`.
The message data containing detailed information with respective channel.
The URL where message success or failed webhook will be sent with index.
Metadata or identifiers passed for callbacks.
## Example Request
```bash theme={null}
curl -X POST \
https://api.zixflow.com/api/v1/campaign/otp-flow \
-H 'Content-Type: application/json' \
-d '{
"webhookurl": "https://example.com/webhook",
"callbackData": "order123",
"messages": [
{
"channel": "sms",
"timeout": 0,
"messageType": "",
"data": {
"senderId": "SAMPLE",
"route": "promotional",
"number": "919876543210",
"message": "Boost efficiency with ZixFlow! 🚀 Streamline workflows & save time. Start now at www.zixflow.com or call 123-456-7890. Your success, simplified!",
"dltTemplateId": "1234",
"dltEntityId": "1234",
"isFlash": false,
"submissionStatus": true,
"reportURL": "https://example.com/webhook"
}
},
{
"channel": "whatsapp",
"timeout": 0,
"messageType": "template",
"data": {
"to": "919876543210",
"phoneId": "test-phone-id",
"templateName": "marketing_sample_2",
"language": "en",
"variables": {
"":"
},
"source": "OTPflow",
"linkWithRecord": false,
"reportURL": "https://example.com/webhook"
}
},
{
"channel": "rcs",
"timeout": 0,
"messageType": "text",
"data": {
"to": "919876543210",
"botId": "test-bot-id",
"text": "Boost efficiency with ZixFlow! 🚀 Streamline workflows & save time. Start now at www.zixflow.com or call 123-456-7890. Your success, simplified!",
"source": "OTPflow",
"linkWithRecord": false,
"reportURL": "https://example.com/webhook"
}
},
{
"channel": "email",
"timeout": 0,
"messageType": "",
"data": {
"to": [
"text@sample.com"
],
"subject": "Boost efficiency with ZixFlow!",
"from": "mailer@sample.us",
"fromName": "Zixflow Marketing",
"bodyHtml": "Boost efficiency with ZixFlow! 🚀 Streamline workflows & save time. Start now at www.zixflow.com or call 123-456-7890. Your success, simplified!
",
"trackClicks": true,
"trackOpens": true,
"replyToEmail": "support@sample.com",
"attachments": [],
"replyToName": "Support",
"bodyText": "Boost efficiency with ZixFlow! 🚀 Streamline workflows & save time. Start now at www.zixflow.com or call 123-456-7890. Your success, simplified!",
"callbackUrl": "https://example.com/webhook"
}
}
]
}'
```
***
## Response
### Success Response
**Status Code:** `200 OK`
```json theme={null}
{
"status": "success",
"message": "Message sent successfully.",
"eventId": "ca64ad45-937d-45b4-9c5d-9b1d93bf2302",
"requestIds": [
"ca64ad45-937d-45b4-9c5d-9b1d93bf2303",
"ca64ad45-937d-45b4-9c5d-9b1d93bf2304",
"ca64ad45-937d-45b4-9c5d-9b1d93bf2305",
"ca64ad45-937d-45b4-9c5d-9b1d93bf2306"
]
}
```
### Error Response
**Status Code:** `400 Bad Request`
```json theme={null}
{
"status": "error",
"message": "Invalid request payload."
}
```
***
## Notes
* Ensure the webhook URL is accessible and capable of handling incoming responses. Our system makes only one attempt, and if your webhook URL is down, the request response cannot be recovered.
* Timeout values should be set based on the use case to prevent excessive delays.
# Get List of Template Variables
Source: https://docs.zixflow.com/api-reference/campaign/rcs/get-template-variables
GET https://api.zixflow.com/api/v1/campaign/rcs/variable-keys/{botId}
Retrieve the variable details for a specific RCS template.
# Get List of RCS Template Variables
This API allows you to retrieve the list of variables for a specific RCS template associated with a bot.
#### Path Parameters
The unique identifier for the bot associated with the RCS template. Contact
the Zixflow support team to configure or retrieve your bot ID.
#### Query Parameters
The name of the RCS template for which variable details are being requested.
#### Response
Indicates whether the call was successful. `true` if successful, `false` if
not.
Provides success or error message details.
An array of key-value pairs representing the variables for the specified RCS template.
The variable key in the template (e.g., "Customer\_Name").
The data type of the variable (e.g., "string", "date").
#### Response Examples
```json 200-Success theme={null}
{
"status": true,
"message": "Success",
"data": [
{
"key": "Customer_Name",
"type": "string"
},
{
"key": "Appointment_Date",
"type": "date"
}
]
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid bot ID or template name"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Incoming Message Webhook
Source: https://docs.zixflow.com/api-reference/campaign/rcs/incoming-message-webhook
You can receive incoming messages on your webhook, enabling real-time integration with your systems.
> Note: this is not for message delivery status — the webhook is triggered only for incoming messages.
#### 🛠️ How to Create a Webhook
To set up a webhook in Zixflow:
1. Navigate to the **Zixflow** application.
2. Go to the **Settings** section.
3. Open the **Developer** section.
4. Click on **Webhook**.
5. Click on the **➕ Add** button.
##### Below is a list of available events, including their structure and example payloads.
#### 1. `incoming.rcs.message`
Triggered when a new RCS message is received.
```json theme={null}
{
"event": "incoming.rcs.message", // Event Type
"eventId": "3472dff5-7c06-40e6-be06-a28a335ac263", // UUID v4 - Event id to uniquilly identify event
"timestamp": 1744346001738, // Message At 13 digit timestamp
"botId": "xxxxxxxxxxxxxx79", // RCS Bot ID
"messageId": "MxUMzTyF6kRBe7q5cPpwDufg", // RCS messsage id
"sender": {
"number": "919999999999" // End user phone number with country code
},
"message": {
// When Text Messages Receives
"type": "text"
"text": "Hi"
// When user click on quick reply buttons
"type": "reply"
"reply": "More"
// When Image Message Receives
"type": "image",
"image":{
"mimeType": "image/xxxx", // Image Mime type
"fileSizeBytes": 86267, // Image Size in Bytes
"fileName": "", // Image File Name
"fileUri": "", // Image File URL
}
// When Video Message Receives
"type": "video",
"video":{
"mimeType": "video/xxxx", // Video Mime type
"fileSizeBytes": 86267, // Video Size in Bytes
"fileName": "", // Video File Name
"fileUri": "", // Video File URL
}
// When Audio Message Receives
"type": "audio",
"audio":{
"mimeType": "audio/xxxx", // Audio Mime type
"fileSizeBytes": 86267, // Audio Size in Bytes
"fileName": "", // Audio File Name
"fileUri": "", // Audio File URL
}
// When File Message Receives
"type": "file",
"file":{
"mimeType": "xxxx/xxxx", // File Mime type
"fileSizeBytes": 86267, // File Size in Bytes
"fileName": "", // File File Name
"fileUri": "", // File File URL
}
// When Location Message Receives
"type": "location",
"location": {
"latitude": 19.2232384
"longitude": 73.0798646
}
},
"metaData": {
"accountId": "xxxxxxxxxxxxxb8de24d95b59", // Zixflow Account Id
"orgMsgId": "xxxxxxxxxxxx67f3cbd6b9d1" // Zixflow Org Message Id
}
}
```
# Introduction to RCS Messaging
Source: https://docs.zixflow.com/api-reference/campaign/rcs/introduction
Rich Communication Services (RCS) is an advanced messaging protocol that allows businesses to send interactive, media-rich messages to users. Currently, Zixflow supports RCS messaging for clients in **India only**.
### Getting Started with RCS
To start using RCS, clients must contact the Zixflow support team. Our team will guide you through creating an RCS account. Once the account is set up, Zixflow will link it to your profile, activating it on your account.
# Send Audio Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/messages/audio-message
POST https://api.zixflow.com/api/v1/campaign/rcs/message/audio
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
Audio url which need to send
A Suggestions Object includes the following fields and formatting options:
Suggestion Type Options: "reply", "open-url", "dialer", "location",
"calendar-event"
Displayed as button text to the end user.
(Optional) Used to track user interactions. When clicked, a unique
postback event is triggered on the webhook.
Required if type is "open-url". Must be a valid URL. Example:
[https://google.com](https://google.com)
Required for "dialer" type. Must be a valid phone number with country code
and "+" prefix. Example: +919876543210
Required for "location" type. Label for the location. Example: "Office
Location"
Required for "location" type. Latitude value in numeric format. Example:
12.9363301
Required for "location" type. Longitude value in numeric format. Example:
77.6084231
Required for "calendar-event" type. Event title. Example: "Zixflow
Appointment"
Required for "calendar-event" type. Event description. Example:
"Appointment with Zixflow team to understand their application."
Required for "calendar-event" type. Start date and time in ISO format.
Example: "2024-11-30T14:00:00Z"
Required for "calendar-event" type. End date and time in ISO format.
Example: "2024-11-30T15:00:00Z"
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Document Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/messages/document-message
POST https://api.zixflow.com/api/v1/campaign/rcs/message/document
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
Document url which need to send
A Suggestions Object includes the following fields and formatting options:
Suggestion Type Options: "reply", "open-url", "dialer", "location",
"calendar-event"
Displayed as button text to the end user.
(Optional) Used to track user interactions. When clicked, a unique
postback event is triggered on the webhook.
Required if type is "open-url". Must be a valid URL. Example:
[https://google.com](https://google.com)
Required for "dialer" type. Must be a valid phone number with country code
and "+" prefix. Example: +919876543210
Required for "location" type. Label for the location. Example: "Office
Location"
Required for "location" type. Latitude value in numeric format. Example:
12.9363301
Required for "location" type. Longitude value in numeric format. Example:
77.6084231
Required for "calendar-event" type. Event title. Example: "Zixflow
Appointment"
Required for "calendar-event" type. Event description. Example:
"Appointment with Zixflow team to understand their application."
Required for "calendar-event" type. Start date and time in ISO format.
Example: "2024-11-30T14:00:00Z"
Required for "calendar-event" type. End date and time in ISO format.
Example: "2024-11-30T15:00:00Z"
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Image Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/messages/image-message
POST https://api.zixflow.com/api/v1/campaign/rcs/message/image
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
Image url which need to send
A Suggestions Object includes the following fields and formatting options:
Suggestion Type Options: "reply", "open-url", "dialer", "location",
"calendar-event"
Displayed as button text to the end user.
(Optional) Used to track user interactions. When clicked, a unique
postback event is triggered on the webhook.
Required if type is "open-url". Must be a valid URL. Example:
[https://google.com](https://google.com)
Required for "dialer" type. Must be a valid phone number with country code
and "+" prefix. Example: +919876543210
Required for "location" type. Label for the location. Example: "Office
Location"
Required for "location" type. Latitude value in numeric format. Example:
12.9363301
Required for "location" type. Longitude value in numeric format. Example:
77.6084231
Required for "calendar-event" type. Event title. Example: "Zixflow
Appointment"
Required for "calendar-event" type. Event description. Example:
"Appointment with Zixflow team to understand their application."
Required for "calendar-event" type. Start date and time in ISO format.
Example: "2024-11-30T14:00:00Z"
Required for "calendar-event" type. End date and time in ISO format.
Example: "2024-11-30T15:00:00Z"
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Text Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/messages/text-message
POST https://api.zixflow.com/api/v1/campaign/rcs/message/text
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
text messages which need to send. Maximum length: 4096 characters.
A Suggestions Object includes the following fields and formatting options:
Suggestion Type Options: "reply", "open-url", "dialer", "location",
"calendar-event"
Displayed as button text to the end user.
(Optional) Used to track user interactions. When clicked, a unique
postback event is triggered on the webhook.
Required if type is "open-url". Must be a valid URL. Example:
[https://google.com](https://google.com)
Required for "dialer" type. Must be a valid phone number with country code
and "+" prefix. Example: +919876543210
Required for "location" type. Label for the location. Example: "Office
Location"
Required for "location" type. Latitude value in numeric format. Example:
12.9363301
Required for "location" type. Longitude value in numeric format. Example:
77.6084231
Required for "calendar-event" type. Event title. Example: "Zixflow
Appointment"
Required for "calendar-event" type. Event description. Example:
"Appointment with Zixflow team to understand their application."
Required for "calendar-event" type. Start date and time in ISO format.
Example: "2024-11-30T14:00:00Z"
Required for "calendar-event" type. End date and time in ISO format.
Example: "2024-11-30T15:00:00Z"
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Video Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/messages/video-message
POST https://api.zixflow.com/api/v1/campaign/rcs/message/video
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
Audio url which need to send
A Suggestions Object includes the following fields and formatting options:
Suggestion Type Options: "reply", "open-url", "dialer", "location",
"calendar-event"
Displayed as button text to the end user.
(Optional) Used to track user interactions. When clicked, a unique
postback event is triggered on the webhook.
Required if type is "open-url". Must be a valid URL. Example:
[https://google.com](https://google.com)
Required for "dialer" type. Must be a valid phone number with country code
and "+" prefix. Example: +919876543210
Required for "location" type. Label for the location. Example: "Office
Location"
Required for "location" type. Latitude value in numeric format. Example:
12.9363301
Required for "location" type. Longitude value in numeric format. Example:
77.6084231
Required for "calendar-event" type. Event title. Example: "Zixflow
Appointment"
Required for "calendar-event" type. Event description. Example:
"Appointment with Zixflow team to understand their application."
Required for "calendar-event" type. Start date and time in ISO format.
Example: "2024-11-30T14:00:00Z"
Required for "calendar-event" type. End date and time in ISO format.
Example: "2024-11-30T15:00:00Z"
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Template Message
Source: https://docs.zixflow.com/api-reference/campaign/rcs/send-rcs-template
POST https://api.zixflow.com/api/v1/campaign/rcs/message/template
# Send RCS Template Message
This API enables you to send RCS template messages to users who have a live session. Only approved templates are supported for message sending.
### Request Body Parameters
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the RCS bot. Find the BOT ID on
the Zixflow RCS Settings page. [RCS
Settings](https://app.zixflow.com/campaign/settings/rcs)
The name of the approved template for sending RCS messages.
(Optional) An object with key-value pairs to populate template placeholders.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) Indicates whether to wait for the submission status. Set it to
"true" if you want to wait for the submission status; otherwise, it defaults
to "false."
### Response
Indicates if the API call was successful. Returns true if successful, false
otherwise.
Provides a message indicating success or details of any error.
### Response Examples
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Get Email Message Report
Source: https://docs.zixflow.com/api-reference/campaign/report/get-email-report
GET https://api.zixflow.com/api/v1/campaign/report/email/{messageId}
#### Path Params
Enter the message ID that you received when sending the campaign.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
It refers to your account ID or workspace ID.
It refers to your message ID
It refers to your campaign ID.
It refers to report status (SENT, OPENED, CLICKED, BOUNCE, COMPLAINT,
UNSUBSCRIBED)
It refers to last status time
It refers to the recipient's email where the message was sent.
It refers to failed reason for message if it failed or undelivered.
```json 200-Success theme={null}
{
"status": true,
"message": "Successfully fetched",
"data": {
"accountId": "652e5068c33fe15059e5s020",
"messageId": "66e120d1b9ee33b6ba00fsd7",
"campaignId": "66e120d1b9eht5059e5e01d",
"status": "SENT",
"statusAt": "2024-09-11T04:47:16.000Z",
"destination": "test@gmail.com",
"remark": ""
}
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Report data not found"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get SMS Message Report
Source: https://docs.zixflow.com/api-reference/campaign/report/get-sms-report
GET https://api.zixflow.com/api/v1/campaign/report/sms/{messageId}
#### Path Params
Enter the message ID that you received when sending the campaign.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
It refers to your account ID or workspace ID.
It refers to your message ID
It refers to your campaign ID.
It refers to report status (SENT, DELIVERED, UNDELIVERED)
It refers to last status time
It refers to the recipient's phone number where the message was sent.
It refers to failed reason for message if it failed or undelivered.
```json 200-Success theme={null}
{
"status": true,
"message": "Successfully fetched",
"data": {
"accountId": "652e5068c33fe15059e5s020",
"messageId": "66e120d1b9ee33b6ba00fsd7",
"campaignId": "66e120d1b9eht5059e5e01d",
"status": "DELIVERED",
"statusAt": "2024-09-11T04:47:16.000Z",
"destination": "919090909090",
"remark": ""
}
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Report data not found"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get Whatsapp Message Report
Source: https://docs.zixflow.com/api-reference/campaign/report/get-whatsapp-report
GET https://api.zixflow.com/api/v1/campaign/report/whatsapp/{messageId}
#### Path Params
Enter the message ID that you received when sending the campaign.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
It refers to your account ID or workspace ID.
It refers to your message ID
It refers to your campaign ID.
It refers to report status (SENT, DELIVERED, FAILED, READ)
It refers to last status time
It refers to the recipient's phone number where the message was sent.
It refers to failed reason for message if it failed or undelivered.
```json 200-Success theme={null}
{
"status": true,
"message": "Successfully fetched",
"data": {
"accountId": "652e5068c33fe15059e5s020",
"messageId": "66e120d1b9ee33b6ba00fsd7",
"campaignId": "66e120d1b9eht5059e5e01d",
"status": "DELIVERED",
"statusAt": "2024-09-11T04:47:16.000Z",
"destination": "919090909090",
"remark": ""
}
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Report data not found"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Introduction
Source: https://docs.zixflow.com/api-reference/campaign/sms/introduction
Campaign - Send SMS
Zixflow makes sending and receiving SMS easy. Find the documentation, sample codes and developer tools you need to build exactly what you want, fast & secure. We’ll handle all the complexity of mobile operators and global regulations. Let’s get building.
Our SMS API allows you to send text messages to users around the globe through simple RESTful APIs.
#### To Send Messages, Generate the API Key
1. Go to Developers under Admin settings at Zixflow Platform - [https://app.zixflow.com/settings/workspace/developer](https://app.zixflow.com/settings/workspace/developer)
2. Now assign the respective permissions to the channel which you want to give specific or all channels (all channels recommended so you don't have to change the API in the future if you want to use other channels)
# Send SMS
Source: https://docs.zixflow.com/api-reference/campaign/sms/send-sms-get
GET https://api.zixflow.com/api/apis/campaign/sms/send
To make it simple, We have designed a method which allows you to send SMS in a single line of the HTTP GET request
[https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424\&senderId=HELLOW\&route=transactional\&number=9112345678974\&message=your\_message\&submissionStatus=true](https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424\&senderId=HELLOW\&route=transactional\&number=9112345678974\&message=your_message\&submissionStatus=true)
[https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=3456789\&senderId=IDENTY\&route=transactional\&number=919090909090\&message=your\_message\&isFlash=true\&dltTemplateId=1234\&dltEntityId=1234\&reportURL=https://webhook.site/0a276bc5-f0e4-4235-9006-b58b\&submissionStatus=true](https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=3456789\&senderId=IDENTY\&route=transactional\&number=919090909090\&message=your_message\&isFlash=true\&dltTemplateId=1234\&dltEntityId=1234\&reportURL=https://webhook.site/0a276bc5-f0e4-4235-9006-b58b\&submissionStatus=true)
#### Query
This is a token used to validate requests coming from valid users. You can
obtain this token from your Zixflow account's API keys section.
The registered and approved Sender name to be used for the message.
Type of connectivity for the message, such as promotional, transactional, or
OTP.
The phone number with a country prefix to which the message will be sent.
The content of the message that you want to send.
(Optional) Set this parameter to true if you want to send a flash SMS via the
API; otherwise, set it to false.
(Optional) Only applicable for India. If you want to pass a template ID
directly via the API, you can do so with this parameter.
(Optional) Only applicable for India. If you want to pass an entity ID
directly via the API, you can do so with this parameter.
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) When a user wants to wait for submission status from the API, they
must set this key to true. It is false by default.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
#### Reports & Deliveries
In order to get delivery reports directly into your system you need to take care of few simple things.
1. Please make sure that, the URL passed in the field reportUrl, must accept HTTP POST request with JSON request body.
If you are sending an SMS using a GET URL, it is necessary to encode the message using the URL Encoding method before sending it. However, if you are using a POST API, there is no need to encode the message.
For example, if the message is "Hi User 1 & User 2, Welcome to zixflow," it needs to be encoded as "Hi%20User%201%20%26%20User%202%2C%20Welcome%20to%20zixflow" using URL Encoding.
You can use a tool like [https://www.urlencoder.org/](https://www.urlencoder.org/) for reference on how to properly encode your message.
```bash cUrl theme={null}
curl --location --request GET 'https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424&senderId=HELLOW&route=transactional&number=9112345678974&message=Your OTP is XXXX&submissionStatus=true'
```
```js Nodejs theme={null}
var axios = require("axios");
var config = {
method: "get",
url: "https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424&senderId=HELLOW&route=transactional&number=9112345678974&message=Your OTP is XXXX&submissionStatus=true",
headers: {},
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```py Python theme={null}
import requests
url = "https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424&senderId=HELLOW&route=transactional&number=9112345678974&message=Your OTP is XXXX&submissionStatus=true"
payload={}
headers = {}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424&senderId=HELLOW&route=transactional&number=9112345678974&message=Your OTP is XXXX&submissionStatus=true")
.method("GET", null)
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var request = http.Request('GET', Uri.parse('https://api.zixflow.com/api/apis/campaign/sms/send?apiKey=123424&senderId=HELLOW&route=transactional&number=9112345678974&message=Your OTP is XXXX&submissionStatus=true'));
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"message": "SMS sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send SMS
Source: https://docs.zixflow.com/api-reference/campaign/sms/send-sms-post
POST https://api.zixflow.com/api/v1/campaign/sms/send
#### Body
The registered and approved Sender name to be used for the message.
Type of connectivity for the message, such as promotional, transactional, or
OTP.
The phone number with a country prefix to which the message will be sent.
The content of the message that you want to send.
(Optional) Set this parameter to true if you want to send a flash SMS via the
API; otherwise, set it to false.
(Optional) Only applicable for India. If you want to pass a template ID
directly via the API, you can do so with this parameter.
(Optional) Only applicable for India. If you want to pass an entity ID
directly via the API, you can do so with this parameter.
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
(Optional) When a user wants to wait for submission status from the API, they
must set this key to true. It is false by default.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
#### Reports & Deliveries
In order to get delivery reports directly into your system you need to take care of few simple things.
1. Please make sure that, the URL passed in the field reportUrl, must accept HTTP POST request with JSON request body.
If you are sending an SMS using a GET URL, it is necessary to encode the message using the URL Encoding method before sending it. However, if you are using a POST API, there is no need to encode the message.
For example, if the message is "Hi User 1 & User 2, Welcome to zixflow," it needs to be encoded as "Hi%20User%201%20%26%20User%202%2C%20Welcome%20to%20zixflow" using URL Encoding.
You can use a tool like [https://www.urlencoder.org/](https://www.urlencoder.org/) for reference on how to properly encode your message.
```bash cUrl theme={null}
curl --location --request POST 'https://api.zixflow.com/api/v1/campaign/sms/send' \
--header 'Authorization: Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c' \
--header 'Content-Type: application/json' \
--data-raw '{
"senderId": "IDENTY",
"route": "transactional",
"number": "919090909090",
"message": "Your OTP is 0101",
"isFlash": false,
"dltTemplateId": "1234",
"dltEntityId": "1234",
"reportURL": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5",
"submissionStatus": false
}'
```
```js Nodejs theme={null}
var axios = require("axios");
var data = JSON.stringify({
senderId: "IDENTY",
route: "transactional",
number: "919090909090",
message: "Your OTP is 0101",
isFlash: false,
dltTemplateId: "1234",
dltEntityId: "1234",
reportURL: "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5",
submissionStatus: false,
});
var config = {
method: "post",
url: "https://api.zixflow.com/api/v1/campaign/sms/send",
headers: {
Authorization:
"Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c",
"Content-Type": "application/json",
},
data: data,
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```py Python theme={null}
import requests
import json
url = "https://api.zixflow.com/api/v1/campaign/sms/send"
payload = json.dumps({
"senderId": "IDENTY",
"route": "transactional",
"number": "919090909090",
"message": "Your OTP is 0101",
"isFlash": False,
"dltTemplateId": "1234",
"dltEntityId": "1234",
"reportURL": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5",
"submissionStatus": False
})
headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"senderId\": \"IDENTY\",\n \"route\": \"transactional\",\n \"number\": \"919090909090\",\n \"message\": \"Your OTP is 0101\",\n \"isFlash\": false,\n \"dltTemplateId\": \"1234\",\n \"dltEntityId\": \"1234\",\n \"reportURL\": \"https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5\",\n \"submissionStatus\": false\n}");
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/v1/campaign/sms/send")
.method("POST", body)
.addHeader("Authorization", "Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c")
.addHeader("Content-Type", "application/json")
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var headers = {
'Authorization': 'Bearer 56616641ee123b80a36b99fedc2bfa4f0d7f63072d97ddb1578c7a5aa970e7edde6618c7f750beb921aee3eebc9cc48899ee992fa4eb95a663d0e0b2eaa35b73f3b2344c',
'Content-Type': 'application/json'
};
var request = http.Request('POST', Uri.parse('https://api.zixflow.com/api/v1/campaign/sms/send'));
request.body = json.encode({
"senderId": "IDENTY",
"route": "transactional",
"number": "919090909090",
"message": "Your OTP is 0101",
"isFlash": false,
"dltTemplateId": "1234",
"dltEntityId": "1234",
"reportURL": "https://webhook.site/0a276bc5-f0e4-4235-9006-b58b7d224ad5",
"submissionStatus": false
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"message": "SMS sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Get List Of Templates
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/get-template-list
GET https://api.zixflow.com/api/v1/campaign/whatsapp/templates/{phoneId}
#### Path Params
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
#### Query Params
The number of templates to be returned, set to 10 in this instance. This
parameter restricts the response to a specific quantity of templates.
The starting point from which the templates are to be fetched within the
entire dataset. In this case, it is set to 0, indicating that retrieval should
commence from the beginning of the dataset.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
It defines template name
It defines template language
It defines template category
It defines template status
Whatsapp Template components structure defined
[here](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates)
```json 200-Success theme={null}
{
"status": true,
"message": "Success",
"data": [
{
"language": "en",
"name": "unsubscribe_response",
"category": "MARKETING",
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Unsubscribe"
},
{
"type": "BODY",
"text": "We're sorry to see you go 💔\n\nYou've been unsubscribed from receiving messages from us.\n\nIf you'd like to again start getting messages, reply with *START*"
}
],
"status": "APPROVED"
}
]
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "file size exceeded"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Of WA Accounts
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/get-whatsapp-accounts
GET https://api.zixflow.com/api/v1/campaign/whatsapp/accounts
# Incoming Message Webhook
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/incoming-message-webhook
You can receive incoming messages on your webhook, enabling real-time integration with your systems.
> Note: this is not for message delivery status — the webhook is triggered only for incoming messages.
#### 🛠️ How to Create a Webhook
To set up a webhook in Zixflow:
1. Navigate to the **Zixflow** application.
2. Go to the **Settings** section.
3. Open the **Developer** section.
4. Click on **Webhook**.
5. Click on the **➕ Add** button.
##### Below is a list of available events, including their structure and example payloads.
#### 1. `incoming.whatsapp.message`
Triggered whenever a new WhatsApp message is received in your Zixflow account.
```json theme={null}
{
"event": "incoming.whatsapp.message", // Event Type
"eventId": "3472dff5-7c06-40e6-be06-a28a335ac263", // UUID v4 - Event id to uniquilly identify event
"timestamp": 1744346001738, // Message At 13 digit timestamp
"phoneId": "xxxxxxxxxxxxxx79", // Meta Phone ID
"wabaId": "xxxxxxxxxxxx266", // Meta WABA ID
"messageId": "wamid.xxxxxxxxxxxxxxxx", // Meta messsage id
"sender": {
"name": "John Doe", // End user name
"number": "919999999999" // End user phone number with country code
},
"message": {
// When Text Message Receives
"type": "text",
"text": {
"body": "Hi"
}
// When Emoji Message Recieves
"type": "text",
"text":{
"body": "😁😁"
}
// When Image Message Recieves
"type": "image",
"image":{
"caption": "", // Optional - image caption if there is any
"mime_type": "image/xxxx", // Image Mime type
"link": "https:://", // Image URL
"name": "sample-image.png", // Image Name
"size": 192699 // image size in bytes
}
// When Video Message Recieves
"type": "video",
"video":{
"caption": "", // Optional - video caption if there is any
"mime_type": "video/xxxx", // Video Mime type
"link": "https:://", // Video URL
"name": "sample-video.mp4", // Video Name
"size": 192699 // Video size in bytes
}
// When Audio Message Recieves
"type": "audio",
"video":{
"caption": "", // Optional - audio caption if there is any
"mime_type": "audio/xxxx", // Audio Mime type
"link": "https:://", // Audio URL
"name": "sample-audio.mp3", // Audio Name
"size": 192699 // Audio size in bytes
},
// When Document Message Recieves
"type": "document",
"video":{
"caption": "", // Optional - Document caption if there is any
"mime_type": "xxxxx/xxxx", // Document Mime type
"link": "https:://", // Document URL
"name": "sample-document.pdf", // Document Name
"size": 192699 // Document size in bytes
},
// When Location Message Recieves
"type": "location",
"location":{
"address": "Cecilia Chapman, Mankato Mississippi", // Optional - Address
"latitude": 71.224418791205, // Location Latitude
"longitude": 72.080068686951, // Location Longitude
"name": "Cecilia Chapman", // Location Name
"url": "https://" // Location URL
},
// When Contact Message Recieves
"type": "contacts",
"contacts":[ // Its array, user can share multiple contacts
{
"name":{
"first_name": "Customer", // Conatct First Name
"last_name": "Care", // Conatct Last Name
"formatted_name": "Customer Care" // Formated Name
},
"phones":[
{
"phone": "+91 121", // Share phone number
"type": "MOBILE" // Phone number type
}
]
}
],
// * When User Click On Reply Button Message
"type": "button",
"button":{
"text": "FAQs" // Button Text
},
// When User Respond to Flow Form In Whatsapp
"type": "interactive",
"interactive":{
"type": "nfm_reply",
"nfm_reply":{
"response_json": "{"screen_1_source_0":"0","screen_0_firstName_0":"text","screen_0_lastName_1":"Kaha","screen_0_email_2":"testb@gmail.com"}", // User flow form response
"body": "Sent",
"name": "flow"
}
},
// When user add product in crate generte order from whatsapp
"type": "order",
"order": {
"catalog_id": "7079831418799929", // Product Catalog Id
"text": "",
"product_items":[
{
"product_retailer_id": "2lc203LtsY", // Selected product ID
"quantity": 1, // Product quantity
"item_price": 9876, // Product Unit Price
"currency": "USD" // Product Currency
}
]
}
},
"metaData": {
"accountId": "xxxxxxxxxxxxxb8de24d95b59", // Zixflow Account Id
"orgMsgId": "xxxxxxxxxxxx67f3cbd6b9d1" // Zixflow Org Message Id
}
}
```
# Introduction
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/introduction
Campaign - Send Whatsapp
Welcome to the WhatsApp API documentation. This guide provides developers with information on two key APIs: *Get WhatsApp Variables* and *Send WhatsApp Message*. These APIs are designed to enable you to send WhatsApp messages with ease, ensuring that you follow WhatsApp template guidelines.
#### To Send Messages, Generate the API Key
1. Go to Developers under Admin settings at Zixflow Platform - [https://app.zixflow.com/settings/workspace/developer](https://app.zixflow.com/settings/workspace/developer)
2. Now assign the respective permissions to the channel which you want to give specific or all channels (all channels recommended so you don't have to change the API in the future if you want to use other channels)
#### Get WhatsApp Variables API
The Get WhatsApp Variables API provides essential information about the WhatsApp template you intend to use. This API allows you to understand the template's structure, including the variable names and value types required for successful message delivery.
This will return a JSON response with information about the template variables, such as their names and data types.
If the template does not require any variables, the API response will indicate that. In this case, you can proceed to send the WhatsApp message without including any variables.
#### Send WhatsApp Message API
The Send WhatsApp Message API is used to send WhatsApp messages to end-users based on approved templates. This API allows you to craft and deliver messages in compliance with WhatsApp's policies.
It's essential to provide valid variables when sending WhatsApp messages. If the variables do not match the template requirements or are not correctly formatted, the message will not be sent to the end-user.
Always ensure that the variable data provided in your API request is accurate and adheres to the template's structure.
WhatsApp messages can only be sent using approved templates provided by
WhatsApp's official template library. Make sure to use the appropriate
template and ensure that it complies with WhatsApp's guidelines to guarantee
successful message delivery.
# Send Whatsapp Audio Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/audio-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send audio messages keep message type as "audio"
A Audio Object consists of the following fields and formatting options:
The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Document Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/document-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send document messages keep message type as "document"
A Document Object consists of the following fields and formatting options:
The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs.
Optional.
Describes the specified image, document, or video. Do not use it with audio or sticker media.
Optional.
Describes the filename for the specific document. Use only with document media.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Image Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/image-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send image messages keep message type as "image"
A Image Object consists of the following fields and formatting options:
The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs.
Optional.
Describes the specified image, document, or video. Do not use it with audio or sticker media.
If the "linkWithRecord" is set to true, the source from which the WhatsApp message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it in the inbox. Set it to "true" for linking with a record; otherwise, it defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Location Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/location-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send location messages keep message type as "location"
A Location Object consists of the following fields and formatting options:
Required.
The longitude of the location.
Required.
The latitude of the location.
Optional.
The name of the location.
Optional.
The address of the location. This field is only displayed if name is present.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Text Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/text-messages
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send text messages keep message type as "text"
A Text Object consists of the following fields and formatting options:
Optional.
By default, WhatsApp recognizes URLs and makes them clickable, but you can also include a preview box with more information about the link. Set this field to true if you want to include a URL preview box.
The majority of the time when you send a URL, whether with a preview or not, the receiver of the message will see a URL that they can click on.
URL previews are only rendered after one of the following has occurred:
The business has sent a message template to the user.
The user initiates a conversation with a "click to chat" link.
The user adds the business phone number to their address book and initiates a conversation.
Default: false
Required for text messages.
The text of the text message that can contain URLs and supports formatting. To view available formatting options, see Text Object Formatting Options.
If you include URLs in your text and want to include a preview box in text messages ("preview\_url": true), ensure it starts with http\:// or https\://. You must include a hostname, since IP addresses are not matched.
Maximum length: 4096 characters.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Video Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/messages/video-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/message/send
You can send messages only if the user session is already active; otherwise, the messages will fail. We recommend sending a approved template message the first time you contact users. Once the session is open, you can send unlimited messages via these APIs. For more info [click here](https://help.zixflow.com/en/articles/65-what-is-conversation-based-pricing)
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
To send video messages keep message type as "video"
A Video Object consists of the following fields and formatting options:
Required when type is audio, document, image, sticker, or video and you are not using an uploaded media ID.
The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs.
Optional.
Describes the specified image, document, or video. Do not use it with audio or sticker media.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Send Whatsapp Template Message
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/send-whatsapp-message
POST https://api.zixflow.com/api/v1/campaign/whatsapp/send
#### Body
Specifies the recipient's phone number in international format (e.g.,
"1xxxxxxxxxx").
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
Refers to the name of the template to be used for the WhatsApp message. In
this case, it is set to "hello\_world." You can obtain it from the template
list screen.
Specifies the language of the message, with "en\_US" representing American
English. You can get it from the template list screen.
Custom variables used for the template. Please provide a JSON object with the
required data.
If the "linkWithRecord" is set to true, the source from which the WhatsApp
message is sent should be mentioned; otherwise, it defaults to API.
Specify whether to associate the current message with a record and display it
in the inbox. Set it to "true" for linking with a record; otherwise, it
defaults to "false."
(Optional) Specify the URL where the user's report and deliveries should be
delivered.
Indicates whether to wait for the submission status. Set it to "true" if you
want to wait for the submission status; otherwise, it defaults to "false."
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
#### Variables
To know all required variable and their keys for selected template, use above Get Template Variable API. It will return response as
```json theme={null}
{
"status": true,
"message": "Success",
"data": [
{
"label": "Header Image",
"keyName": "image",
"type": "IMAGE_URL"
},
{
"label": "Body 1",
"keyName": "body_1",
"type": "TEXT"
}
]
}
```
| type | sample value |
| ------------- | ---------------- |
| IMAGE\_URL | `` |
| VIDEO\_URL | `` |
| DOCUMENT\_URL | `` |
| TEXT | `` |
| OTP | `` |
For above variable response the varibales value will be
```json theme={null}
{
variables:{
"image": "",
"body_1": ""
....
[keyName]:""
}
}
```
```bash cUrl theme={null}
curl --location --request POST 'https://api.zixflow.com/api/v1/campaign/whatsapp/send' \
--header 'Authorization: Bearer {{zixflow_api_key}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"to": "13023895111",
"phoneId": "104898309288724",
"templateName": "marketing_sample_2",
"language": "en",
"variables": {
"video":"http://techslides.com/demos/sample-videos/small.mp4"
},
"submissionStatus": true
}'
```
```js Nodejs theme={null}
var axios = require("axios");
var data = JSON.stringify({
to: "13023895111",
phoneId: "104898309288724",
templateName: "marketing_sample_2",
language: "en",
variables: {
video: "http://techslides.com/demos/sample-videos/small.mp4",
},
submissionStatus: true,
});
var config = {
method: "post",
url: "https://api.zixflow.com/api/v1/campaign/whatsapp/send",
headers: {
Authorization: "Bearer {{zixflow_api_key}}",
"Content-Type": "application/json",
},
data: data,
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```py Python theme={null}
import requests
import json
url = "https://api.zixflow.com/api/v1/campaign/whatsapp/send"
payload = json.dumps({
"to": "13023895111",
"phoneId": "104898309288724",
"templateName": "marketing_sample_2",
"language": "en",
"variables": {
"video": "http://techslides.com/demos/sample-videos/small.mp4"
},
"submissionStatus": True
})
headers = {
'Authorization': 'Bearer {{zixflow_api_key}}',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"to\": \"13023895111\",\n \"phoneId\": \"104898309288724\",\n \"templateName\": \"marketing_sample_2\",\n \"language\": \"en\",\n \"variables\": {\n \"video\":\"http://techslides.com/demos/sample-videos/small.mp4\"\n },\n \"submissionStatus\": true\n}");
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/v1/campaign/whatsapp/send")
.method("POST", body)
.addHeader("Authorization", "Bearer {{zixflow_api_key}}")
.addHeader("Content-Type", "application/json")
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var headers = {
'Authorization': 'Bearer {{zixflow_api_key}}',
'Content-Type': 'application/json'
};
var request = http.Request('POST', Uri.parse('https://api.zixflow.com/api/v1/campaign/whatsapp/send'));
request.body = json.encode({
"to": "13023895111",
"phoneId": "104898309288724",
"templateName": "marketing_sample_2",
"language": "en",
"variables": {
"video": "http://techslides.com/demos/sample-videos/small.mp4"
},
"submissionStatus": true
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"message": "Message sent successfully"
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "Invalid data Provided"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "Unauthorised"
}
```
# Get Template Variables
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/template-variable-get
GET https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys
#### Query Params
This is the unique identifier associated with the sender's phone number given
by WhatsApp. Find the Phone ID on the Zixflow WhatsApp Settings page next to
the number. [Whatsapp
Settings](https://app.zixflow.com/campaign/settings/whatsapp)
Refers to the name of the template to be used for the message, e.g.,
"welcome\_message."
Specifies the language for the message, e.g., "en" for English.
#### Response
Indicates whether the call was successful. true if successful, false if not.
success or error response message
The contents of uploaded file
It defines variable label from the template
This defines the essential key names that must be used when specifying values for this variable in the WhatsApp API.
It defines type of value
| type | sample value |
| ------------- | ---------------- |
| IMAGE\_URL | `` |
| VIDEO\_URL | `` |
| DOCUMENT\_URL | `` |
| TEXT | `` |
| OTP | `` |
```bash cUrl theme={null}
curl --location --request GET 'https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys?templateName=welcome_message&language=en' \
--header 'Authorization: Bearer {{zixflow_api_key}}'
```
```js Nodejs theme={null}
var axios = require("axios");
var config = {
method: "get",
url: "https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys?templateName=welcome_message&language=en",
headers: {
Authorization: "Bearer {{zixflow_api_key}}",
},
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
```
```py Python theme={null}
import requests
url = "https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys?templateName=welcome_message&language=en"
payload={}
headers = {
'Authorization': 'Bearer {{zixflow_api_key}}'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
```
```java JAVA theme={null}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys?templateName=welcome_message&language=en")
.method("GET", null)
.addHeader("Authorization", "Bearer {{zixflow_api_key}}")
.build();
Response response = client.newCall(request).execute();
```
```dart DART theme={null}
var headers = {
'Authorization': 'Bearer {{zixflow_api_key}}'
};
var request = http.Request('GET', Uri.parse('https://api.zixflow.com/api/v1/campaign/whatsapp/variable-keys?templateName=welcome_message&language=en'));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
```
```json 200-Success theme={null}
{
"status": true,
"message": "Success",
"data": [
{
"label": "Header Image",
"keyName": "image",
"type": "IMAGE_URL"
},
{
"label": "Body 1",
"keyName": "body_1",
"type": "TEXT"
}
]
}
```
```json 400-Bad Request theme={null}
{
"status": false,
"message": "file size exceeded"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Authentication Templates
Source: https://docs.zixflow.com/api-reference/campaign/whatsapp/whatsapp-auth-template
If your mobile app offers users the option to receive one-time passwords or verification codes via WhatsApp, you must use an authentication template.
##### How to Create an Auth Template in Zixflow
* Navigate to the **WhatsApp Templates** section.
* Click on **Authentication Template**.
* Choose the desired template type.
* Submit the template for approval.
Once approved, you can start using it!
##### Authentication templates consist of:
* Fixed preset text: `` is your verification code.
* An optional security disclaimer: For your security, do not share this code.
* An optional expiration warning: This code expires in `` minutes.
* Either a one-tap autofill button, a copy code button, or no button at all if using zero-tap.
One-tap autofill buttons are the preferred solution as they offer the best user experience. However, one-tap autofill buttons are currently only supported on Android and require additional changes to your app's code.
See [additional guidelines](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fupdates-to-pricing%2Fnew-template-guidelines%23authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) for when it's appropriate to use an authentication template.
#### One-Tap Autofill Authentication Templates
Authentication templates include a one-tap autofill button.
When a WhatsApp user taps the autofill button, the WhatsApp client triggers an activity which opens your app and delivers it the password or code.
See [One-Tap Autofill Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fautofill-button-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Copy Code Authentication Templates
Copy code authentication templates allow you to send a one-time password or code along with a copy code button to your users.
When a WhatsApp user taps the copy code button, the WhatsApp client copies the password or code to the device's clipboard. The user can then switch to your app and paste the password or code into your app.
See [Copy Code Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fcopy-code-button-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Zero-Tap Authentication Templates
Zero-tap authentication templates allow your users to receive one-time passwords or codes via WhatsApp without having to leave your app.
When a user in your app requests a password or code and you deliver it using a zero-tap authentication template, the WhatsApp client broadcasts the included password or code, which your app can then capture with a broadcast receiver.
See [Zero-Tap Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fzero-tap-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Best Practices
* Confirm the user's WhatsApp phone number before sending the one-time password or code to that number.
* Make it clear to your user that the password or code will be delivered to their WhatsApp phone number, especially if you offer multiple ways for the user to receive password or code delivery. See Getting Opt-In for additional tips.
* When the user pastes the password or code into your app, or your app receives it as part of the one-tap autofill button flow, make it clear to the user that your app has captured it.
See also [Best Practices for Authenticating Users via WhatsApp](https://developers.facebook.com/docs/whatsapp/business-management-api/authentication-templates/authentication-best-practices).
# Whatsapp Authentication-International Rates
Specific countries have an authentication-international rate in our whatsapp pricing. If you open an authentication conversation with a WhatsApp user whose country calling code is for a country that has an authentication-international rate, you will be billed that country's authentication–international rate if:
* your business is eligible for authentication-international rates
* your business is based in another country (see Primary Business Location explained below)
* the conversation was opened on or after your start time for that country
For example, if your business is based in India and you open an authentication conversation with a WhatsApp user who has a +91 (India) country calling code, you will not be billed the authentication-international rate, since you are based in the same country as the user. If your business is based in Indonesia, however, you will be billed the authentication-international rate, if you meet all of the criteria above.
#### Eligibility
If your business opens more than 750K conversations in a moving 30-day period across all of your WhatsApp Business Accounts with WhatsApp users whose country calling codes are for a country that has an authentication-international rate, it will be deemed eligible for authentication-international rates.
Once deemed eligible, WhatsApp will set your start times 30 days out for each country that has an authentication-international rate. In addition, WhatsApp will attempt to determine your primary business location using publicly-available information.
WhatsApp will then send you an eligibility email that includes these start times and the country that WhatsApp set as your primary business location (if WhatsApp were able to determine the country). This provides you with 30 days notice before authentication-international rates apply.
Note that eligibility is permanent. Once your business is deemed eligible, all authentication conversations opened on or after your start time will be charged the authentication-international rate in markets where authorization-international rates apply.
#### Start Times
Start times are business- and country-specific timestamps. They indicate when newly-opened authentication conversations are subject to authentication-international rates. Authentication conversations opened by your business with WhatsApp users in these countries on or after these dates only will be charged authentication-international rates.
Start times are set when your business is first deemed eligible for authentication-international rates, and are 30 days from your eligibility date, so you will always have 30-days notice before the authentication-international rate applies.
Start times are included in your eligibility email.
#### Primary Business Location
Your primary business location is the country where your business is based. It will appear in the Business Manager under the Primary Business Location field starting May 1, 2024, if WhatsApp are able to determine where your business is based using publicly-available information.
Examples of publicly-available information include:
* Where your business may be publicly-traded and listed
* Your business's corporate structure (where a parent or may be based or publicly-traded)
WhatsApp will attempt to determine where your business is based when:
* It is deemed eligible for authentication-international rates
* You edit your primary business location using the Business Manager.
This process can take up to 3 business days. The outcome of this determination can be:
* Verified — WhatsApp determined where your business is based and set your primary business location to this country.
* Need more information — WhatsApp require more information in order to make a determination.
* Rejected — WhatsApp disagreed with the country you designated in the Business Manager (if you used it to edit the Primary Business Location field)
You will be notified of the outcome in your initial eligibility email, or in a separate email if you used the Business Manager to edit your location.
If rejected or if WhatsApp need more information, or if you disagree with the country it determined to be the primary business location, you can use the Business Manager to edit your location.
Note that if your primary business location status is not verified but you are past your start time for a given country, any authentication conversation that you open with a WhatsApp user in that country will be billed the authentication-international rate.
#### Editing Your Primary Business Location
You can edit your business's primary business location at any time using WhatsApp Business Manager:
[Business Settings](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fbusiness.facebook.com%2Fsettings%2F\&event_type=click\&last_nav_impression_id=0cpNYUEOYwPxwso8D\&max_percent_page_viewed=56\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fpricing%2Fauthentication-international-rates\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dpricing%26path3%3Dauthentication-international-rates\®ion=apac\&scrolled=true\&session_id=0txnhiCp2uAynN61d\&site=developers) > Business info > Primary Business Location > Edit (button)
The Primary Business Location field in the Business Manager will also display a status:
* Verified — WhatsApp have verified your business's primary location.
* Pending verification — WhatsApp in the process of determining your business's primary location.
* Rejected — WhatsApp disagreed with the country you designated, based on publicly available information and what you included when you edited your location. You can manually edit your location again and include different information as part of your submission.
#### Examples
A business with an Indonesia primary business location opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| Indonesia | - | - | Authentication |
| India | No | - | Authentication |
| India | Yes | No | Authentication |
| India | Yes | Yes | Authentication-International |
A business with an India primary business location opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| India | - | - | Authentication |
| Indonesia | No | - | Authentication |
| Indonesia | Yes | No | Authentication |
| Indonesia | Yes | Yes | Authentication-International |
A business with a primary business location that does not have an authentication-international rate opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| Indonesia | No | - | Authentication |
| Indonesia | Yes | No | Authentication |
| Indonesia | Yes | Yes | Authentication-International |
| India | No | - | Authentication |
| India | Yes | No | Authentication |
| India | Yes | Yes | Authentication-International |
# Get Collection By ID
Source: https://docs.zixflow.com/api-reference/collection/get-collection
GET https://api.zixflow.com/api/v1/collections/{collectionId}
This endpoint returns a collection
#### Path Params
A unique identifier for the list, allowing for precise referencing.
#### API Response Description:
Upon querying your collection endpoint, the API returns a structured response containing information about the requested collection. Here's a breakdown of the response:
Indicates the success or failure of the collection retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the collection with the message
"Collection fetched successfully."
An Object containing details of the available collections.
A unique identifier for the collection, allowing for precise referencing.
The human-readable name of the collection, providing a clear
identification.
A unique identifier for the collection, useful for programmatic
identification and differentiation.
Specifies the type of the collection. For system-defined collections, it
will have values like "people," "company," or "deals." For user-defined
collections, it will return "custom."
This structured response aims to offer clarity and ease of interpretation, enabling developers and users to effectively understand and utilize the information retrieved from the collection endpoint.
```json 200-Success theme={null}
{
"status": true,
"message": "Collection fetched successfully",
"data": {
"_id": "652e506bf781c59be3825523",
"collectionType": "people",
"name": "People",
"slug": "people"
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Of Collections
Source: https://docs.zixflow.com/api-reference/collection/get-list-of-collections
GET https://api.zixflow.com/api/v1/collections
Lists all system and custom collections of your workspace.
#### API Response Description:
Upon querying your collection endpoint, the API returns a structured response containing information about the available collections. Here's a breakdown of the response:
Indicates the success or failure of the collection retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the collection with the message
"Collection fetched successfully."
An array containing details of the available collections. For each collection
in the data array:
A unique identifier for the collection, allowing for precise referencing.
The human-readable name of the collection, providing a clear
identification.
A unique identifier for the collection, useful for programmatic
identification and differentiation.
Specifies the type of the collection. For system-defined collections, it
will have values like "people," "company," or "deals." For user-defined
collections, it will return "custom."
This structured response aims to offer clarity and ease of interpretation, enabling developers and users to effectively understand and utilize the information retrieved from the collection endpoint.
```json 200-Success theme={null}
{
"status": true,
"message": "Collection fetched successfully",
"data": [
{
"_id": "652e506bf781c59be3825523",
"collectionType": "people",
"name": "People",
"slug": "people"
},
{
"_id": "652e506bf781c59be3825526",
"collectionType": "company",
"name": "Company",
"slug": "company"
},
{
"_id": "652e506bf781c59be3825528",
"collectionType": "deals",
"name": "Deals",
"slug": "deals"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Introduction to Collections
Source: https://docs.zixflow.com/api-reference/collection/introduction
Collections are the backbone of Zixflow's data models, providing a structured framework for organizing information. Within Zixflow, you'll encounter two types of collections: system-defined and user-defined.
1. System Collections:
* Person: Represents individual entities.
* Company: Represents organizational entities.
* Deal: Represents transactions or agreements.
2. Custom Collections:
* Tailored to meet your specific requirements.
Users have the flexibility to create their own custom collections, allowing for a personalized data organization that aligns with their unique use cases. This dynamic combination of system and user-defined collections forms the foundation of Zixflow's versatile data management system.
# Filtering And Sorting
Source: https://docs.zixflow.com/api-reference/filtering-and-sorting
Some of our endpoints support specifying filter or sorting criteria within the request. These options can reduce the result set size or return data in a more suitable order.
## Filtering
Zixflow supports two distinct formats for filtering: **shorthand** and **verbose**.
* **Shorthand Format**: Ideal for quick equality checks.
* **Verbose Format**: Allows for more complex conditions and logical operators.
***
## Shorthand Filters
Shorthand filters are straightforward and concise. Here's an example:
##### Example: People named "John Doe" with the email "[john@doe.com](mailto:john@doe.com)"
```http theme={null}
POST /api/v1/collection-records/{collectionId}/query
Content-Type: application/json
{
"filter": {
"name": "John Doe",
"emails": "john@doe.com"
}
}
```
## Verbose Filters
Verbose filters enable more complex queries, such as combining multiple conditions or querying specific properties of an attribute. All shorthand filters can also be expressed in verbose syntax.
##### Example: Using verbose syntax for the same filter
```http theme={null}
POST /api/v1/collection-records/{collectionId}/query
Content-Type: application/json
{
"filter": {
"$and": [
{
"name": {
"$eq": "John Doe"
}
},
{
"emails": {
"$eq": "john@doe.com"
}
}
]
}
}
```
Here:
* A logical operator (`$and`) is used to combine conditions.
* Specific properties on the attributes (`name` and `emails`) are queried.
> Each attribute type has specific properties available, so the set of possible filters varies by attribute type.
***
## Comparison Operators
There are **fourteen comparison operators** in total. Below are some examples:
### `$eq` - Equality
Checks for equality and is supported by every attribute type. If using shorthand syntax, `$eq` is usually implied but can also be specified explicitly.
> Deals with an exact name
```json theme={null}
{
"name": {
"$eq": "John Doe"
}
}
```
### `$not_empty`
Filters results based on whether there is any value defined.
> Companies which have at least one domain
```json theme={null}
{
"name": {
"$not_empty": true
}
}
```
### `$in`
Checks if the record or entry has a value that is part of a set.
> Records where `record_id` is one of many values
```json theme={null}
{
"filter": {
"name": {
"$in": ["John Doe", "John Doe 2"]
}
}
}
```
***
## String Comparisons
For string-like properties or attributes, the following operators are available:
### `$contains`
Matches parts of a string, case-sensitively.
> Name contains John
```json theme={null}
{
"name": {
"$contains": "John"
}
}
```
### `$not_contains`
Do not matches parts of a string, case-sensitively.
> Name does not contains John
```json theme={null}
{
"name": {
"$not_contains": "John"
}
}
```
### `$starts_with` & `$ends_with`
Matches the beginning or the end of a string, respectively.
> People with phone numbers starting `+1`
```json theme={null}
{
"phoneNumbers": {
"$starts_with": "+1"
}
}
```
> Phone number starts with `+1` and ends with `0909`
```json theme={null}
{
"phoneNumbers": {
"$starts_with": "+1",
"$ends_with": "0909"
}
}
```
***
## Numeric or Date Comparisons
For sortable properties like numbers or dates, use the following operators:
* **`$lt`**: Less than
* **`$lte`**: Less than or equal
* **`$gte`**: Greater than or equal
* **`$gt`**: Greater than
> People with 4 or more rating
```json theme={null}
{
"rating": {
"$gte": 4
}
}
```
> people created in 2024
```json theme={null}
{
"createdAt": {
"$gte": "2024-01-01",
"$lte": "2024-12-31"
}
}
```
***
## Logical Operators
Combine multiple conditions using `$and`, `$or`, and `$not`.
### `$and` Operator
Specifies that all conditions must match. If using shorthand syntax with multiple attributes, `$and` is implied.
> People with name John Doe and email is [john@doe.com](mailto:john@doe.com)
```json theme={null}
{
"$and": [{ "name": "John Doe" }, { "emails": "john@doe.com" }]
}
```
### `$or` Operator
Specifies that at least one condition must match.
> List all peoples with name John Doe or Alice Doe
```json theme={null}
{
"$or": [{ "name": "John Doe" }, { "name": "Alice Doe" }]
}
```
### `$not` Operator
Matches all documents that **do not** meet the condition.
> People not with name John
```json theme={null}
{
"name": { "$not": "John" }
}
```
***
## Sorting
Sorting allows us to retrieve results in a specific order based on attribute values. Each sort operation must define a direction.
Sorting can be performed by an attribute apiKeyName. For instance, we can sort People first by their name and then by their email address:
```json theme={null}
{
"sort": [
{ "direction": "asc", "attribute": "name" },
{ "direction": "desc", "attribute": "emails" }
]
}
```
# Introduction
Source: https://docs.zixflow.com/api-reference/introduction
Welcome to Zixflow's API Documentation. We've designed this document to help developers and Zixflow users fully understand and integrate our API for a seamless and easy deployment. These APIs enable you to utilize most of your Zixflow account's panel features fully.
We have listed all the APIs, their required parameters, and their example requests and responses on the right for easy understanding.
Our APIs are based around REST architecture and use the basic [HTTP](https://www.tutorialspoint.com/http/http_status_codes) request methods. Our APIs accept [JSON](https://www.w3schools.com/whatis/whatis_json.asp) - encoded body requests and return data in the same form.
#### Getting Started
First, you need to register with zixflow and create an account. Click on this [link](https://app.zixflow.com/signup) to complete the sign-up process.
After the sign-up process is complete, follow the enlisted steps to create an API user:
Open your zixflow account and navigate to the right-hand panel.
1. Click On **Workspace Settings => Developer => API Keys => Create an API.**
2. On the pop-up menu, fill in the **API Name**.
3. Click on '**Add API**'. On successful completion, you should have a new API token generated.
4. Copy this token and use it to authenticate your further API calls.
#### Document and API Usage Guidelines
* Our APIs use the basic HTTP request codes: POST, GET, PATCH, DELETE.
* You can select the required response and request code language by selecting it from the drop-down menu on the top.
* You can import and test all our full API collection in the Postman app by clicking on the ' Run in Postman' button.
* Note: Any requests made using the valid API credentials will affect the real-time data in your zixflow account.
* Authorization: Bearer
* All the APIs are provided with their appropriate example requests and responses for successful and failed calls.
#### Rate Limits
* Our API has a rate limit of **150 requests per second**.
* If you exceed this limit, you will receive a "Too many requests, try again later!" error response.
* When you encounter this error, please wait and try again later to avoid further rate limiting.
#### Support
* All the required information on integration is in this document. Please read it thoroughly.
* For any integration and API-related support, you can feel free to drop us an email at [support@zixflow.com](mailto:support@zixflow.com).
* The zixflow terms of service are listed here: [Terms Of Service](https://zixflow.com/privacy-policy)
# Create A List Entry
Source: https://docs.zixflow.com/api-reference/list-entries/create
POST https://api.zixflow.com/api/v1/list-entries/{listId}
This endpoint creates a new entry in the specified list.
#### Description
This API endpoint allows the creation of a new entry within a specified list. The list entry is associated with a collection record, and additional data can be provided based on the attributes of the list.
#### Path
A unique identifier for the list where the new entry will be added.
#### Body
The ID of the collection record to be linked with the list entry.
Key-value pairs representing data for the list entry. Keys are defined by the attribute API key name, and values are based on the input type of the attribute.
#### Response
Indicates the success or failure of the list entry creation. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful creation of the list entry with the message "List entry created successfully!"
An object containing details of the created list entry. The structure of the data object is dynamic and depends on the attributes supported by the list:
```json 200-Success theme={null}
{
"status": true,
"message": "List entry created successfully!",
"data": {
"listId": "6551fdcad2d75edff28c9085",
"peopleId": "654566f0c57bc82e28e5bb40",
"companyId": null,
"dealId": null,
"recordId": null,
"owner": "63d0e0b2eaa35b73f3b2344e",
"_id": "6551fe0c92dc3d7c3aa20e12",
"createdAt": "2023-11-13T10:44:28.028Z",
"updatedAt": "2023-11-13T10:44:28.028Z",
"__v": 0
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Delete List Entry By ID
Source: https://docs.zixflow.com/api-reference/list-entries/delete
DELETE https://api.zixflow.com/api/v1/list-entries/{listId}/{entryId}
This endpoint allows the deletion of a selected list entry.
#### Description
This API endpoint enables the deletion of a specific list entry by providing the unique identifiers of both the list and the entry.
#### Path
A unique identifier for the list, allowing for precise referencing.
A unique identifier for the list entry to be deleted.
#### Response
Indicates the success or failure of the list entry deletion. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful deletion of the list entry with the message "List
Entry deleted successfully."
```json 200-Success theme={null}
{
"status": true,
"message": "List Entry deleted successfully"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update List Entry
Source: https://docs.zixflow.com/api-reference/list-entries/edit
PATCH https://api.zixflow.com/api/v1/list-entries/{listId}/{entryId}
This endpoint updates an existing entry in the specified list.
#### Description
This API endpoint allows the modification of an existing entry within a specified list. The list entry is identified by its unique ID, and data can be updated based on the attributes of the list.
#### Path
A unique identifier for the list containing the entry to be updated.
The ID of the list entry to be updated.
#### Body
Key-value pairs representing data to update in the list entry. Keys are defined by the attribute API key name, and values are based on the input type of the attribute.
#### Response
Indicates the success or failure of the list entry update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the list entry with the message "List entry updated successfully!"
```json 200-Success theme={null}
{
"status": true,
"message": "List entry updated successfully!"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List of List Entries
Source: https://docs.zixflow.com/api-reference/list-entries/get
POST https://api.zixflow.com/api/v1/list-entries/{listId}/query
This endpoint returns all entry data of selected list
#### Description
This API endpoint enables the retrieval of list entries. The structure of the list entries is dynamic and varies depending on the list and attributes, with no fixed response keys within the data.
#### Path
A unique identifier for the list.
#### Body
An object that will eventually allow users to define specific criteria for
filtering data. Currently, it is an empty object, indicating that no filtering
is applied at this time.
An array that will eventually enable users to specify sorting criteria for the
data. it is currently empty, implying that no sorting is applied in the
current context.
he number of entries to be returned, set to 10 in this instance. This
parameter restricts the response to a specific quantity of entries.
The starting point from which the entries are to be fetched within the entire
dataset. In this case, it is set to 0, indicating that retrieval should
commence from the beginning of the dataset.
#### Response
Indicates the success or failure of the response. In this case, true signifies
a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the entries with the message "List
Entries fetched successfully"
The structure of the list entries is dynamic and varies depending on the list
and attributes, with no fixed response keys within the data.
```json 200-Success theme={null}
{
"status": true,
"message": "List Entries fetched successfully",
"data": [
{
"_id": "65321b9a05ca4dc48ed8b231",
"owner": {
"_id": "63d0e0b2eaa35b73f3b23450",
"name": "Test User",
"avatar": "",
"email": "test@outlook.com"
},
"createdAt": "2023-10-20T06:18:02.403Z",
"apiPipeline3": null
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List Entry By ID
Source: https://docs.zixflow.com/api-reference/list-entries/get-by-id
GET https://api.zixflow.com/api/v1/list-entries/{listId}/{entryId}
This endpoint returns selected list entry data
#### Description
This API endpoint enables the retrieval of list entries. The structure of the list entries is dynamic and varies depending on the list and attributes, with no fixed response keys within the data.
#### Path
A unique identifier for the list.
A unique identifier for the list entry.
#### Response
Indicates the success or failure of the response. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the entries with the message "List Entries fetched successfully"
The structure of the list entries is dynamic and varies depending on the list and attributes, with no fixed response keys within the data.
```json 200-Success theme={null}
{
"status": true,
"message": "List Entries fetched successfully",
"data": {
"_id": "65321b9a05ca4dc48ed8b231",
"owner": {
"_id": "63d0e0b2eaa35b73f3b23450",
"name": "Test User",
"avatar": "",
"email": "test@outlook.com"
},
"createdAt": "2023-10-20T06:18:02.403Z",
"apiPipeline3": null
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List By ID
Source: https://docs.zixflow.com/api-reference/list/get-list
GET https://api.zixflow.com/api/v1/lists/{listId}
This endpoint returns a list
#### Path Params
A unique identifier for the list, allowing for precise referencing.
#### API Response Description:
Indicates the success or failure of the list retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the list with the message
"List fetched successfully."
Detailed information about the selected List:
A unique identifier for the list, allowing for precise referencing.
The human-readable name of the list, providing a clear identification.
A unique identifier for the list, useful for programmatic
identification and differentiation.
An emoji associated with the list for visual identification.
The unique identifier of the collection to which the list belongs,
facilitating collection identification.
Defines whether duplicate records can be added inside the list.
Details about the collection from which the list is created:
A unique identifier for the collection, allowing for precise referencing.
Specifies the type of the collection, such as "people," "company," or "deals."
The human-readable name of the collection, providing a clear identification.
A unique identifier for the collection, useful for programmatic
identification and differentiation.
```json 200-Success theme={null}
{
"status": true,
"message": "Collection fetched successfully",
"data": {
"_id": "653bbba76ecd501e8dcbbb5d",
"name": "Alumni",
"slug": "Alumni",
"emoji": "🚀",
"collectionId": "652e506bf781c59be3825523",
"duplicationAllowed": true,
"collection": {
"_id": "652e506bf781c59be3825523",
"collectionType": "people",
"name": "People",
"slug": "people"
}
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List of Lists
Source: https://docs.zixflow.com/api-reference/list/get-list-of-lists
GET https://api.zixflow.com/api/v1/lists
This endpoint returns all lists
#### API Response Description:
Upon querying your list endpoint, the API returns a structured response containing information about the available lists. Here's a breakdown of the response:
Indicates the success or failure of the list retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the list with the message
"List fetched successfully."
An array containing details of the available lists. For each list
in the data array:
A unique identifier for the list, allowing for precise referencing.
The human-readable name of the list, providing a clear
identification.
A unique identifier for the list, useful for programmatic
identification and differentiation.
An emoji associated with the list for visual identification.
The unique identifier of the collection to which the list belongs,
facilitating collection identification.
Defines whether duplicate records can be added inside the list.
Details about the collection from which the list is created:
A unique identifier for the collection, allowing for precise referencing.
Specifies the type of the collection, such as "people," "company," or "deals."
The human-readable name of the collection, providing a clear identification.
A unique identifier for the collection, useful for programmatic
identification and differentiation.
This structured response aims to offer clarity and ease of interpretation, enabling developers and users to effectively understand and utilize the information retrieved from the list endpoint.
```json 200-Success theme={null}
{
"status": true,
"message": "List fetched successfully.",
"data": [
{
"_id": "653bbba76ecd501e8dcbbb5d",
"name": "Alumni",
"slug": "Alumni",
"emoji": "🚀",
"collectionId": "652e506bf781c59be3825523",
"duplicationAllowed": true,
"collection": {
"_id": "652e506bf781c59be3825523",
"collectionType": "people",
"name": "People",
"slug": "people"
}
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Create A Collection Record
Source: https://docs.zixflow.com/api-reference/records/create
POST https://api.zixflow.com/api/v1/collection-records/{collectionId}
This endpoint creates a new collection record in the specified collection.
#### Description
This API endpoint enables the creation of a new collection record within a specified collection. The collection record is associated with the provided collection ID.
#### Path
A unique identifier for the collection where the record will be added.
#### Body
Key-value pairs representing data for the collection record. Keys are defined by the attribute API key name, and values depend on the input type of the attribute.
#### Response
Indicates the success or failure of the collection record creation. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful creation of the collection record with the message "Record created successfully!"
The unique identifier for the created collection record.
An object containing details of the created collection record, including key-value pairs representing the record's data.
```json 200-Success theme={null}
{
"status": true,
"message": "Record created successfully!",
"_id": "6551f5ff3c896c334f28d659",
"data": {
"_id": "6551f5ff3c896c334f28d659",
"": ""
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Delete Collection Record By ID
Source: https://docs.zixflow.com/api-reference/records/delete
DELETE https://api.zixflow.com/api/v1/collection-records/{collectionId}/{recordId}
This endpoint allows the deletion of a selected collection record.
#### Description
This API endpoint facilitates the removal of a specific collection record by providing the unique identifiers of both the collection and the record.
#### Path
A unique identifier for the collection, allowing for precise referencing.
A unique identifier for the collection record to be deleted.
#### Response
Indicates the success or failure of the collection record deletion. In this case, true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance, it confirms the successful deletion of the collection record with the message "Record Deleted Successfully!"
```json 200-Success theme={null}
{
"status": true,
"message": "Record Deleted Successfully!"
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Update A Collection Record
Source: https://docs.zixflow.com/api-reference/records/edit
PATCH https://api.zixflow.com/api/v1/collection-records/{collectionId}/{recordId}
This endpoint updates an existing collection record in the specified collection.
#### Description
This API endpoint allows the modification of an existing collection record within a specified collection. The record to be updated is identified by the provided collection ID and record ID.
#### Path
A unique identifier for the collection containing the record to be updated.
The unique identifier for the record to be updated within the specified collection.
#### Body
Key-value pairs representing updated data for the collection record. Keys are defined by the attribute API key name, and values depend on the input type of the attribute.
#### Response
Indicates the success or failure of the collection record update. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful update of the collection record with the message "Record updated successfully!"
```json 200-Success theme={null}
{
"status": true,
"message": "Record updated successfully!"
}
```
```json theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List of Collection Records
Source: https://docs.zixflow.com/api-reference/records/get
POST https://api.zixflow.com/api/v1/collection-records/{collectionId}/query
This endpoint returns all records data of selected collection
#### Description
This API endpoint enables the retrieval of collection records data. The structure of the collection record data is dynamic and varies depending on the collection, with no fixed response keys within the data.
#### Path
A unique identifier for the collection.
#### Body
An object that will eventually allow users to define specific criteria for
filtering data. Currently, it is an empty object, indicating that no filtering
is applied at this time.
An array that will eventually enable users to specify sorting criteria for the
data. It is currently empty, implying that no sorting is applied in the
current context.
he number of records to be returned, set to 10 in this instance. This
parameter restricts the response to a specific quantity of records.
The starting point from which the records are to be fetched within the entire
dataset. In this case, it is set to 0, indicating that retrieval should
commence from the beginning of the dataset.
#### Response
Indicates the success or failure of the response. In this case, true signifies
a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the record with the message "success"
The structure of the collection record data is dynamic and varies depending on
the collection, with no fixed response keys within the data.
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": [
{
"name": "Test Record",
"firstName": "Test",
"lastName": "Record",
"emails": [
"user@zixflow.com"
],
"phoneNumbers": [
"592965223",
"9176639301"
],
"company": {
"_id": "64b60314e7cca0f47d780544",
"name": "Zixflow",
"avatar": ""
},
"jobTitle": "Developer",
"description": "",
"address": "Mumbai, India",
"emailValidation": {
"_id": "64ad815b273e66dae1afe124",
"color": "#dbeddb",
"name": "Deliverable",
"isArchived": false,
"order": 2
},
"facebook": "",
"instagram": "",
"linkedin": "",
"twitter": "",
"lastInteraction": "2023-11-06T02:34:45.405Z",
"timezone": {
"_id": "64ad82ba273e66dae1b04689",
"color": "#e3e2e0",
"name": "Pacific/Niue",
"isArchived": false,
"order": 1
},
"source": {
"_id": "64ad815b273e66dae1afe141",
"color": "#fadec9",
"name": "Manually created",
"isArchived": false,
"order": 1
},
"owner": {
"_id": "64411b92cc16b5b0b858cc5f",
"name": "Agent Account",
"avatar": "",
"email": "dummy@outlook.com"
},
"share": [ ],
"createdAt": "2023-07-14T07:55:43.517Z",
"apiCustomAttribute": "Custom data",
"apiPipeline": {
"_id": "6520b88e4f7b6dea01cf9b7b",
"name": "Stage 2",
"color": "#02b55c",
"order": 2,
"isArchived": false,
"timeInStatus": null,
"celebrationEnabled": false,
"statusType": "normal"
},
"apiPipeline1": {
"_id": "64ae5870a3325d97495572f2",
"name": "Stage 1",
"color": "#5e6ad2",
"isArchived": false,
"timeInStatus": 0,
"celebrationEnabled": false,
"statusType": "normal"
},
"apiNumber1": 20,
"apiNumber2": 30
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get Collection Record By ID
Source: https://docs.zixflow.com/api-reference/records/get-by-id
GET https://api.zixflow.com/api/v1/collection-records/{collectionId}/{recordId}
This endpoint returns selected collection record data
#### Description
This API endpoint enables the retrieval of selected collection record data. The structure of the collection record data is dynamic and varies depending on the collection, with no fixed response keys within the data.
#### Path
A unique identifier for the collection.
A unique identifier for the collection.
#### Response
Indicates the success or failure of the response. In this case, true
signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the record with the message "success"
The structure of the collection record data is dynamic and varies depending on the collection, with no fixed response keys within the data.
```json 200-Success theme={null}
{
"status": true,
"message": "success",
"data": {
"name": "Test Record",
"firstName": "Test",
"lastName": "Record",
"emails": [
"user@zixflow.com"
],
"phoneNumbers": [
"592965223",
"9176639301"
],
"company": {
"_id": "64b60314e7cca0f47d780544",
"name": "Zixflow",
"avatar": ""
},
"jobTitle": "Developer",
"description": "",
"address": "Mumbai, India",
"emailValidation": {
"_id": "64ad815b273e66dae1afe124",
"color": "#dbeddb",
"name": "Deliverable",
"isArchived": false,
"order": 2
},
"facebook": "",
"instagram": "",
"linkedin": "",
"twitter": "",
"lastInteraction": "2023-11-06T02:34:45.405Z",
"timezone": {
"_id": "64ad82ba273e66dae1b04689",
"color": "#e3e2e0",
"name": "Pacific/Niue",
"isArchived": false,
"order": 1
},
"source": {
"_id": "64ad815b273e66dae1afe141",
"color": "#fadec9",
"name": "Manually created",
"isArchived": false,
"order": 1
},
"owner": {
"_id": "64411b92cc16b5b0b858cc5f",
"name": "Agent Account",
"avatar": "",
"email": "dummy@outlook.com"
},
"share": [ ],
"createdAt": "2023-07-14T07:55:43.517Z",
"apiCustomAttribute": "Custom data",
"apiPipeline": {
"_id": "6520b88e4f7b6dea01cf9b7b",
"name": "Stage 2",
"color": "#02b55c",
"order": 2,
"isArchived": false,
"timeInStatus": null,
"celebrationEnabled": false,
"statusType": "normal"
},
"apiPipeline1": {
"_id": "64ae5870a3325d97495572f2",
"name": "Stage 1",
"color": "#5e6ad2",
"isArchived": false,
"timeInStatus": 0,
"celebrationEnabled": false,
"statusType": "normal"
},
"apiNumber1": 20,
"apiNumber2": 30
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get Workspace Member By ID
Source: https://docs.zixflow.com/api-reference/workspace-members/get
GET https://api.zixflow.com/api/v1/workspace-members/{memberId}
This endpoint returns selected worksapce member data
#### Path Params
The unique identifier of the member, allowing for precise referencing.
#### API Response Description:
Indicates the success or failure of the workspace member retrieval. In this
case, true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the workspace member with the message
"Workspace member fetched successfully!"
Detailed information about the selected workspace member:
The unique identifier of the workspace to which the member belongs.
The unique identifier of the member, allowing for precise referencing.
The URL of the member's profile picture, if available.
The full name of the member, providing a clear identification.
The email address of the member.
The phone number of the member.
The role of the member in the workspace, such as OWNER, ADMIN, or MEMBER.
The status of the member, such as INVITED, SUSPENDED, or ACTIVE.
The timezone of the member.
Indicates whether the member is deactivated or suspended (true/false).
```json 200-Success theme={null}
{
"status": true,
"message": "Workspace member fetched successfully!",
"data": {
"workspaceId": "652e5068c33fe13b6ba00fd7",
"userId": "652e5068c33fe1cd1da00fda",
"avatar": "",
"fullName": "User Name",
"email": "user@zixflow.com",
"phone": "9190909090",
"userType": "OWNER",
"userStatus": "ACTIVE",
"timezone": "Asia/Kolkata",
"deactivated": false
}
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# Get List of Workspace Members
Source: https://docs.zixflow.com/api-reference/workspace-members/get-list-of-members
GET https://api.zixflow.com/api/v1/workspace-members
This endpoint returns all worksapce member details
#### API Response Description:
Indicates the success or failure of the workspace members retrieval. In this case,
true signifies a successful operation.
Provides a human-readable message accompanying the response. In this instance,
it confirms the successful retrieval of the workspace members with the message
"Workspace members fetched successfully!"
An array containing details of the available workspace members. For each member
in the data array:
The unique identifier of the workspace to which the member belongs.
The unique identifier of the member, allowing for precise referencing.
The URL of the member's profile picture, if available.
The full name of the member, providing a clear identification.
The email address of the member.
The phone number of the member.
The role of the member in the workspace, such as OWNER, ADMIN, or MEMBER.
The status of the member, such as INVITED, SUSPENDED, or ACTIVE.
The timezone of the member.
Indicates whether the member is deactivated or suspended (true/false).
```json 200-Success theme={null}
{
"status": true,
"message": "Workspace members fetched successfully!",
"data": [
{
"workspaceId": "652e5068c33fe13b6ba00fd7",
"userId": "652e5068c33fe1cd1da00fda",
"avatar": "",
"fullName": "User Name",
"email": "user@zixflow.com",
"phone": "9190909090",
"userType": "OWNER",
"userStatus": "ACTIVE",
"timezone": "Asia/Kolkata",
"deactivated": false
}
]
}
```
```json 401-Unauthorised theme={null}
{
"status": false,
"message": "No token provided"
}
```
# API Authentication
Source: https://docs.zixflow.com/api-reference/zixflow-ai/authentication
Authenticate Zixflow AI API requests using x-api-key and x-workspace-id
Zixflow AI APIs use **header-based authentication**. You must include these headers in every request:
```txt theme={null}
x-api-key:
x-workspace-id:
```
## Required Headers
* **`x-api-key`**: Your API key for the workspace.
* **`x-workspace-id`**: Your workspace ID.
## Where to find your API key and workspace ID
You can create/manage your API key and copy your workspace ID from the Zixflow dashboard:
* **Dashboard path**: `Settings > Developer > API Key`
* **Direct link**: `https://ai.zixflow.com/settings/workspace/developer`
## Example request
```bash theme={null}
curl -X POST "https://api-ai.zixflow.com/api/ingest/sms/v1/message/send" \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-H "x-workspace-id: " \
-d '{ "recipient": "919876543210", "message": "Hello", "sender_id": "ZIXFLW", "route": "transactional" }'
```
## Screenshot
Below is the screen where you can copy both **API Key** and **Workspace ID**:

# Send Email Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/email/send-message
POST https://api-ai.zixflow.com/api/ingest/email/v1/message/send
Send an email message to a recipient
#### Description
This endpoint allows you to send an email message to a recipient. You can include attachments, track opens and clicks, and customize the sender information.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The email address of the recipient.
The email address of the sender. If not provided, the default sender email will be used.
The display name of the sender.
The subject line of the email.
The HTML or plain text content of the email message.
Optional pre-header text that appears in the email preview.
The email address where replies should be sent.
The display name for the reply-to address.
Array of attachment URLs to include with the email.
Whether to track email opens. Default: false
Whether to track link clicks in the email. Default: false
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "Email request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid data provided"
}
```
```json 401-Unauthorised theme={null}
{
"success": false,
"message": "Unauthorised"
}
```
# Send Email Template
Source: https://docs.zixflow.com/api-reference/zixflow-ai/email/send-template
POST https://api-ai.zixflow.com/api/ingest/email/v1/template/send
Send an email using a predefined template
#### Description
This endpoint allows you to send an email using a predefined template. Templates can include variables that are replaced with actual values when the email is sent.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The email address of the recipient.
The name of the email template to use.
Key-value pairs for template variables. Keys should match the variable names in the template, and values will replace the placeholders.
The email address of the sender. If not provided, the default sender email will be used.
The display name of the sender.
The subject line of the email. If not provided, the template's default subject will be used.
Optional pre-header text that appears in the email preview.
The email address where replies should be sent.
The display name for the reply-to address.
Array of attachment URLs to include with the email.
Whether to track email opens. Default: false
Whether to track link clicks in the email. Default: false
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "Email request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid template or missing variables"
}
```
```json 401-Unauthorised theme={null}
{
"success": false,
"message": "Unauthorised"
}
```
# Error Codes
Source: https://docs.zixflow.com/api-reference/zixflow-ai/error-codes
Reference for error codes returned by the Zixflow AI API
API responses may include a numeric `code`, a `status` string (UPPER\_SNAKE\_CASE), and a human-readable `description`. Use this page to interpret errors and handle them in your application.
Error responses typically include `code`, `status`, and `description`. The `status` field is stable for programmatic handling; the `description` may be updated for clarity.
## General (90001–90099)
| Code | Status | Description |
| ----- | ----------------------- | ------------------------------------------------------------------------------------------- |
| 90001 | PROCESSING\_ERROR | An error occurred while processing the request. |
| 90002 | FAILED\_GENERATE\_ID | A system error occurred while creating a request. Please try again. |
| 90003 | FAILED\_CREATE\_REQUEST | An error occurred while creating the request. Please contact support if the issue persists. |
## Validation (90100–90199)
| Code | Status | Description |
| ----- | ------------------------------ | ------------------------------------------------------------- |
| 90101 | INVALID\_WORKSPACE\_ID\_FORMAT | The provided workspace ID does not match the required format. |
| 90102 | WORKSPACE\_NOT\_FOUND | The specified workspace was not found. |
| 90103 | INVALID\_RECIPIENT\_NUMBER | The recipient number is invalid. |
| 90104 | INVALID\_RECIPIENT\_EMAIL | The recipient email address is invalid. |
| 90105 | INVALID\_CHANNEL | The channel is invalid or not supported. |
## System (90200–90299)
| Code | Status | Description |
| ----- | ------------------------ | ------------------------------------------------------------- |
| 90201 | DATABASE\_NOT\_AVAILABLE | The database service is currently unavailable or unreachable. |
| 90202 | COUNTRY\_NIL | Country information not found for the workspace. |
| 90203 | CACHE\_NOT\_AVAILABLE | The cache service is currently unavailable or unreachable. |
## Pricing (90400–90499)
| Code | Status | Description |
| ----- | ----------------------- | ------------------------------------------------------- |
| 90401 | PRICE\_NOT\_FOUND | The price is not configured for the route or country. |
| 90402 | WALLET\_NOT\_FOUND | The wallet was not found for the workspace. |
| 90403 | FAILED\_PROCESS\_WALLET | Failed to process wallet deduction. |
| 90404 | LOW\_WALLET\_BALANCE | Insufficient wallet balance to process the transaction. |
## WhatsApp (90500–90699)
### General & template
| Code | Status | Description |
| ----- | --------------------------------- | -------------------------------------------------------------------------------------------- |
| 90501 | INVALID\_WHATSAPP\_PHONE\_ID | The provided WhatsApp phone ID is invalid or does not match the required format. |
| 90502 | INVALID\_WHATSAPP\_TEMPLATE\_NAME | The provided WhatsApp template is invalid or does not match the required format. |
| 90503 | WHATSAPP\_CATEGORY\_NOT\_FOUND | The provided WhatsApp message category is invalid or does not match the required format. |
| 90504 | DAILY\_LIMIT\_EXCEEDED | The daily limit has been exceeded for the WhatsApp message. |
| 90505 | UNSUPPORTED\_REQUEST\_TYPE | The provided WhatsApp request type is invalid or does not match the required format. |
| 90506 | UNSUPPORTED\_INTERACTIVE\_TYPE | The provided WhatsApp interactive type is invalid or does not match the required format. |
| 90507 | SERVICE\_WINDOW\_CLOSED | Service window is closed. Service messages can only be sent when the service window is open. |
### Message payload & body
| Code | Status | Description |
| ----- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| 90520 | INVALID\_MESSAGE\_PAYLOAD | The provided WhatsApp message payload is invalid or does not match the required format. |
| 90521 | WHATSAPP\_MESSAGE\_BODY\_REQUIRED | The provided WhatsApp message body is required but not provided. |
| 90522 | WHATSAPP\_MESSAGE\_BODY\_TOO\_LONG | The provided WhatsApp message body is too long and exceeds the maximum length of 4096 characters. |
| 90523 | WHATSAPP\_IMAGE\_ID\_OR\_LINK\_REQUIRED | The WhatsApp image payload must include either an 'id' or a 'link'. |
| 90524 | WHATSAPP\_AUDIO\_ID\_OR\_LINK\_REQUIRED | The WhatsApp audio payload must include either an 'id' or a 'link'. |
| 90525 | WHATSAPP\_DOCUMENT\_ID\_OR\_LINK\_REQUIRED | The WhatsApp document payload must include either an 'id' or a 'link'. |
| 90526 | WHATSAPP\_VIDEO\_ID\_OR\_LINK\_REQUIRED | The WhatsApp video payload must include either an 'id' or a 'link'. |
| 90527 | WHATSAPP\_LOCATION\_LATITUDE\_AND\_LONGITUDE\_REQUIRED | The WhatsApp location payload must include both 'latitude' and 'longitude'. |
| 90528 | WHATSAPP\_CONTACTS\_CONTACTS\_ARRAY\_CANNOT\_BE\_EMPTY | The WhatsApp contacts payload must include at least one contact. |
### Interactive messages
| Code | Status | Description |
| ----- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| 90530 | INVALID\_INTERACTIVE\_PAYLOAD | The interactive message payload is invalid. Please ensure it matches the required WhatsApp Business API format. |
| 90531 | INVALID\_INTERACTIVE\_TYPE | The interactive message type is invalid. Please provide a supported interactive type (button, list, carousel, etc.). |
| 90532 | WHATSAPP\_INTERACTIVE\_BODY\_REQUIRED | The interactive message body text is required. Please provide the message body content. |
| 90533 | WHATSAPP\_INTERACTIVE\_ACTION\_REQUIRED | The interactive message action is required. Please provide the action configuration for your interactive message. |
| 90534 | WHATSAPP\_INTERACTIVE\_ACTION\_BUTTON\_REQUIRED | The interactive message action button is required. Please provide at least one button in the action configuration. |
| 90535 | WHATSAPP\_INTERACTIVE\_ACTION\_SECTIONS\_REQUIRED | The interactive message action sections are required. Please provide at least one section for list-type interactive messages. |
| 90536 | WHATSAPP\_INTERACTIVE\_ACTION\_SECTIONS\_TITLE\_REQUIRED | The section title is required. Please provide a title for each section in your interactive message. |
| 90537 | WHATSAPP\_INTERACTIVE\_ACTION\_SECTIONS\_ROWS\_CANNOT\_BE\_EMPTY | The section rows cannot be empty. Please provide at least one row item in each section. |
| 90538 | WHATSAPP\_INTERACTIVE\_ACTION\_SECTIONS\_ROWS\_ID\_REQUIRED | The row ID is required. Please provide a unique identifier for each row in the section. |
| 90539 | WHATSAPP\_INTERACTIVE\_ACTION\_SECTIONS\_ROWS\_TITLE\_REQUIRED | The row title is required. Please provide a title for each row in the section. |
| 90540 | WHATSAPP\_INTERACTIVE\_ACTION\_BUTTONS\_MAX\_3\_BUTTONS | Interactive message buttons are limited to a maximum of 3 buttons. Please reduce the number of buttons in your request. |
| 90541 | WHATSAPP\_INTERACTIVE\_ACTION\_BUTTONS\_TYPE\_MUST\_BE\_REPLY | Button type must be 'reply'. Please ensure all buttons use the 'reply' type for interactive messages. |
| 90542 | WHATSAPP\_INTERACTIVE\_ACTION\_BUTTONS\_REPLY\_ID\_REQUIRED | The button reply ID is required. Please provide a unique identifier for each button reply action. |
| 90543 | WHATSAPP\_INTERACTIVE\_ACTION\_BUTTONS\_REPLY\_TITLE\_REQUIRED | The button reply title is required. Please provide a title for each button in your interactive message. |
| 90544 | WHATSAPP\_INTERACTIVE\_ACTION\_NAME\_MUST\_BE\_CTA\_URL | The action name must be 'cta\_url' for call-to-action URL buttons. Please update the action name accordingly. |
| 90545 | WHATSAPP\_INTERACTIVE\_ACTION\_PARAMETERS\_REQUIRED | The action parameters are required. Please provide the necessary parameters for your interactive message action. |
| 90546 | WHATSAPP\_INTERACTIVE\_ACTION\_PARAMETERS\_URL\_REQUIRED | The action parameter URL is required. Please provide a valid URL in the action parameters for the call-to-action button. |
| 90547 | WHATSAPP\_INTERACTIVE\_ACTION\_NAME\_MUST\_BE\_LOCATION\_REQUEST\_MESSAGE | The action name must be 'location\_request\_message' for location request interactive messages. Please update the action name accordingly. |
| 90548 | WHATSAPP\_INTERACTIVE\_ACTION\_PARAMETERS\_COUNTRY\_REQUIRED | The country parameter is required. Please provide a valid country code in the action parameters for location request messages. |
| 90549 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_REQUIRED | The carousel cards are required. Please provide at least one card in your product carousel interactive message. |
| 90550 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_MAX\_10\_CARDS | Product carousel is limited to a maximum of 10 cards. Please reduce the number of cards in your request. |
| 90551 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_TYPE\_REQUIRED | The card type is required. Please specify the type for each card in your product carousel. |
| 90552 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_BODY\_TEXT\_REQUIRED | The card body text is required. Please provide body text for each card in your product carousel. |
| 90553 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_ACTION\_NAME\_REQUIRED | The card action name is required. Please provide an action name for each card in your product carousel. |
| 90554 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_ACTION\_PRODUCT\_RETAILER\_ID\_REQUIRED | The product retailer ID is required. Please provide a valid retailer ID in the card action for product carousel messages. |
| 90555 | WHATSAPP\_INTERACTIVE\_ACTION\_CARDS\_ACTION\_CATALOG\_ID\_REQUIRED | The catalog ID is required. Please provide a valid catalog ID in the card action for product carousel messages. |
## RCS (90700–90899)
| Code | Status | Description |
| ----- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 90701 | INVALID\_RCS\_BOT\_ID | The RCS bot ID is invalid or does not match the required format. Please provide a valid bot ID for your RCS configuration. |
| 90702 | RCS\_BOT\_NOT\_FOUND | The RCS bot was not found for your workspace. Please ensure the bot is properly configured in your workspace settings. |
| 90703 | RCS\_BOT\_NOT\_SUPPORTED | The RCS bot is not supported for the recipient's country. Please use a bot that supports the target country or select a different messaging channel. |
| 90704 | UNSUPPORTED\_RCS\_VENDOR | The RCS vendor is not supported. Please use a supported RCS vendor or contact support for assistance. |
| 90705 | BOT\_ID\_OR\_BOT\_SECRET\_MISSING | The bot ID or bot secret is missing. Please provide both bot ID and bot secret in your RCS configuration. |
| 90706 | FAILED\_TO\_PROCESS\_TOKEN | Failed to process the authentication token. Please verify your bot credentials and try again. If the issue persists, contact support. |
| 90707 | FAILED\_TO\_CHECK\_RCS\_CAPABILITY | Failed to check the RCS capability for the recipient. Please try again later or contact support if the issue persists. |
| 90708 | RCS\_CAPABILITY\_NOT\_SUPPORTED | RCS messaging is not supported for this recipient. The recipient's device or carrier may not support RCS. Please use an alternative messaging channel. |
| 90709 | UNSUPPORTED\_MESSAGE\_TYPE | The message type is invalid or not supported. Please provide a supported RCS message type (text, card, carousel, etc.). |
| 90710 | INVALID\_RCS\_MESSAGE\_PAYLOAD | The message payload is invalid or does not match the required RCS format. Please ensure your payload conforms to the RCS Business Messaging API specification. |
| 90711 | INVALID\_RCS\_TEMPLATE\_NAME | The RCS template name is invalid or does not match the required format. Please provide a valid template name that exists in your RCS template library. |
| 90712 | FAILED\_RCS\_MESSAGE\_PROCESSING | Failed to process the RCS message. Please verify your message payload and try again. If the issue persists, contact support. |
## SMS (90900–91999)
SMS errors are grouped by category. Ranges: `90900–90999` API-facing shared codes; `91000–91064` validation, DLT, and template/content; `91400–91999` delivery, operator, handset, user, and provider. Delivery reports and vendor responses may map to these internal codes.
### SMS general (90900–91099)
Code `90903` is reserved as a legacy slot and documented below as `SMS_CODE_RESERVED`.
| Code | Status | Description |
| ----- | ---------------------------------- | ------------------------------------------------------------------------------------------- |
| 90901 | INVALID\_SENDER\_ID\_OR\_ROUTE | The sender ID or route is invalid or does not match the required format. |
| 90902 | RECIPIENT\_COUNTRY\_NOT\_SUPPORTED | The recipient country is not supported by the sender ID. |
| 90903 | SMS\_CODE\_RESERVED | Reserved legacy slot. Use `91002 DLT_SCRUBBING_ERROR` for current API validation responses. |
| 90904 | INVALID\_SMS\_CONTENT | The SMS content is invalid or does not match the required format. |
| 91001 | INVALID\_PHONE\_NUMBER | The recipient phone number is invalid. |
| 91002 | DLT\_SCRUBBING\_ERROR | The DLT template ID or entity ID is required for SMS messages in India. |
| 91003 | FAILED\_PARSE\_CONTENT | Failed to parse SMS content. |
| 91004 | FAILED\_GENERATE\_CONFIG | Failed to generate SMS configuration JSON. |
| 91005 | CONTENT\_PARSING\_ERROR | Error parsing SMS content (unicode/encoding). |
### SMS DLT (91006–91056)
DLT (Do Not Disturb / regulatory) codes apply to SMS in India. Vendor delivery reports may map to these internal codes.
| Code | Status | Description |
| ----- | ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| 91006 | SENDER\_BLOCKED\_BY\_DLT | Sender blocked by DLT provider. |
| 91007 | TELEMARKETER\_BLOCKED\_BY\_DLT | Telemarketer blocked by DLT scrubbing. |
| 91008 | ENTITY\_BLOCKED\_BY\_DLT | Principal entity ID blocked by DLT provider. |
| 91009 | TEMPLATE\_BLOCKED\_BY\_DLT | Template or content blocked by DLT provider. |
| 91010 | ENTITY\_NOT\_REGISTERED | No entry of entity on the platform. |
| 91011 | ENTITY\_INACTIVE | Principal entity is inactive or disabled. |
| 91012 | ENTITY\_BLACKLISTED | Principal entity has been blocklisted. |
| 91013 | INVALID\_ENTITY\_ID | Principal entity ID is invalid or wrong format. |
| 91014 | ENTITY\_ID\_NOT\_ALLOWED\_FOR\_TM | Principal entity is not allowed for the telemarketer. |
| 91015 | TELEMARKETER\_NOT\_REGISTERED | No entry of TMID on the platform. |
| 91016 | TELEMARKETER\_INACTIVE | Telemarketer is inactive on the platform. |
| 91017 | TELEMARKETER\_BLACKLISTED | Telemarketer is blocklisted on all platforms. |
| 91018 | HEADER\_INACTIVE | Header is inactive or disabled. |
| 91019 | HEADER\_BLACKLISTED | Header has been blocklisted. |
| 91020 | PEID\_NOT\_MATCHED\_WITH\_HEADER | PEID does not match the header/sender ID information. |
| 91021 | HEADER\_IN\_FREEPOOL | Header in free pool. |
| 91022 | TEMPLATE\_INACTIVE | Template is inactive or disabled. |
| 91023 | TEMPLATE\_BLACKLISTED | Template has been blocklisted. |
| 91024 | TEMPLATE\_NOT\_MATCHED | Template does not match the provided content or message. |
| 91025 | HEADER\_NOT\_REGISTERED\_FOR\_TEMPLATE | Header is not registered or associated with the requested template. |
| 91026 | TEMPLATE\_VARIABLE\_EXCEEDED\_MAX\_LENGTH | Template variable exceeds the maximum allowed length. |
| 91027 | ERROR\_IDENTIFYING\_TEMPLATE | Error in identifying the template. |
| 91028 | INVALID\_TEMPLATE\_ID | Template ID is invalid or does not exist. |
| 91029 | TEMPLATE\_NOT\_REGISTERED\_TO\_ENTITY | Template is not registered or associated with the specified principal entity ID. |
| 91030 | PROMOTIONAL\_TEMPLATE\_USED\_ON\_OTHERS\_HEADER | Promotional template used on other/transaction header. |
| 91031 | INVALID\_TEMPLATE\_TYPE | Invalid template type. |
| 91032 | PREFERENCE\_NOT\_MATCHED | Preference does not match the specified criteria on the end user number. |
| 91033 | INVALID\_PROMO\_TIME | Promotional time is invalid or outside the allowed range (9 PM to 10 AM). |
| 91034 | SE\_CATEGORY\_BLOCK | Service entity category has been blocked. |
| 91035 | CONSENT\_FAILED | General error code for consent. |
| 91036 | SCRUBBING\_FAILED | General error code in case of any exceptions. |
| 91037 | TLV\_PEID\_NOT\_FOUND | Principal Entity ID within the TLV structure was not found. |
| 91038 | TLV\_TMPID\_NOT\_FOUND | Template ID within the TLV structure was not found. |
| 91039 | CONTENT\_MULTIPART\_INCOMPLETE\_BY\_DLT | DLT has not received long SMS parts or if any part is missing. |
| 91040 | HEADER\_SUSPENDED\_DUE\_TO\_VALIDITY | Header suspended due to validity issues concerning DLT compliance. |
| 91041 | HEADER\_SUSPENDED\_DUE\_TO\_USAGE | Header suspended due to excessive usage that violates DLT regulations. |
| 91042 | HEADER\_SUSPENDED\_DUE\_TO\_CUSTOMER | Header suspended due to issues related to the customer's DLT account. |
| 91043 | TEMPLATE\_SUSPENDED\_DUE\_TO\_VALIDITY | Template suspended due to validity problems concerning DLT compliance. |
| 91044 | TEMPLATE\_SUSPENDED\_DUE\_TO\_USAGE | Template suspended due to excessive usage that violates DLT regulations. |
| 91045 | TEMPLATE\_SUSPENDED\_DUE\_TO\_CUSTOMER | Template suspended due to issues related to the customer's DLT compliance status. |
| 91046 | CTA\_NOT\_WHITELISTED | URL in SMS template has not been safelisted on the DLT platform. |
| 91047 | PE\_TM\_HASH\_NOT\_RECEIVED | PE-TM hash was not received or an empty value was provided. |
| 91048 | PE\_TM\_HASH\_NOT\_REGISTERED | Submitted PE-TM hash does not match any registered hash for the specified PE. |
| 91049 | PE\_TM\_HASH\_INACTIVE | PE-TM hash status is invalid because one of the telemarketers in the chain is inactive. |
| 91050 | PE\_TM\_HASH\_BLACKLISTED | PE-TM hash status is blocklisted. |
| 91051 | PE\_TM\_HASH\_SUSPENDED | PE-TM hash status is suspended by the customer. |
| 91052 | BLOCKED\_BY\_DLT | Message blocked by DLT regulations. |
| 91053 | DLT\_ENTITY\_NOT\_FOUND | DLT entity not found. |
| 91054 | DLT\_TEMPLATE\_NOT\_FOUND | DLT template not found. |
| 91055 | DLT\_HEADER\_NOT\_FOUND | DLT header not found. |
| 91056 | DLT\_SCRUBBING\_TIMEOUT | Timeout while performing DLT scrubbing. |
### SMS template/content (91057–91064)
| Code | Status | Description |
| ----- | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
| 91057 | MSG\_TEXT\_TOO\_LONG | Message text is too long. |
| 91058 | INVALID\_PDU\_FORMAT | Invalid PDU format. |
| 91059 | REJECTED\_INVALID\_UDH | Message formatted incorrectly due to invalid ESM class parameter or inaccurate amount of characters. |
| 91060 | DUPLICATE\_MESSAGE | Duplicate message ID specified in the submit request. |
| 91061 | INVALID\_DESTINATION\_ADDRESS | Invalid destination address. |
| 91062 | INVALID\_SOURCE\_ADDRESS | Invalid source address value. |
| 91063 | INVALID\_REQUEST\_TYPE | The request type was invalid. |
| 91064 | INVALID\_REQUEST\_DESTINATION | Invalid request - destination address country code is not recognized. |
### SMS delivery (91400–91499)
| Code | Status | Description |
| ----- | ---------------------------------- | -------------------------------------------------------------------------------- |
| 91401 | DELIVERY\_FAILED | SMS delivery failed. |
| 91402 | SUBSCRIBER\_UNAVAILABLE | Subscriber unavailable or unreachable. |
| 91403 | MESSAGE\_EXPIRED | Message expired before delivery. |
| 91404 | REJECTED\_ROUTE\_NOT\_AVAILABLE | Network error during delivery. |
| 91405 | UNDELIVERABLE\_REJECTED\_OPERATOR | Message blocked by mobile operator. |
| 91406 | CONTENT\_BLOCKED | Message content blocked. |
| 91407 | REJECTED\_DESTINATION\_BLOCKLISTED | Destination number blocked. |
| 91408 | MESSAGE\_CANCELED | Message was canceled before delivery. |
| 91409 | DESTINATION\_FLOODING | Message rejected due to flooding filter - too many messages to same destination. |
| 91410 | DESTINATION\_TXT\_FLOODING | Message rejected due to number of identical messages sent to a single number. |
| 91411 | MESSAGE\_SUBMITTED\_NOT\_ACKED | Message submitted to but not acknowledged by the mobile operator. |
| 91412 | DEST\_OVERLOADED | Destination overloaded. |
### SMS operator (91500–91599)
A few operator statuses are vendor-provided legacy values and may not strictly follow modern `SNAKE_CASE` formatting.
| Code | Status | Description |
| ----- | ------------------------------------ | -------------------------------------------------------------------------------- |
| 91501 | BEARER\_SERVICE\_NOT\_PROVISIONED | Requested bearer service is not available or activated. |
| 91502 | SS\_INCOMPATIBILITY | SS is not compatible. |
| 91503 | RESOURCE\_LIMITATION | Network congestion - SS7 network congestion error on the MAP protocol level. |
| 91504 | UNKNOWN\_ALPHABET | Unknown alphabet. |
| 91505 | OPERATOR\_NOT\_FOUND | Mobile operator not found for the destination address. |
| 91506 | SOURCE\_ADDRESS\_IS\_BLOCKED | Source address is blocked or is not provisioned. |
| 91507 | TF\_NUMBER\_NOT\_VERIFIED | Toll-free number has either gone over the limit or is not verified. |
| 91508 | INVALID\_RESPONSE\_RECEIVED | An invalid response has been received. |
| 91509 | SC\_BLOCKED\_FOR\_END\_USER | Mobile operator blocked the end user from this short code. |
| 91510 | CONTENT\_BLOCKED\_BY\_OPERATOR | Content blocked by the mobile operator for this end user. |
| 91511 | SC\_NOT\_PROVISIONED | Short code not provisioned with mobile operator. |
| 91512 | SC\_EXPIRED | Short code expired with mobile operator. |
| 91513 | SC\_BLOCKED | Short code blocked by mobile operator. |
| 91514 | DESTINATION\_BLOCKED\_BY\_OPERATOR | Mobile operator is blocking the phone number from receiving messages. |
| 91515 | DESTINATION\_NOT\_SMS\_PROVISIONED | Destination address not provisioned for SMS. |
| 91516 | DEST\_ADDRESS\_SUSPENDED | Destination address suspended by the mobile operator. |
| 91517 | CAMPAIGN\_ID\_REJECTED | Program ID or Campaign ID rejected by the mobile operator. |
| 91518 | BLOCKED\_NEW\_SUBSCRIPTIONS\_FOR\_SC | New subscriptions for this short code are blocked by the mobile operator. |
| 91519 | ACCOUNT\_NOT\_PROVISIONED\_FOR\_SMS | End user's number belongs to a network for which the account is not provisioned. |
| 91520 | MOBILE\_OPERATOR\_NETWORK\_ERROR | Mobile operator network error. |
| 91521 | SMS\_REJECTED\_BY\_MOBILE\_OPERATOR | SMS rejected by the mobile operator for attempted destination address. |
| 91522 | UNKNOWN\_MOBILE\_OPERATOR | Message failed due to an unknown mobile operator error. |
| 91523 | FAILED\_MESSAGE\_DELIVERY | Failed message delivery. |
| 91524 | TIME\_OUT | Time out. |
| 91525 | NOT\_SUBMITTED\_TO\_GMSC | Messages were not passed to any operator and failed at the platform. |
| 91526 | NOT\_SUBMITTED\_TO\_SMPP\_CHANNEL | Inbound SM cannot be transferred through SMPP due to lack of SMPP connectivity. |
### SMS handset/subscriber (91600–91699)
| Code | Status | Description |
| ----- | --------------------------------------- | ----------------------------------------------------------------------------------------- |
| 91601 | UNKNOWN\_SUBSCRIBER | Subscriber or recipient is unknown or not recognized by the network. |
| 91602 | UNKNOWN\_BASE\_STATION | Error involving an unidentified or unrecognized base station. |
| 91603 | UNIDENTIFIED\_SUBSCRIBER | Invalid or unregistered number, incomplete or incorrect subscriber information. |
| 91604 | ABSENT\_SUBSCRIBER\_SM | Destination numbers were unreachable, powered off, or in an area with limited coverage. |
| 91605 | UNKNOWN\_EQUIPMENT | Mobile device has not been recognized by EIR during device verification. |
| 91606 | ROAMING\_NOT\_ALLOWED | Subscriber is roaming in another country or operator's infrastructure. |
| 91607 | ILLEGAL\_SUBSCRIBER | Mobile station failed authentication, often due to subscriber's number being blocklisted. |
| 91608 | TELESERVICE\_NOT\_PROVISIONED | Mobile subscription does not support the requested service (e.g. SMS). |
| 91609 | ILLEGAL\_EQUIPMENT | IMEI check on the handset failed due to the IMEI being blocklisted or unauthorized. |
| 91610 | CALL\_BARRED | Operator suspended the subscriber's service, usually for unpaid invoices. |
| 91611 | FACILITY\_NOT\_SUPPORTED | Requested service is not supported by the handset or network. |
| 91612 | ABSENT\_SUBSCRIBER | Destination numbers were unreachable, powered off, or in an area with limited coverage. |
| 91613 | SUBSCRIBER\_BUSY\_FOR\_MT\_SMS | Subscriber is busy for MT SMS. |
| 91614 | SM\_DELIVERY\_FAILURE | Failure in delivering an SMS to the recipient. |
| 91615 | MESSAGE\_WAITING\_LIST\_FULL | The message waiting list is full. |
| 91616 | SYSTEM\_FAILURE | System failure. |
| 91617 | DATA\_MISSING | Some data is missing. |
| 91618 | UNEXPECTED\_DATA\_VALUE | Unexpected data value has been entered. |
| 91619 | UNKNOWN\_ERROR | Unknown error. |
| 91620 | SM\_DF\_MEMORYCAPACITYEXCEEDED | Message inbox on the recipient's mobile phone is full, preventing new messages. |
| 91621 | SM\_DF\_EQUIPMENTPROTOCOLERROR | Recipient device reported an SMS equipment protocol error. |
| 91622 | SM\_DF\_EQUIPMENTNOTSM\_EQUIPPED | Recipient device is not equipped for SMS services. |
| 91623 | SM\_DF\_UNKNOWNSERVICECENTRE | Unknown SMS service centre was reported by the recipient network. |
| 91624 | SM\_DF\_SC\_CONGESTION | SMS service centre congestion prevented message handling. |
| 91625 | SM\_DF\_INVALIDSME\_ADDRESS | Invalid SME address was reported for the recipient equipment context. |
| 91626 | SM\_DF\_SUBSCRIBERNOTSC\_SUBSCRIBER | Subscriber is not provisioned with the referenced service centre. |
| 91627 | CANNOT\_RECEIVE\_SC | End users connected to this MVNO cannot receive short code messages. |
| 91628 | SC\_BLOCKED\_BY\_END\_USER | End user has asked their mobile operator to block messages sent from your short code. |
| 91629 | USER\_OUT\_OF\_CREDIT | End user is out of prepaid credit and cannot receive the message. |
| 91630 | TEMPORARY\_HANDSET\_FAILURE | Temporary handset failure. |
| 91631 | DEST\_ADDRESS\_UNABLE\_TO\_RECEIVE\_SMS | Destination address is unable to receive SMS. |
### SMS user/account (91700–91799)
Some statuses in this section are vendor-derived and preserved as-is for backward compatibility (including legacy spellings).
| Code | Status | Description |
| ----- | ----------------------------------------- | --------------------------------------------------------------------------------------- |
| 91701 | ACCOUNT\_ACCESS\_DENIED | Account access has been denied. |
| 91702 | LIMIT\_REACHED | Carrier sending limit has been reached. |
| 91703 | QUOTA\_REACHED | Carrier daily quota reached. |
| 91704 | CAMPAIGNID\_NOT\_PROVISIONED | Program ID or campaign ID is not provisioned for this mobile operator or is not active. |
| 91705 | ACC\_NOT\_PROVISIONED\_TO\_SMS\_DEMO\_SC | Account is not provisioned to use the SMS demo short code. |
| 91706 | EXCEEDED\_THE\_TIME\_LIMIT\_OF\_SMS\_DEMO | Exceeded the time limit for using SMS demo. |
| 91707 | DEST\_ADDRESS\_NOT\_IN\_SMS\_DEMO | Destination address is not in the safelist for SMS demo. |
| 91708 | EXCEEDED\_THE\_MAX\_NUMBER | Exceeded the maximum number of demo requests. |
| 91709 | INVALID\_GLOBAL\_CAMPAIGN\_ID | Invalid global campaign ID. |
| 91710 | ACC\_HAS\_NO\_ADDRESS\_FOR\_DEST | Account is not provisioned with an address that can reach the destination. |
| 91711 | INTERACTION\_NOT\_SUPPORTED | Interaction is not supported for the message destination. |
| 91712 | ACC\_NOT\_2\_WAY | Account is not provisioned for global two-way SMS. |
| 91713 | CONTENT\_BLOCKED | Content blocked by user opt-out (MO: STOP). |
| 91714 | BLOCKED\_BY\_CAMPAIGN\_BLACKLIST | Content blocked by campaign blocklist. |
| 91715 | REJECTED\_SPAM\_BY\_OPERATOR | Message was identified as spam and cannot be delivered. |
| 91716 | SIGNALS\_BLOCKED | Message rejected due to an anti-fraud mechanism. |
| 91717 | IMSI\_BLACKLISTED | IMSI is blocklisted. |
| 91718 | BLACKLISTED\_DESTINATIONADDRESS | Destination number has been blocklisted. |
| 91719 | BLACKLISTED\_SENDERADDRESS | Sender number has been blocklisted. |
| 91720 | DEACTIVATED\_LIST | Phone number is listed as deactivated. |
| 91721 | MONTHLY\_LIMIT\_REACHED | Account monthly credit limit has been reached. |
| 91722 | DEST\_ADDRESS\_BLACKLISTED | Numbers were identified as blocklisted in the operator's DND database. |
### SMS provider (91800–91899)
Provider statuses in this range are protocol-level and vendor-derived. Use the `status` value for precise diagnosis; descriptions are normalized summaries.
| Code | Status | Description |
| ----- | ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| 91801 | PROVIDER\_GENERAL\_ERROR | Provider returned an unrecoverable protocol error while processing the message. |
| 91802 | NO\_RESPONSE | Message was processed and forwarded to the operator, but no delivery response was returned in time. |
| 91803 | SERVICE\_COMPLETION\_FAILURE | Message processing failed before completion at the downstream provider side. |
| 91804 | UNEXPECTED\_RESPONSE\_FROM\_PEER | Received an unexpected or malformed protocol response from a downstream peer. |
| 91805 | MISTYPED\_PARAMETER | Request includes a mistyped or invalid protocol parameter. |
| 91806 | NOT\_SUPPORTED\_SERVICE | Requested network service is not supported by the downstream provider. |
| 91807 | DUPLICATED\_INVOKE\_ID | Duplicate invoke ID was detected in the signaling transaction. |
| 91808 | OR\_APPCONTEXTNOTSUPPORTED | Remote node does not support the requested application context. |
| 91809 | OR\_INVALIDDESTINATIONREFERENCE | Destination reference in the protocol envelope is invalid. |
| 91810 | OR\_INVALIDORIGINATINGREFERENCE | Originating reference in the protocol envelope is invalid. |
| 91811 | OR\_ENCAPSULATEDAC\_NOTSUPPORTED | Peer does not support the encapsulated application context. |
| 91812 | OR\_TRANSPORTPROTECTIONNOTADEQUATE | Transport protection level is insufficient for this request. |
| 91813 | OR\_NOREASONGIVEN | Remote node rejected the request without a specific reason code. |
| 91814 | OR\_POTENTIALVERSIONINCOMPATIBILITY | Protocol version incompatibility was detected between interconnected nodes. |
| 91815 | OR\_REMOTENODENOTREACHABLE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91816 | NNR\_NOTRANSLATIONFORANADDRESSOFSUCHNATURE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91817 | NNR\_NOTRANSLATIONFORTHISSPECIFICADDRESS | No translation for this specific address. |
| 91818 | NNR\_SUBSYSTEMCONGESTION | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91819 | NNR\_SUBSYSTEMFAILURE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91820 | NNR\_UNEQUIPPEDUSER | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91821 | NNR\_MTPFAILURE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91822 | NNR\_NETWORKCONGESTION | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91823 | NNR\_UNQUALIFIED | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91824 | NNR\_ERRORINMESSAGETRANSPORTXUDT | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91825 | NNR\_ERRORINLOCALPROCESSINGXUDT | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91826 | NNR\_DESTINATIONCANNOTPERFORMREASSEMBLYXUDT | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91827 | NNR\_SCCPFAILURE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91828 | NNR\_HOPCOUNTERVIOLATION | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91829 | NNR\_SEGMENTATIONNOTSUPPORTED | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91830 | NNR\_SEGMENTATIONFAILURE | Mobile subscriber was not reachable due to a network operator protocol error. |
| 91831 | UA\_USERSPECIFICREASON | Message was aborted by the peer due to a user-specific reason. |
| 91832 | UA\_USERRESOURCELIMITATION | Message was aborted by the peer due to user resource limitations. |
| 91833 | UA\_RESOURCEUNAVAILABLE | Message was aborted by the peer because a required resource was unavailable. |
| 91834 | UA\_APPLICATIONPROCEDURECANCELLATION | Message was aborted by the peer due to application procedure cancellation. |
| 91835 | PA\_PROVIDERMALFUNCTION | Message was aborted due to a provider malfunction. |
| 91836 | PA\_SUPPORTINGDIALOGORTRANSACTIONRELEASED | Message was aborted because the supporting dialogue or transaction was released. |
| 91837 | PA\_RESSOURCELIMITATION | Message was aborted due to resource limitations in the provider network. |
| 91838 | PA\_MAINTENANCEACTIVITY | Message was aborted due to maintenance activity in the provider network. |
| 91839 | PA\_VERSIONINCOMPATIBILITY | Message was aborted due to protocol version incompatibility. |
| 91840 | PA\_ABNORMALMAPDIALOG | Message was aborted due to an abnormal MAP dialog termination. |
| 91841 | NC\_ABNORMALEVENTDETECTEDBYPEER | Message was aborted because the peer detected an abnormal event. |
| 91842 | NC\_RESPONSEREJECTEDBYPEER | Message was aborted because the peer rejected the protocol response. |
| 91843 | NC\_ABNORMALEVENTRECEIVEDFROMPEER | Message was aborted due to an abnormal event received from a peer. |
| 91844 | NC\_MESSAGECANNOTBEDELIVEREDTOPEER | Message could not be delivered to the peer due to protocol routing failure. |
| 91845 | NC\_PROVIDEROUTOFINVOKE | Message was aborted because the provider ran out of invoke IDs. |
| 91846 | NO\_COVERAGE | No coverage for requested phone number. |
| 91847 | GATEWAY\_OPERATION\_FAILED | Provider service failed. |
| 91848 | OPERATION\_RESTRICTED | Operation restricted. |
| 91849 | INVALID\_REQUEST | Validation failed for requested arguments. |
| 91850 | INVALID\_TOKEN | The token is invalid or does not exist. |
| 91851 | CONSENT\_NOT\_GRANTED | User consent not granted. |
| 91852 | MI\_ACCOUNT\_DISABLED | User account is disabled for Mobile Identity service. |
| 91853 | OPERATION\_NOT\_ALLOWED | Service is not allowed for this account. |
| 91854 | COUNTRY\_NOT\_ALLOWED | Destination country is not allowed for this account. |
| 91855 | GATEWAY\_NOT\_ALLOWED | Provider is not allowed for this account. |
| 91856 | REJECTED\_NOT\_ENOUGH\_CREDITS | Not enough credits for this service. |
| 91857 | MOBILE\_DEVICE\_TIMEOUT | Timeout occurred during a mobile device redirect. |
| 91858 | REJECTED\_INVALID\_IP\_ADDRESS | IP address not in MNO data range. |
| 91859 | OTP\_SMS\_SEND\_FAILED | Failed to send an SMS message. |
| 91860 | NI\_ATTRIBUTE\_NOT\_ALLOWED | Requested Number Intelligence attribute is not allowed for this account. |
| 91861 | SIM\_SWAP\_CHECK\_FAILED | Failed to execute SIM Swap check request due to an error on the MNO side. |
| 91862 | SIM\_SWAP\_DETECTED | SIM swap detected. |
| 91863 | USER\_INTERNAL\_ERROR | Internal error on the platform. |
| 91864 | INVALIDMSCADDRESS | Text is blocklisted. |
## Email (91100–91299)
Reserved for the email channel. The following codes are defined today:
| Code | Status | Description |
| ----- | -------------------------------- | ------------------------------------------------------------------------ |
| 91101 | INVALID\_FROM\_EMAIL | The from email address is invalid or does not match the required format. |
| 91102 | EMAIL\_CONFIGURATION\_NOT\_FOUND | The email configuration is not found for the workspace. |
| 91103 | EMAIL\_TEMPLATE\_NOT\_FOUND | The email template is not found for the workspace. |
# Events Authentication
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/authentication
Authenticate Events API requests using HTTP Basic Auth with your API key
Events APIs use **HTTP Basic Authentication**. Include this header in every request:
```txt theme={null}
Authorization: Basic
```
The API key goes in the **username** field of HTTP Basic Auth. The password is always empty — note the trailing colon before Base64 encoding.
## Required Header
* **`Authorization`**: `Basic` followed by Base64 of `api_key:` (API key + colon, empty password).
## Where to find your API key
* **Dashboard path**: Workspace Settings → API Keys
* Create or manage keys in the Zixflow dashboard for your workspace.
## Encoding example
```bash theme={null}
# API key: ws_abc123
echo -n "ws_abc123:" | base64
# → d3NfYWJjMTIzOg==
```
## Example request
```bash theme={null}
curl -X POST "https://api-events.zixflow.com/v1/track" \
-H "Authorization: Basic d3NfYWJjMTIzOg==" \
-H "Content-Type: application/json" \
-d '{"userId": "user_123", "event": "Button Clicked"}'
```
## Auth error responses
| HTTP Status | Reason |
| ------------------------- | ----------------------------------------- |
| `401 Unauthorized` | Missing, invalid, or unrecognised API key |
| `503 Service Unavailable` | Auth cache temporarily unavailable |
See [Events Errors](/api-reference/zixflow-ai/events/errors) for the full error response shape.
Do not use messaging ingest headers (`x-api-key`, `x-workspace-id`) with Events APIs. Those apply only to `api-ai.zixflow.com`.
# Batch
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/batch
POST https://api-events.zixflow.com/v1/batch
Send multiple events in a single HTTP request
#### Description
Send multiple events in a single HTTP request. SDKs queue events locally and flush them as a batch. Prefer `/batch` over individual calls when sending many events from a server.
Short alias: `POST /v1/b`
**Limits:**
* Minimum: 1 item per batch
* Maximum: 100 items per batch
* Items are processed concurrently
Group and Alias are **not supported**. Do not include `type: "group"` or `type: "alias"` in the batch.
#### Headers
HTTP Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
Must be `application/json`.
#### Body
Array of event objects. Each item must include a `type` field and the fields for that event type.
When the batch was sent, as ISO 8601.
#### Batch item `type` values
| Type | Equivalent endpoint | Notes |
| ---------- | ----------------------------------------------------------- | --------------------------- |
| `identify` | [`/v1/identify`](/api-reference/zixflow-ai/events/identify) | Uses `traits` |
| `track` | [`/v1/track`](/api-reference/zixflow-ai/events/track) | Uses `event` + `properties` |
| `screen` | [`/v1/screen`](/api-reference/zixflow-ai/events/screen) | Uses `name` + `properties` |
| `page` | [`/v1/page`](/api-reference/zixflow-ai/events/page) | Uses `name` + `properties` |
Each batch item shares the same fields as its corresponding individual endpoint, plus a `type` field. Each item may also include an optional [`context`](/api-reference/zixflow-ai/events/context) object.
#### Response
Successful requests return `HTTP 200` with an empty JSON object.
```json 200-Success theme={null}
{}
```
```json 400-Bad Request theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "batch",
"message": "batch is required"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"error": "UNAUTHORIZED",
"details": []
}
```
#### Example request body
```json theme={null}
{
"batch": [
{
"type": "identify",
"userId": "user_12345",
"traits": { "plan": "enterprise" }
},
{
"type": "track",
"userId": "user_12345",
"event": "Plan Upgraded",
"properties": { "from_plan": "pro", "to_plan": "enterprise" }
},
{
"type": "screen",
"userId": "user_12345",
"name": "Home"
}
],
"sentAt": "2026-05-04T10:00:30.000Z"
}
```
# Context Object
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/context
Environment metadata attached to identify, track, screen, page, and batch events
`context` is optional environment metadata about **where and how** an event was produced. It is not identity (`userId`, `traits`) and not business event data (`event`, `properties`).
| Layer | Answers | Fields |
| ----------- | -------------------------------------- | ---------------------------------------- |
| Identity | Who? | `userId`, `anonymousId`, `traits` |
| Event | What happened? | `event` / `name`, `properties` |
| **Context** | On what device, app, page, or network? | `device`, `os`, `app`, `ip`, `locale`, … |
SDKs populate most of this automatically. When calling the HTTP API from your server, send only what you know (often just `ip` and/or `library`).
## When to send it
* **From SDKs:** Usually leave it alone — the SDK attaches context on every call.
* **From your backend:** Include useful server-known fields such as `ip`, `library`, and optionally `app`.
* **Optional:** Events are accepted without `context`. Richer context improves platform detection, segmentation, and debugging.
## Example
```json theme={null}
{
"context": {
"ip": "203.0.113.1",
"locale": "en-US",
"timezone": "America/New_York",
"userAgent": "Mozilla/5.0 ...",
"library": {
"name": "@zixflow/analytics-browser",
"version": "2.x.x"
},
"app": {
"name": "Acme",
"version": "2.1.0",
"build": "403"
},
"device": {
"id": "device-uuid",
"manufacturer": "Apple",
"model": "iPhone 14 Pro",
"type": "ios"
},
"os": {
"name": "iOS",
"version": "17.4.1"
},
"screen": {
"width": 1170,
"height": 2532,
"density": 3
},
"page": {
"path": "/pricing",
"referrer": "https://google.com",
"title": "Pricing",
"url": "https://app.example.com/pricing"
},
"campaign": {
"source": "google",
"medium": "cpc",
"name": "spring_sale",
"term": "sneakers",
"content": "ad_variant_a"
}
}
}
```
Omit any field you do not have. Nested objects may be partial.
## Field reference
### Top-level
| Field | Type | Description |
| ----------- | ------ | -------------------------------------------------------------- |
| `ip` | string | Client IP address. Useful for geo when sending from a server. |
| `locale` | string | Locale / language tag (for example `en-US`). |
| `timezone` | string | IANA timezone (for example `America/New_York`). |
| `userAgent` | string | Browser or client user-agent string. Helps infer web platform. |
### `library`
Which SDK or integration produced the event.
| Field | Type | Description |
| ----------------- | ------ | ---------------------------------------------------------------------------------- |
| `library.name` | string | Library name (for example `@zixflow/analytics-browser`, `@zixflow/analytics-node`) |
| `library.version` | string | Library version |
### `app`
| Field | Type | Description |
| ------------- | ------ | ------------------------------------------------ |
| `app.name` | string | Application name |
| `app.version` | string | Marketing / semver version (for example `2.1.0`) |
| `app.build` | string | Build number (for example `403`) |
### `device`
| Field | Type | Description |
| --------------------- | ------ | ----------------------------------------- |
| `device.id` | string | Device identifier when available |
| `device.manufacturer` | string | Manufacturer (for example `Apple`) |
| `device.model` | string | Model (for example `iPhone 14 Pro`) |
| `device.name` | string | User-facing device name when available |
| `device.type` | string | Platform hint: `ios`, `android`, or `web` |
### `os`
| Field | Type | Description |
| ------------ | ------ | -------------------------------------- |
| `os.name` | string | OS name (for example `iOS`, `Android`) |
| `os.version` | string | OS version |
### `screen`
Display metrics (typically from mobile / browser SDKs).
| Field | Type | Description |
| ---------------- | ------ | ---------------- |
| `screen.width` | number | Width in pixels |
| `screen.height` | number | Height in pixels |
| `screen.density` | number | Pixel density |
### `page`
Mostly used by the browser SDK for page views and tracks.
| Field | Type | Description |
| --------------- | ------ | -------------- |
| `page.path` | string | URL path |
| `page.url` | string | Full URL |
| `page.referrer` | string | Referrer URL |
| `page.title` | string | Document title |
### `campaign`
UTM / campaign attribution (typically browser).
| Field | Type | Description |
| ------------------ | ------ | -------------- |
| `campaign.source` | string | `utm_source` |
| `campaign.medium` | string | `utm_medium` |
| `campaign.name` | string | `utm_campaign` |
| `campaign.term` | string | `utm_term` |
| `campaign.content` | string | `utm_content` |
## How Zixflow uses context
Platform detection (devices / push) prefers, in order:
1. `context.device.type` (`ios` / `android` / `web`)
2. `context.os.name`
3. `context.library.name` (browser libraries → `web`)
4. Presence of `userAgent` → `web`
Context also supports segments (device type, OS, app version), locale/timezone-aware messaging, and debugging which client produced an event.
## Server-side example
Minimal context from a backend track call:
```json theme={null}
{
"userId": "user_123",
"event": "Order Fulfilled",
"properties": {
"order_id": "ord_789"
},
"context": {
"ip": "203.0.113.1",
"library": {
"name": "acme-fulfillment",
"version": "1.0.0"
}
}
}
```
## What not to put in context
* Business event details → use `properties` (track / screen / page)
* Durable user profile fields → use identify `traits`
* Required identity → use `userId` and/or `anonymousId`
## Applies to
`context` can be sent on:
* [Identify](/api-reference/zixflow-ai/events/identify)
* [Track](/api-reference/zixflow-ai/events/track)
* [Screen](/api-reference/zixflow-ai/events/screen)
* [Page](/api-reference/zixflow-ai/events/page)
* Each item inside [Batch](/api-reference/zixflow-ai/events/batch)
# Events Errors
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/errors
Error response shape and HTTP status codes for the Events API
All Events endpoints return a consistent error shape:
```json theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "userId",
"message": "userId is required"
}
]
}
```
## HTTP status codes
| HTTP Status | Meaning |
| ----------- | ------------------------------------------------------------------ |
| `200` | Success — event or request accepted |
| `400` | Bad request — invalid JSON or failed validation |
| `401` | Unauthorized — missing or invalid API key |
| `404` | Not found — user identifier not found (Track API / Customers only) |
| `503` | Service unavailable — auth cache temporarily unavailable |
| `500` | Internal error — unexpected failure |
## Fields
| Field | Type | Description |
| ------------------- | ------ | ------------------------------------------------ |
| `error` | string | Machine-readable error code |
| `details` | array | Optional list of field-level validation problems |
| `details[].reason` | string | Why the field failed (for example `REQUIRED`) |
| `details[].field` | string | Field name that failed validation |
| `details[].message` | string | Human-readable explanation |
For authentication failures, see [Events Authentication](/api-reference/zixflow-ai/events/authentication).
# Identify
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/identify
POST https://api-events.zixflow.com/v1/identify
Create or update a user profile
#### Description
Create or update a user profile. Call this when a user signs up, logs in, or when you want to update their attributes.
Short alias: `POST /v1/i`
**When to use:**
* User signs up or logs in → call identify with their `userId` and traits
* User updates their profile (name, email, plan) → call identify with updated traits
* Merging anonymous pre-signup activity → call identify with both `userId` and `anonymousId`
**What happens:**
1. If `userId` is new → creates a new user profile
2. If `userId` exists → merges `traits` with existing profile (additive, not replace)
3. If both `userId` and `anonymousId` are provided → links all anonymous events to the identified profile
#### Headers
HTTP Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
Must be `application/json`.
#### Body
Your application's stable identifier for this user. At least one of `userId` or `anonymousId` is required.
Anonymous ID from the SDK. Required if `userId` is not provided. Providing both triggers identity merge.
Key-value user attributes to set or update (for example `email`, `name`, `plan`).
When the identify happened, as ISO 8601. Defaults to server time if omitted.
Deduplication ID. SDKs generate this automatically.
Optional environment metadata (device, OS, app, IP, locale, and more). SDKs populate this automatically. See [Context Object](/api-reference/zixflow-ai/events/context).
#### Response
Successful requests return `HTTP 200` with an empty JSON object.
```json 200-Success theme={null}
{}
```
```json 400-Bad Request theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "userId",
"message": "userId is required"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"error": "UNAUTHORIZED",
"details": []
}
```
# Events Introduction
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/introduction
HTTP API for server-side event tracking and user profiles
Welcome to the Zixflow Events API. Use this service to send server-side events and manage user profiles.
> **Who this is for:** Backend developers and integrators sending server-side events. Mobile and web app actions should use an [SDK](/documentation/sdk/javascript/introduction) instead.
## Base URL
All Events API endpoints are available at:
```
https://api-events.zixflow.com
```
Events APIs use a different host and authentication scheme than messaging ingest (`api-ai.zixflow.com` with `x-api-key` / `x-workspace-id`). See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
## API vs SDK
| | SDKs | HTTP API |
| --------------- | ----------------------------- | ---------------------------------------------------------------- |
| **Who uses it** | Web and mobile apps | Backend servers, scripts, integrations |
| **Auth** | API key in SDK initialization | `Authorization: Basic base64(api_key:)` |
| **Batching** | Automatic | Manual via [`/v1/batch`](/api-reference/zixflow-ai/events/batch) |
| **When to use** | User-facing app actions | Server-side events and profile updates from backend |
**Rule of thumb:** If the event happens because a user tapped something in your app, use an SDK. If it happens on your server (fulfillment, billing, fraud detection), use this API.
## API Groups
| Group | Base path | Purpose |
| ------------------ | --------- | ----------------------------------------------------------- |
| **Data Pipelines** | `/v1/` | Segment-compatible identify, track, screen, page, and batch |
Group and Alias are **not supported**.
## Data Pipelines (`/v1/`)
* [Identify](/api-reference/zixflow-ai/events/identify) — Create or update a user profile
* [Track](/api-reference/zixflow-ai/events/track) — Record a user action or business event
* [Screen](/api-reference/zixflow-ai/events/screen) — Record a mobile screen view
* [Page](/api-reference/zixflow-ai/events/page) — Record a web page view
* [Batch](/api-reference/zixflow-ai/events/batch) — Send multiple events in one request
All Data Pipelines requests accept `Content-Type: application/json`. Successful responses return `HTTP 200` with an empty body `{}`.
## Getting Started
1. Obtain your API key from the Zixflow dashboard (Workspace Settings → API Keys).
2. Authenticate with Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
3. Send events with the Data Pipelines endpoints above.
4. Handle errors using the shared [Events Errors](/api-reference/zixflow-ai/events/errors) reference.
For device, OS, app, IP, locale, and campaign fields, see [Context Object](/api-reference/zixflow-ai/events/context).
## Conceptual Guides
For product concepts (identity, attributes, devices, scenarios), see the [Events documentation](/documentation/events/getting-started) and the [Event API conceptual reference](/documentation/events/event-api-reference).
# Page
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/page
POST https://api-events.zixflow.com/v1/page
Record a web page view
#### Description
Record a web page view. Equivalent to [Screen](/api-reference/zixflow-ai/events/screen) but for web apps.
Short alias: `POST /v1/p`
**When to use:**
* Web apps and marketing sites
* Track specific page metadata (title, URL, referrer)
#### Headers
HTTP Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
Must be `application/json`.
#### Body
Identified user ID. At least one of `userId` or `anonymousId` is required.
Anonymous user ID. Required if `userId` is not provided.
Page name.
Page metadata such as `path`, `url`, `referrer`, and `title`.
When the page view occurred, as ISO 8601.
Optional environment metadata (device, OS, app, IP, locale, campaign, and more). SDKs populate this automatically. See [Context Object](/api-reference/zixflow-ai/events/context).
#### Response
Successful requests return `HTTP 200` with an empty JSON object.
```json 200-Success theme={null}
{}
```
```json 400-Bad Request theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "userId",
"message": "userId is required"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"error": "UNAUTHORIZED",
"details": []
}
```
# Screen
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/screen
POST https://api-events.zixflow.com/v1/screen
Record a mobile screen view
#### Description
Record a mobile screen view. Used by mobile SDKs automatically when screen auto-tracking is enabled, or manually when you want to attach screen-specific properties.
Short alias: `POST /v1/s`
**When to use:**
* Mobile apps to track navigation flow
* Attribute screen-level data (product ID, category) to the view event
#### Headers
HTTP Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
Must be `application/json`.
#### Body
Identified user ID. At least one of `userId` or `anonymousId` is required.
Anonymous user ID. Required if `userId` is not provided.
Screen name (for example `"Product Detail"`).
Screen category.
Screen-specific properties (for example `product_id`, `category`, `price`).
When the screen view occurred, as ISO 8601.
Optional environment metadata (device, OS, app, IP, locale, and more). SDKs populate this automatically. See [Context Object](/api-reference/zixflow-ai/events/context).
#### Response
Successful requests return `HTTP 200` with an empty JSON object.
```json 200-Success theme={null}
{}
```
```json 400-Bad Request theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "userId",
"message": "userId is required"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"error": "UNAUTHORIZED",
"details": []
}
```
# Track
Source: https://docs.zixflow.com/api-reference/zixflow-ai/events/track
POST https://api-events.zixflow.com/v1/track
Record a user action or business event
#### Description
Record a user action or business event. This is the most commonly used endpoint for custom event tracking.
Short alias: `POST /v1/t`
**When to use:**
* Any user action: button tapped, purchase completed, feature used
* Business events from your server: subscription renewed, payment failed
* Device lifecycle events such as `Application Installed` or `Application Opened` (SDKs send these automatically)
#### Headers
HTTP Basic Auth. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
Must be `application/json`.
#### Body
Identified user ID. At least one of `userId` or `anonymousId` is required.
Anonymous user ID if the user is not identified. Required if `userId` is not provided.
Name of the event (for example `"Order Completed"`).
Event-specific data (for example `order_id`, `revenue`, `currency`).
When the event occurred, as ISO 8601.
Optional environment metadata (device, OS, app, IP, locale, and more). SDKs populate this automatically. See [Context Object](/api-reference/zixflow-ai/events/context).
#### Semantic events
Certain event names trigger special handling beyond the standard event pipeline:
| Event name | Special behavior |
| --------------------------- | ---------------------------------------------------------- |
| `Device Created or Updated` | Routes to device registration pipeline |
| `Device Deleted` | Soft-deletes device record (`active=false`) |
| `Device Registered` | Routes to device logs table |
| `Device Updated` | Routes to device logs table |
| `User Deleted` | Sets `is_deleted=true` on profile, deactivates all devices |
| `User Suppressed` | Sets `is_suppressed=true`, blocks all channel delivery |
| `User Unsuppressed` | Clears suppression flag |
| `Application Installed` | Routes to device logs table |
| `Application Opened` | Routes to device logs table |
| `Application Backgrounded` | Routes to device logs table |
| `Application Foregrounded` | Routes to device logs table |
| `Application Crashed` | Routes to device logs table |
#### Response
Successful requests return `HTTP 200` with an empty JSON object.
```json 200-Success theme={null}
{}
```
```json 400-Bad Request theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "event",
"message": "event is required"
}
]
}
```
```json 401-Unauthorised theme={null}
{
"error": "UNAUTHORIZED",
"details": []
}
```
# Introduction
Source: https://docs.zixflow.com/api-reference/zixflow-ai/introduction
API Ingestion Service for SMS, WhatsApp, RCS, Email, and Sendflow
Welcome to Zixflow AI API documentation. This service provides unified APIs for sending messages across multiple channels including Email, SMS, WhatsApp, RCS, and managing Sendflow automations.
## Base URL
Messaging and Sendflow ingest endpoints are available at:
```
https://api-ai.zixflow.com
```
Events (identify, track, screen, page, and batch) use a separate host:
```
https://api-events.zixflow.com
```
See [Events Introduction](/api-reference/zixflow-ai/events/introduction). Events authenticate with HTTP Basic Auth, not `x-api-key` / `x-workspace-id`. See [Events Authentication](/api-reference/zixflow-ai/events/authentication).
## Supported Channels
The Zixflow AI supports the following communication channels:
### SMS
* **Send SMS Message**: Send direct SMS messages with support for flash SMS and DLT compliance (India)
* **Send SMS Template**: Send SMS using predefined templates with variable substitution
### WhatsApp
* **Send WhatsApp Template**: Send approved WhatsApp Business templates
* **Direct Messages**: Send text, image, video, audio, document, sticker, location, contact, and reaction messages
* **Interactive Messages**: Send interactive messages with reply buttons, list buttons, URL buttons, address requests, location requests, media carousels, and product carousels
* **Data APIs**: Get WhatsApp accounts, templates, and template variables
### RCS (Rich Communication Services)
* **Send RCS Template**: Send RCS templates with variable substitution
* **Direct Messages**: Send text, image, video, audio, and document messages
* **Data APIs**: Get RCS template variables
### Email
* **Send Email Message**: Send direct email messages with attachments, open/click tracking, and custom sender information
* **Send Email Template**: Send emails using predefined templates with variable substitution
### Sendflow
* **Trigger Sendflow**: Trigger automation workflows with recipient information and custom variables
* **Stop Sendflow**: Stop running workflow executions
* **Get Delivery Report**: Retrieve delivery status for a single Sendflow execution by request ID
* **Report Logs**: Retrieve paginated Sendflow execution logs with execution and delivery status
* **Report Summary**: Retrieve aggregated Sendflow metrics by date range, channel, and country
### AI Flow Reports
* **Report Logs**: Retrieve paginated AI flow execution logs
* **Report Summary**: Retrieve aggregated AI flow metrics, including conversion and journey completion
### Messages
* **Get Delivery Report**: Retrieve delivery status for a single message by request ID
* **Report Logs**: Retrieve paginated delivery logs by channel and date range
* **Report Summary**: Retrieve aggregated messaging metrics grouped by date
### Events
* **Data Pipelines**: Identify, track, screen, page, and batch on `api-events.zixflow.com`
* Group and Alias are not supported
## Additional Features
* **Postback Notifications**: Receive webhook notifications for message delivery status updates across all channels. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for details.
* **Delivery Reports**: Query delivery status for messages sent through any channel using the request ID
* **Aggregated Reports**: Query logs and summaries for Sendflow, AI Flow, and messages from the Data API (`/api/data`)
## Authentication
The API uses header-based authentication. Include the following headers in all requests:
```
x-api-key:
x-workspace-id:
```
For detailed information on how to obtain your API key and workspace ID, see the [Authentication](/api-reference/zixflow-ai/authentication) documentation.
#### Document and API Usage Guidelines
* Our APIs use the basic HTTP request codes: POST, GET, PATCH, DELETE.
* Note: Any requests made using the valid API credentials will affect the real-time data in your zixflow account.
* All the APIs are provided with their appropriate example requests and responses for successful and failed calls.
#### Rate Limits
* Our API has a rate limit of **150 requests per second**.
* If you exceed this limit, you will receive a "Too many requests, try again later!" error response.
* When you encounter this error, please wait and try again later to avoid further rate limiting.
## Getting Started
1. **Set up authentication**: Obtain your API key and workspace ID from the Zixflow dashboard. See [Authentication](/api-reference/zixflow-ai/authentication) for step-by-step instructions.
2. **Include headers**: Add both `x-api-key` and `x-workspace-id` headers to all API requests
3. **Choose your channel**: Select the appropriate endpoint for your communication channel (SMS, WhatsApp, RCS, Email, or Sendflow)
4. **Format phone numbers**: For SMS, WhatsApp, and RCS, use international format without the `+` sign (e.g., `919876543210` for India)
5. **Handle responses**: All send endpoints return a `request_id` that you can use to track delivery status
6. **Set up webhooks** (optional): Configure `post_back_url` to receive real-time delivery notifications
#### Support
* All the required information on integration is in this document. Please read it thoroughly.
* For any integration and API-related support, you can feel free to drop us an email at [support@zixflow.com](mailto:support@zixflow.com).
* The zixflow terms of service are listed here: [Terms Of Service](https://zixflow.com/privacy-policy)
# Get Message Delivery Report
Source: https://docs.zixflow.com/api-reference/zixflow-ai/messages/get-delivery-report
GET https://api-ai.zixflow.com/api/data/messages/v1/delivery-report
Retrieves a single message delivery report by request_id
#### Description
This endpoint allows you to retrieve the delivery status and details of a message that was sent through any channel (WhatsApp, SMS, Email, RCS) by providing the request\_id that was returned when the message was sent.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Query Parameters
The unique request ID that was returned when the message was sent. This ID is used to track the delivery status of the message.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
The delivery report data containing the following fields:
The communication channel used (e.g., "whatsapp", "sms", "email", "rcs").
The unique identifier for the message.
The recipient's contact information (phone number or email address).
The request ID that was used to query this report.
The current delivery status of the message.
Timestamp indicating when the status was last updated.
Additional remarks or notes about the delivery status.
The workspace ID associated with this message.
```json 200-Success theme={null}
{
"success": true,
"message": "Delivery report retrieved successfully",
"data": {
"channel": "whatsapp",
"message_id": "wamid.XXX",
"recipient": "919876543210",
"request_id": "66e120d1b9eht5059e5e01d",
"status": "delivered",
"statusAt": "2024-01-15T10:30:00Z",
"remark": "Message delivered successfully",
"workspace_id": "workspace_123"
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid request_id parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 404-Not Found theme={null}
{
"success": false,
"message": "Delivery report not found for the given request_id"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get Message Report Logs
Source: https://docs.zixflow.com/api-reference/zixflow-ai/messages/get-report-logs
GET https://api-ai.zixflow.com/api/data/messages/v1/reports/logs
Returns paginated delivery logs
#### Description
This endpoint returns paginated delivery logs. You must provide a date range and channel. Results are cached for 5 seconds for the same workspace and query.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Query Parameters
Start of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). Required. Sample: `2024-01-01T00:00:00Z`.
End of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). Required. Sample: `2024-01-31T00:00:00Z`.
Channel to query. Required. Possible values: `sms`, `whatsapp`, `rcs`, `email`, `push`. Sample: `whatsapp`.
Page number to return. Default is `1`. Sample: `1`.
Number of items to return per page. Default is `50`. Sample: `50`.
Optional delivery status filter. If omitted, logs for all statuses are returned. Sample: `delivered`.
Optional delivery type filter. If omitted, logs for all types are returned. Possible values: `campaign`, `api`, `flow`, `inbox`, `otpflow`. Sample: `campaign`.
Optional recipient search term (phone or email). If omitted, no search filter is applied. Sample: `+1555`.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
An array of message report log objects, each containing:
The recipient address (phone number or email).
The delivery status of the message.
Timestamp when the message was sent.
Timestamp when the message was delivered.
Cost of the message.
Refunded amount, if any.
Number of message parts (for example, SMS segments).
Failure reason when the message was not delivered.
Additional remarks about the delivery.
Platform used to send the message.
Sender or account phone number, when applicable.
RCS bot ID, when applicable.
SMS sender ID, when applicable.
SMS route, when applicable.
From email address, when applicable.
From display name, when applicable.
Email subject, when applicable.
Button labels associated with the message, when applicable.
Associated people record, when available.
The people record ID.
The people record name.
Pagination information containing:
The total number of logs available.
The number of logs returned in this response.
The page size used for this request.
```json 200-Success theme={null}
{
"success": true,
"message": "Message report logs retrieved successfully",
"data": [
{
"recipient": "+15551234567",
"status": "delivered",
"sent_at": "2024-01-15T10:30:00Z",
"delivered_at": "2024-01-15T10:30:08Z",
"cost": 0.012,
"refunded": 0,
"message_count": 1,
"failed_reason": "",
"remark": "",
"platform": "whatsapp",
"phone_number": "123456789",
"bot_id": "",
"sender_id": "",
"route": "",
"from_email": "",
"from_name": "",
"subject": "",
"buttons": ["Track order"],
"people": {
"id": "65f1a2b3c4d5e6f7a8b9c0d2",
"name": "Jane Doe"
}
}
],
"pagination": {
"size": 150,
"count": 50,
"pageSize": 50
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "start_date, end_date, and channel are required"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 429-Too Many Requests theme={null}
{
"success": false,
"message": "Too many requests, try again later!"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get Message Reports Summary
Source: https://docs.zixflow.com/api-reference/zixflow-ai/messages/get-report-summary
GET https://api-ai.zixflow.com/api/data/messages/v1/reports/summary
Returns aggregated messaging metrics by date
#### Description
This endpoint returns aggregated messaging metrics grouped by date. You must provide a date range and channel. Results are cached for 5 minutes for the same workspace and query.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Query Parameters
Start of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). Required. Sample: `2024-01-01T00:00:00Z`.
End of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). Required. Sample: `2024-01-31T00:00:00Z`.
Channel to query. Required. Possible values: `sms`, `whatsapp`, `rcs`, `email`, `push`. Sample: `whatsapp`.
Optional delivery type filter. If omitted, metrics include all types. Possible values: `campaign`, `api`, `flow`. Sample: `campaign`.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
An array of date summary objects, each containing:
The date for this summary row.
Audience size for the date.
Number of messages submitted.
Number of messages sent.
Number of messages delivered.
Number of undelivered messages.
Number of failed messages.
Number of messages marked as read.
Number of emails opened.
Number of clicks.
Number of bounced emails.
Number of spam complaints.
Number of unsubscribes.
Number of SMS messages submitted.
Number of SMS messages sent.
Number of SMS messages delivered.
Number of SMS messages undelivered.
Total cost for the date.
```json 200-Success theme={null}
{
"success": true,
"message": "Message reports summary retrieved successfully",
"data": [
{
"date": "2024-01-15",
"audience": 5000,
"submitted": 4980,
"sent": 4900,
"delivered": 4720,
"undelivered": 80,
"failed": 100,
"read": 2100,
"opened": 0,
"clicked": 340,
"bounce": 0,
"complaint": 0,
"unsubscribed": 12,
"sms_submitted": 0,
"sms_sent": 0,
"sms_delivered": 0,
"sms_undelivered": 0,
"cost": 58.4
}
]
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "start_date, end_date, and channel are required"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 429-Too Many Requests theme={null}
{
"success": false,
"message": "Too many requests, try again later!"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Postback Notifications
Source: https://docs.zixflow.com/api-reference/zixflow-ai/postback-notifications
Postback payload format for delivery status notifications across SMS, RCS, WhatsApp, and Email
When you provide a `post_back_url` in your send request, Zixflow sends an HTTP POST request to that URL whenever the message status changes (e.g., delivered, failed, read, etc). The same payload format is used for all four channels: **SMS**, **RCS**, **WhatsApp**, and **Email**.
## Postback Payload Format
The request body sent to your `post_back_url` is a JSON object with the following fields:
| Field | Type | Description |
| ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `workspace_id` | string | Your Zixflow workspace ID. |
| `type` | string | Type of notification. For delivery updates this is `"delivery_status"`. |
| `message_id` | number | Unique identifier for the message in Zixflow. |
| `request_id` | string | The request/correlation ID (e.g., UUID) for the original send request. |
| `channel` | string | Channel that sent the message: `"sms"`, `"rcs"`, `"whatsapp"`, or `"email"`. |
| `recipient` | string | Recipient identifier (phone number for SMS/RCS/WhatsApp, email address for Email). |
| `status` | string | Current delivery status (e.g., `"delivered"`, `"failed"`, `"read"`, `"clicked"`, etc.). |
| `remark` | string | Optional remark or error/details associated with the status. |
| `status_at` | string | ISO 8601 timestamp when the status was recorded (e.g., `"2026-01-28T17:17:59.254174911Z"`). |
| `post_back_data` | string | The optional value you sent in `post_back_data` when sending the message. Use this to correlate the notification with your own records. |
## Example Payload
```json theme={null}
{
"workspace_id": "69614209f51dfafb2231a6fe",
"type": "delivery_status",
"message_id": 604132581490819100,
"request_id": "65ca1d21-8854-4d39-a0bd-64f1cd7b8db1",
"channel": "whatsapp",
"recipient": "919090909090",
"status": "delivered",
"remark": "",
"status_at": "2026-01-28T17:17:59.254174911Z",
"post_back_data": "somedata"
}
```
## Using post\_back\_data
If you pass `post_back_data` in your send request (e.g., an order ID, ticket ID, or internal reference), that same value is included in the postback as `post_back_data`. Use it to match the notification to the message in your system without storing Zixflow `message_id` or `request_id`.
## Channel applicability
This postback format is **common across all four channels**:
* **SMS** — send message/template with `post_back_url` (and optional `post_back_data`)
* **RCS** — send message/template with `post_back_url` (and optional `post_back_data`)
* **WhatsApp** — send message/template with `post_back_url` (and optional `post_back_data`)
* **Email** — send message/template with `post_back_url` (and optional `post_back_data`)
Ensure your `post_back_url` endpoint accepts `POST` requests with a JSON body and responds with a success status (e.g., 2xx) so Zixflow can treat the delivery as acknowledged.
## Where post\_back\_url is used
You can pass `post_back_url` (and optional `post_back_data`) on these Zixflow AI send endpoints:
* **SMS**: [Send SMS Message](/api-reference/zixflow-ai/sms/send-message), [Send SMS Template](/api-reference/zixflow-ai/sms/send-template)
* **RCS**: [Send RCS Template](/api-reference/zixflow-ai/rcs/send-template), [Text](/api-reference/zixflow-ai/rcs/messages/text-message), [Image](/api-reference/zixflow-ai/rcs/messages/image-message), [Video](/api-reference/zixflow-ai/rcs/messages/video-message), [Audio](/api-reference/zixflow-ai/rcs/messages/audio-message), [Document](/api-reference/zixflow-ai/rcs/messages/document-message)
* **WhatsApp**: [Send WhatsApp Template](/api-reference/zixflow-ai/whatsapp/send-template), and all [Direct](/api-reference/zixflow-ai/whatsapp/messages/text-message) and [Interactive](/api-reference/zixflow-ai/whatsapp/messages/interactive-reply-buttons-message) message endpoints
* **Email**: [Send Email Message](/api-reference/zixflow-ai/email/send-message), [Send Email Template](/api-reference/zixflow-ai/email/send-template)
Each of those pages documents the `post_back_url` and `post_back_data` request fields and links back here for the payload format.
# Get RCS Template Variables
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/get-template-variables
GET https://api-ai.zixflow.com/api/data/rcs/v1/{bot_id}/variable-keys
Retrieves the variable keys and types for a specific RCS template
#### Description
This endpoint allows you to retrieve the variable keys and their types for a specific RCS template. This is useful when you need to know what variables are required or available in a template before sending a message.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique identifier of the RCS bot. This is the Bot ID associated with your RCS account.
#### Query Parameters
The name of the RCS template for which you want to retrieve variable information.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
An array of template variable objects, each containing:
The variable key name that should be used when sending the template.
The type of the variable (e.g., "text", "image", "video", etc.).
```json 200-Success theme={null}
{
"success": true,
"message": "Template variables retrieved successfully",
"data": [
{
"key": "customer_name",
"type": "text"
},
{
"key": "order_number",
"type": "text"
},
{
"key": "product_image",
"type": "image"
}
]
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid bot_id or template_name parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Introduction to RCS Messaging
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/introduction
Rich Communication Services messaging API
Rich Communication Services (RCS) is an advanced messaging protocol that allows businesses to send interactive, media-rich messages to users. Currently, Zixflow supports RCS messaging for clients in **India only**.
### Getting Started with RCS
To start using RCS, clients must contact the Zixflow support team. Our team will guide you through creating an RCS account. Once the account is set up, Zixflow will link it to your profile, activating it on your account.
### Supported Message Types
The RCS API supports the following message types:
* **Text Messages**: Send plain text messages with optional suggestions
* **Image Messages**: Send images with captions
* **Video Messages**: Send video content with captions
* **Audio Messages**: Send audio files
* **Document Messages**: Send document files
* **Templates**: Send pre-approved RCS templates with variables
### Features
* Interactive suggestions and quick replies
* Rich media support (images, videos, audio, documents)
* Template-based messaging for compliance
* Post-back URLs for delivery status tracking
# Send RCS Audio Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/messages/audio-message
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/message/audio/send
Send an audio message via RCS
#### Description
This endpoint allows you to send an audio message via RCS. You can include interactive suggestions for quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The URL of the audio file to send. The audio file must be publicly accessible.
The RCS bot ID to use for sending the message.
Array of interactive suggestions (quick reply buttons) to include with the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send RCS Document Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/messages/document-message
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/message/document/send
Send a document message via RCS
#### Description
This endpoint allows you to send a document message via RCS. You can include interactive suggestions for quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The URL of the document to send. The document must be publicly accessible.
The RCS bot ID to use for sending the message.
Array of interactive suggestions (quick reply buttons) to include with the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send RCS Image Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/messages/image-message
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/message/image/send
Send an image message via RCS
#### Description
This endpoint allows you to send an image message via RCS. You can include interactive suggestions for quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The URL of the image to send. The image must be publicly accessible.
The RCS bot ID to use for sending the message.
Array of interactive suggestions (quick reply buttons) to include with the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send RCS Text Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/messages/text-message
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/message/text/send
Send a text message via RCS
#### Description
This endpoint allows you to send a text message via RCS. You can include interactive suggestions for quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The text content of the RCS message.
The RCS bot ID to use for sending the message.
Array of interactive suggestions (quick reply buttons) to include with the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid data provided"
}
```
# Send RCS Video Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/messages/video-message
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/message/video/send
Send a video message via RCS
#### Description
This endpoint allows you to send a video message via RCS. You can include interactive suggestions for quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The URL of the video to send. The video must be publicly accessible.
The RCS bot ID to use for sending the message.
Array of interactive suggestions (quick reply buttons) to include with the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send RCS Template
Source: https://docs.zixflow.com/api-reference/zixflow-ai/rcs/send-template
POST https://api-ai.zixflow.com/api/ingest/rcs/v1/template/send
Send an RCS message using a predefined template
#### Description
This endpoint allows you to send an RCS message using a predefined template. Templates can include variables that are replaced with actual values when the message is sent.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The name of the RCS template to use.
Key-value pairs for template variables. Keys should match the variable names in the template, and values will replace the placeholders.
The RCS bot ID to use for sending the message.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "RCS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid template or missing variables"
}
```
```json 401-Unauthorised theme={null}
{
"success": false,
"message": "Unauthorised"
}
```
# Condition Node
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/condition-node
How to use the Condition Node in Sendflow to route data based on rules
The **Condition Node** checks a specific rule (a "condition") and decides which path the data should take next.
## How It Works
* You write a simple sentence (a rule).
* If the rule is **True**, the flow follows the path connected to that rule.
* If the rule is **False**, the flow moves to the next rule or the "Else" (fallback) path.
## 1. Comparing Values (Basic Operators)
These operators check if a value matches, is greater than, or differs from another value.
| Operator | Name | Logic | Example Pattern |
| -------- | ---------------- | ------------------------------ | ------------------------- |
| `==` | Equals | Does A equal B? | `channel == "whatsapp"` |
| `!=` | Not Equals | Is A anything except B? | `status != "opt-out"` |
| `>` | Greater Than | Is A bigger than B? | `message_count > 5` |
| `>=` | Greater or Equal | Is A bigger or the same as B? | `credit_balance >= 10.00` |
| `<` | Less Than | Is A smaller than B? | `retry_attempts < 3` |
| `<=` | Less or Equal | Is A smaller or the same as B? | `days_inactive <= 7` |
You can use quotes for text (`"whatsapp"`) but they are usually optional. Numbers do not need quotes.
**Real-world examples:**
* Send SMS only if the user is verified: `user_verified == "true"`
* Send RCS only if the balance is positive: `wallet_balance > 0`
## 2. Checking Lists (The `in` Operator)
Use the **in** operator to check if a value belongs to a specific group or list. This is faster than writing multiple "OR" rules.
**Syntax:** `VariableName in [Item1, Item2, Item3]` or `VariableName in item1, item2`
**Examples:**
* `state in [NY, CA, TX]` — True if the state is New York, California, or Texas
* `status in [pending, processing]`
**Real-world examples:**
* Send via WhatsApp if the country supports it: `country_code in [IN, BR, ID, US]`
* Process only specific message statuses: `delivery_status in [failed, undelivered]`
* Target specific user tags: `user_tag in [vip, premium, beta_tester]`
## 3. Pattern Matching (The `matches` Operator)
For advanced users only.
Use **matches** to check if text follows a specific pattern (like a phone number format or email domain) using Regex.
**Syntax:** `variable matches pattern`
**Real-world examples:**
* Check if the phone number is an Indian mobile number: `phone matches "^91\d{10}$"`
* Send email only to corporate domains: `email matches "@company\.com$"`
* Validate an OTP format (4 digits): `otp_input matches "^\d{4}$"`
## 4. The "NOT" Operator (!)
Use **!** to reverse a rule. If the rule inside is True, the **!** makes it False.
**Syntax:** `!variable == value`
**Real-world examples:**
* Send SMS if the user is NOT blocked: `!status == "blocked"`
* Proceed if the email is NOT empty: `!email == ""`
* Target users who are NOT in the 'churn' list: `!segment == "churned"`
## 5. Combining Rules (&& and ||)
You can mix and match rules to create complex logic.
### The AND Operator (&&)
Both sides must be true for the condition to pass.
**Syntax:** `Condition A && Condition B`
**Example:** `age > 18 && country == "USA"`\
**Meaning:** "The user must be over 18 AND they must live in the USA."
**Real-world examples:**
* Send WhatsApp if the user has opted in AND has a valid phone number: `opt_in_whatsapp == "true" && phone_valid == "true"`
* Send critical alert if priority is high AND status is pending: `priority == "high" && status == "pending"`
### The OR Operator (||)
At least one side must be true for the condition to pass.
**Syntax:** `Condition A || Condition B`
**Example:** `role == "admin" || role == "manager"`\
**Meaning:** "Let them in if they are an Admin OR if they are a Manager."
**Real-world examples:**
* Send message via cheaper channel (SMS OR RCS): `channel_preference == "sms" || channel_preference == "rcs"`
* Retry sending if the error was 'timeout' OR 'busy': `error_code == "timeout" || error_code == "busy"`
### Summary of Priority
When you write a complex rule, the system checks them in this order:
1. **!** (NOT) checks happen first.
2. **&&** (AND) groups are checked next.
3. **||** (OR) splits are checked last.
**Complex example:**
```
country == "US" && !status == "unsubscribed" || role == "admin"
```
**Translation:** "Send the message if: (The user is in the US AND is not unsubscribed) OR (The user is an Admin)."
In this system, the "AND" (&&) operator splits the rule first.
**Rule:** `A || B && C` is treated as `(A or B) AND C`.
This is helpful for scenarios like: "Match either channel AND ensure the user is active."
***
## Cheat Sheet Examples
Common use cases in messaging or notification workflows:
* **Check for VIP customers:** `total_spent >= 1000`
* **Check if an order is active:** `status != "cancelled"`
* **Send email only to Gmail users:** `email matches "@gmail.com"`
* **Check logic for shipping fees:** `country == "UK" && order_value < 50`
***
## Use Case Reference
### 1. Scheduling & Time Windows
Assuming you have variables like `current_hour` (0–23) or `day_name`.
| Goal | Condition String | Explanation |
| ------------------- | ------------------------------------------------ | --------------------------------- |
| Business Hours Only | `current_hour >= 9 && current_hour < 18` | Sends only between 9 AM and 6 PM. |
| Exclude Weekends | `day_name != "Saturday" && day_name != "Sunday"` | Blocks messages on Sat/Sun. |
| Specific Days Only | `day_name in [Monday, Wednesday, Friday]` | Only allows Mon/Wed/Fri. |
| Morning Rush | `current_hour <= 10` | Matches times before or at 10 AM. |
### 2. Budget & Limits
Assuming variables like `wallet_balance`, `msg_cost`, `retry_count`.
| Goal | Condition String | Explanation |
| ------------------- | ---------------------------- | ------------------------------------------ |
| Sufficient Funds | `wallet_balance >= msg_cost` | Check if user can afford the message. |
| Stop Infinite Loops | `retry_count < 5` | Only retry if attempted less than 5 times. |
| High Value Check | `campaign_budget > 1000` | Checks for large budget campaigns. |
| Safety Cap | `daily_sent <= 10000` | Stops sending if daily limit is reached. |
### 3. Smart Routing
Deciding which channel (SMS, WhatsApp, Email) to use.
| Goal | Condition String | Explanation |
| ----------------- | -------------------------------------------- | -------------------------------------------- |
| WhatsApp Priority | `has_whatsapp == "true" && opt_in == "true"` | Use WhatsApp only if installed AND opted-in. |
| Fallback Logic | `channel == "sms"` | Use SMS as fallback channel. |
| Vendor Selection | `country == "US"` | Route by country. |
| Cheap Route | `msg_type == "marketing" && cost < 0.05` | Send marketing only if route is cheap. |
### 4. Audience Targeting
Filtering specific types of users.
| Goal | Condition String | Explanation |
| --------------- | -------------------------------------- | -------------------------------------------------- |
| VIP List | `plan in [gold, platinum, enterprise]` | Matches high-tier users. |
| Active Users | `status == "active" && !is_blocked` | User must be active AND not blocked. |
| Specific Domain | `email matches "@pixelcorp\.com$"` | Matches only PixelCorp employee emails. |
| Local Users | `zip_code matches "^902\d{2}$"` | Matches zip codes starting with 902 (e.g., 90210). |
### 5. Content Validation
Checking the message data before sending.
| Goal | Condition String | Explanation |
| --------------- | ------------------------------------------ | ---------------------------------------------- |
| No Empty Msg | `message_body != ""` | Ensures message is not empty. |
| Contains Link | `message_body matches "https?://"` | Checks if a link exists in the text. |
| Length Check | `char_count <= 160` | Checks if message fits in one SMS segment. |
| Variable Exists | `first_name != "null" && first_name != ""` | Ensures name is available for personalization. |
### 6. Complex Combinations
Combining multiple factors.
**Scenario:** Send a promo if the user is a VIP OR it's their birthday, BUT they must be opted-in.
* **Condition:** `is_vip == "true" || is_birthday == "true" && active_optin == "true"`
* **Logic:** (VIP OR Birthday) AND Opted-In
**Scenario:** Send via RCS if phone supports it, otherwise check if SMS is allowed.
This usually requires two steps, but in one node:
* **Condition:** `rcs_enabled == "true" || sms_fallback_allowed == "true"`
# Get Sendflow Delivery Report
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/get-delivery-report
GET https://api-ai.zixflow.com/api/data/sendflow/v1/delivery-report
Retrieves a single Sendflow delivery report by request_id. Includes execution_status (run) and delivery_status (message DLR).
#### Description
This endpoint retrieves a single Sendflow delivery report by `request_id`. The response includes `execution_status` (workflow run status) and `delivery_status` (message delivery report). Use the request ID returned when you triggered the Sendflow.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Query Parameters
The unique request ID that was returned when the Sendflow was triggered. This ID is used to track the execution status of the workflow.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
The Sendflow delivery report data containing the following fields:
The request ID that was used to query this report.
The unique execution ID for this Sendflow workflow run.
The workflow run status (execution status).
The message delivery report (DLR) status.
Timestamp indicating when the status was last updated.
The email address associated with this Sendflow execution (if applicable).
The phone number associated with this Sendflow execution (if applicable).
Additional remarks or notes about the execution status.
The workspace ID associated with this Sendflow execution.
```json 200-Success theme={null}
{
"success": true,
"message": "Sendflow delivery report retrieved successfully",
"data": {
"request_id": "8cf12de6-0504-41f9-b576-cbdb20b3e275",
"execution_id": "exec_123456789",
"execution_status": "Completed",
"delivery_status": "delivered",
"statusAt": "2024-01-15T10:30:00Z",
"email": "user@example.com",
"phone": "919876543210",
"remark": "Sendflow executed successfully",
"workspace_id": "workspace_123"
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid request_id parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 404-Not Found theme={null}
{
"success": false,
"message": "Sendflow delivery report not found for the given request_id"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get Sendflow Report Logs
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/get-report-logs
GET https://api-ai.zixflow.com/api/data/sendflow/v1/reports/logs/{flowId}
Returns paginated sendflow execution logs with execution status and delivery status
#### Description
This endpoint returns paginated Sendflow execution logs for a specific flow, including execution status and delivery status. You can filter by run status, date range, and a search term (request ID, phone, or email). Results are cached for 5 seconds for the same workspace and query.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique identifier of the Sendflow.
#### Query Parameters
Page number to return. Default is `1`. Sample: `1`.
Number of items to return per page. Default is `20`. Sample: `20`.
Optional run status filter. If omitted, logs for all statuses are returned. Possible values: `Pending`, `Running`, `Completed`, `Failed`, `Stopped`. Sample: `Completed`.
Optional start of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). If omitted, no lower date bound is applied. Sample: `2024-01-01T00:00:00Z`.
Optional end of the date range in RFC3339 format (`YYYY-MM-DDTHH:mm:ssZ`). If omitted, no upper date bound is applied. Sample: `2024-01-31T00:00:00Z`.
Optional search term. Matches `request_id`, phone, or email. If omitted, no search filter is applied. Sample: `+1555`.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
An array of Sendflow execution log objects, each containing:
The unique request ID for this Sendflow run.
The date of the Sendflow execution.
The run status of the Sendflow execution. Possible values: `Pending`, `Running`, `Completed`, `Failed`, `Stopped`.
The message delivery status for this execution.
Recipients associated with this execution.
The profile ID of the recipient.
The name of the recipient.
The phone number of the recipient.
The email address of the recipient.
Pagination information containing:
The total number of logs available.
The number of logs returned in this response.
The page size used for this request.
```json 200-Success theme={null}
{
"success": true,
"message": "Sendflow report logs retrieved successfully",
"data": [
{
"request_id": "8cf12de6-0504-41f9-b576-cbdb20b3e275",
"date": "2024-01-15T10:30:00Z",
"status": "Completed",
"delivery_status": "delivered",
"recipients": [
{
"id": "65f1a2b3c4d5e6f7a8b9c0d2",
"name": "Jane Doe",
"phone": "+15551234567",
"email": "jane@example.com"
}
]
}
],
"pagination": {
"size": 36,
"count": 20,
"pageSize": 20
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid flowId or query parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 429-Too Many Requests theme={null}
{
"success": false,
"message": "Too many requests, try again later!"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get Sendflow Reports Summary
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/get-report-summary
GET https://api-ai.zixflow.com/api/data/sendflow/v1/reports/summary/{flowId}
Returns aggregated sendflow metrics
#### Description
This endpoint returns aggregated Sendflow metrics for a specific flow. You can filter by date range, channels, and countries. Results are cached for 5 minutes for the same workspace and query.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique identifier of the Sendflow.
#### Query Parameters
Optional start of the date range. Accepts RFC3339 (`YYYY-MM-DDTHH:mm:ssZ`) or `YYYY-MM-DD`. If omitted, no lower date bound is applied. Sample: `2024-01-01T00:00:00Z`.
Optional end of the date range. Accepts RFC3339 (`YYYY-MM-DDTHH:mm:ssZ`) or `YYYY-MM-DD`. If omitted, no upper date bound is applied. Sample: `2024-01-31T00:00:00Z`.
Optional comma-separated channel filter. If omitted, metrics include all channels. Possible values: `sms`, `email`, `whatsapp`, `rcs`. Sample: `whatsapp,sms`.
Optional comma-separated ISO country codes. If omitted, metrics include all countries. Sample: `US,IN`.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
Aggregated Sendflow metrics containing:
Total number of Sendflow requests.
Number of messages submitted.
Number of messages sent.
Number of messages delivered.
Number of messages acknowledged.
Number of failed executions or deliveries.
Total cost for the selected period.
```json 200-Success theme={null}
{
"success": true,
"message": "Sendflow reports summary retrieved successfully",
"data": {
"requests": 800,
"submitted": 790,
"sent": 770,
"delivered": 740,
"acknowledged": 710,
"failed": 20,
"cost": 42.8
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid flowId or query parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 429-Too Many Requests theme={null}
{
"success": false,
"message": "Too many requests, try again later!"
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Introduction to Sendflow
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/introduction
AI-powered Sendflow API designed to maximize message delivery success by automatically routing messages through multiple channels on autopilot
Sendflow is the world's first AI-powered API designed to maximize message delivery success by automatically routing messages through multiple channels on autopilot.
### Overview
We've worked with hundreds of clients, and one common question is: **How can we achieve 100% message delivery?**
Over the years, we've optimized every channel using AI-powered real-time dynamic routing and automated retries at Zixflow. While we outperform competitors in delivery rates, achieving 100% delivery isn't always possible due to limitations in operator infrastructure, which is beyond our control. However, ensuring critical use cases like OTPs or mandatory messages reach users on time is essential, as missed messages can lead to lost business opportunities.
To solve this, we're introducing the world's first Sendflow API. With this, we can achieve **99.99% delivery for valid numbers**.
Here's how it works:
* The system uses AI to optimize delivery across all channels with priority routing, not just at Zixflow but also at the operator level.
* If a message fails or isn't delivered in time, it automatically switches to another channel.
* You can pass potential messages to all activated channels in your account, and the system will handle delivery optimization on autopilot.
This approach ensures faster and more reliable message delivery, reducing drop-offs and maximizing business impact.
### How Does It Work?
* **Sequential Delivery**: Messages are sent in the defined order (e.g., SMS → WhatsApp → RCS). You can define the flow on the Zixflow dashboard in the Sendflow section.
* **Timeout Handling**:
* If a message is delivered within the timeout window, the next steps are skipped.
* If a message fails, it will switch to the next channel (e.g., if the message fails within 2 seconds, it will move to the next channel immediately without waiting for the timeout).
* If we don't receive a delivery report (success or failure) within the timeout window, it will switch to the next channel.
* **Multi-Channel Reliability**: Combining SMS, WhatsApp, and RCS ensures high delivery success.
### Example Flow
**Scenario**: Send via SMS (timeout 10s), then WhatsApp (timeout 10s), then RCS (timeout 10s).
**Process**:
1. SMS message sent first.
2. If delivered, stops. If failed or no response, moves to WhatsApp.
3. Same logic applies to RCS.
### Benefits
* **Higher Delivery Rates**: Ensures at least one channel succeeds.
* **Efficient Use of Resources**: No redundant attempts and reduces wait-time for users.
* **Customizable**: Control timeouts and delivery order.
* **Save Costs**: Instead of retrying on the same channel multiple times or sending messages to all channels simultaneously, this approach triggers the second channel only if the first one fails, reducing unnecessary usage and cost.
### Limitations
* **Timeout Limitation**: The maximum timeout window for all channels combined is 1 day. After this, the entire request is skipped.
* **Total Messages**: In one request, you can send a minimum of 1 message and a maximum of 5 messages. You can use the same channel multiple times or use different channels as per your need (e.g., SMS → SMS → WhatsApp → RCS → Email).
* **Fallback Channels**: In some cases, delivery reports may arrive late, even if the message was delivered on time. Despite optimizations, this may result in messages being sent on multiple channels if the report isn't received within the timeout window.
### Pricing
**No Additional Cost**: There are no extra charges for this service. You'll only be charged for the messages sent, based on the pricing of each channel.
# Stop Sendflow Execution
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/stop
POST https://api-ai.zixflow.com/api/ingest/sendflow/v1/{sendflow_id}/stop/{request_id}
Stop a running Sendflow workflow execution
#### Description
This endpoint allows you to stop a running Sendflow workflow execution using the request ID returned from the trigger endpoint.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique identifier of the Sendflow workflow.
The unique identifier of the workflow execution request to stop. This is returned from the trigger endpoint.
#### Response
Indicates whether the call was successful. true if successful, false if not.
Success or error response message.
```json 200-Success theme={null}
{
"success": true,
"message": "Sendflow execution stopped successfully"
}
```
```json 404-Not Found theme={null}
{
"success": false,
"message": "Request ID not found or already completed"
}
```
# Trigger Sendflow
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sendflow/trigger
POST https://api-ai.zixflow.com/api/ingest/sendflow/v1/{sendflow_id}/trigger
Trigger a Sendflow automation workflow
#### Description
This endpoint allows you to trigger a Sendflow automation workflow. You can pass recipient information (phone and email) and custom variables to the workflow, and optionally run it in draft mode for testing.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique identifier of the Sendflow workflow to trigger.
#### Query Parameters
Whether to run the workflow in draft mode. Default: false. Use true for testing workflows.
#### Body
Recipient information for the workflow. Contains fixed phone and email fields.
The phone number of the recipient in international format (e.g., "919876543210").
The email address of the recipient.
Key-value pairs for template variables. Keys should match the variable names in your Sendflow workflow, and values will replace the placeholders.
#### Request Example
```json theme={null}
{
"recipients": {
"phone": "919876543210",
"email": "demo@example.com"
},
"variables": {
"name": "sam"
}
}
```
#### Response
Indicates whether the call was successful. true if successful, false if not.
Success or error response message.
The unique identifier for this workflow execution request. Use this ID to stop the workflow if needed.
```json 200-Success theme={null}
{
"success": true,
"message": "Sendflow triggered successfully",
"request_id": "req_1234567890"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid sendflow_id"
}
```
# Send SMS Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/sms/send-message
POST https://api-ai.zixflow.com/api/ingest/sms/v1/message/send
Send an SMS message to a recipient
#### Description
This endpoint allows you to send an SMS message to a recipient. You can specify the sender ID, route, and include DLT (Distributed Ledger Technology) information for compliance.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The phone number with a country prefix to which the message will be sent. **Required.** Must be in international format without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The content of the message that you want to send.
The registered and approved sender name to be used for the message. You can add and manage sender IDs in [Zixflow SMS Settings](https://ai.zixflow.com/settings/channels/sms).
Type of connectivity for the message. Supported routes: `promotional`, `transactional`, or `otp`.
Set this parameter to `true` if you want to send a flash SMS via the API; otherwise, set it to `false`. Flash SMS appears directly on the screen. Default: `false`.
Only applicable for India. If you want to pass a DLT entity ID directly via the API, you can do so with this parameter. DLT (Distributed Ledger Technology) entity ID for compliance purposes.
Only applicable for India. If you want to pass a DLT template ID directly via the API, you can do so with this parameter. This is specifically for DLT (Distributed Ledger Technology) compliance purposes in India.
To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "SMS request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid data provided"
}
```
```json 401-Unauthorised theme={null}
{
"success": false,
"message": "Unauthorised"
}
```
# Get WhatsApp Accounts
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/get-accounts
GET https://api-ai.zixflow.com/api/data/whatsapp/v1/accounts
Retrieves a list of all WhatsApp accounts for the authenticated workspace
#### Description
This endpoint allows you to retrieve a list of all WhatsApp Business accounts associated with your workspace. Each account includes details such as phone ID, display phone number, verified name, quality rating, and daily limits.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
An array of WhatsApp account objects, each containing:
The unique Phone ID associated with the WhatsApp Business account. Use this ID when sending messages.
The WhatsApp Business Account (WABA) ID.
The display phone number for this WhatsApp account.
The verified business name associated with this WhatsApp account.
The quality rating of the WhatsApp account (e.g., "GREEN", "YELLOW", "RED").
The daily messaging limit for this WhatsApp account.
```json 200-Success theme={null}
{
"success": true,
"message": "Successfully fetched WhatsApp accounts",
"data": [
{
"phone_id": "123456789",
"waba_id": "987654321",
"display_phone_number": "+1 234 567 8900",
"verified_name": "My Business",
"quality_rating": "GREEN",
"daily_limit": 1000
},
{
"phone_id": "987654321",
"waba_id": "987654321",
"display_phone_number": "+1 234 567 8901",
"verified_name": "Another Business",
"quality_rating": "GREEN",
"daily_limit": 500
}
]
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get WhatsApp Template Variables
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/get-template-variables
GET https://api-ai.zixflow.com/api/data/whatsapp/v1/{phone_id}/variable-keys
Retrieves the variable keys and types for a specific WhatsApp template
#### Description
This endpoint allows you to retrieve the variable keys and their types for a specific WhatsApp template. This is useful when you need to know what variables are required or available in a template before sending a message. Each variable includes its key name, type, and display label.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
#### Query Parameters
The name of the WhatsApp template for which you want to retrieve variable information.
The language code of the template (e.g., "en", "en\_US"). This must match the language of the template you want to query.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
The template variables response containing:
An array of template variable objects, each containing:
The variable key name that should be used when sending the template. This is the identifier you'll use in your API request.
The display label for the variable, which provides a human-readable description of what the variable represents.
The type of the variable. Possible values: "IMAGE\_URL", "VIDEO\_URL", "DOCUMENT\_URL", "TEXT", "OTP".
```json 200-Success theme={null}
{
"success": true,
"message": "Template variables retrieved successfully",
"data": {
"data": [
{
"keyName": "customer_name",
"label": "Customer Name",
"type": "TEXT"
},
{
"keyName": "order_number",
"label": "Order Number",
"type": "TEXT"
},
{
"keyName": "product_image",
"label": "Product Image",
"type": "IMAGE_URL"
},
{
"keyName": "otp_code",
"label": "OTP Code",
"type": "OTP"
}
]
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid phone_id, template_name, or language parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Get WhatsApp Templates
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/get-templates
GET https://api-ai.zixflow.com/api/data/whatsapp/v1/{phone_id}/templates
Retrieves a list of WhatsApp templates for a specific phone ID
#### Description
This endpoint allows you to retrieve a paginated list of all WhatsApp templates available for a specific Phone ID. Templates can be filtered by status and include details such as name, category, language, and status.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Path Parameters
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
#### Query Parameters
The maximum number of templates to return in the response. Default is 10.
The number of templates to skip before starting to return results. Used for pagination. Default is 0.
#### Response
Indicates whether the API call was successful.
Success or error message from the API.
The template list response containing:
An array of WhatsApp template objects, each containing:
The name of the WhatsApp template.
The language code of the template (e.g., "en", "en\_US").
The category of the template. Possible values: "MARKETING", "UTILITY", "AUTHENTICATION".
The approval status of the template. Possible values: "APPROVED", "PENDING", "REJECTED".
An array of template components that define the structure of the template.
Pagination information containing:
The total number of templates available.
The number of templates returned in this response.
The page size used for this request.
```json 200-Success theme={null}
{
"success": true,
"message": "Templates retrieved successfully",
"data": {
"data": [
{
"name": "welcome_message",
"language": "en",
"category": "UTILITY",
"status": "APPROVED",
"components": []
},
{
"name": "order_confirmation",
"language": "en_US",
"category": "MARKETING",
"status": "APPROVED",
"components": []
}
],
"pagination": {
"size": 25,
"count": 10,
"pageSize": 10
}
}
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid phone_id parameter"
}
```
```json 401-Unauthorized theme={null}
{
"success": false,
"message": "Unauthorized. Please check your API key and workspace ID."
}
```
```json 500-Internal Server Error theme={null}
{
"success": false,
"message": "Internal server error. Please try again later."
}
```
# Introduction to WhatsApp Messaging
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/introduction
WhatsApp Business API messaging
Welcome to the WhatsApp API documentation. This guide provides developers with comprehensive information on sending various types of WhatsApp messages through the Zixflow AI Ingestion Service.
### Getting Started with WhatsApp
To send WhatsApp messages, you need to:
1. Set up a WhatsApp Business Account through Zixflow
2. Obtain your Phone ID from the Zixflow WhatsApp Settings page
3. Generate an API key: go to **Developers** under Admin settings at [Zixflow Platform](https://app.zixflow.com/settings/workspace/developer), and assign the respective permissions for the channel (all channels recommended for future flexibility)
4. Use the appropriate endpoint for your message type
### Template-Based Messaging
For template-based messages, use **Get WhatsApp Variables** to understand the template structure—including variable names and value types required for delivery. The API returns a JSON response with template variable details; if no variables are required, you can send the message without them.
The **Send WhatsApp Template** API delivers messages using approved templates. Provide valid variables that match the template requirements and format; incorrect or missing variables will prevent the message from being sent. Always ensure variable data is accurate and adheres to the template structure.
WhatsApp messages can only be sent using approved templates from WhatsApp's official template library. Use the appropriate template and ensure it complies with WhatsApp's guidelines for successful delivery.
### Supported Message Types
The WhatsApp API supports the following message types:
* **Text Messages**: Send plain text messages with optional preview URLs
* **Media Messages**: Send images, videos, audio files, and documents
* **Location Messages**: Send location information
* **Contact Messages**: Send contact cards (vCards)
* **Sticker Messages**: Send stickers
* **Reaction Messages**: Send message reactions
* **Interactive Messages**: Send interactive messages with buttons, lists, carousels, and more
* **Templates**: Send pre-approved WhatsApp templates with variables
### Features
* Rich media support (images, videos, audio, documents)
* Interactive elements (buttons, lists, carousels)
* Template-based messaging for compliance
* Post-back URLs for delivery status tracking
* Support for all WhatsApp Business API features
# Send WhatsApp Audio Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/audio-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/audio/send
Send an audio message via WhatsApp
#### Description
This endpoint allows you to send an audio message via WhatsApp. You can send audio files by providing a media ID (for uploaded media) or a link to hosted media.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Audio message object.
Media ID if using uploaded media (previously uploaded via media upload API).
URL of the hosted audio file if using hosted media. The audio file must be publicly accessible.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Contact Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/contact-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/contacts/send
Send a contact card (vCard) message via WhatsApp
#### Description
This endpoint allows you to send a contact card (vCard) message via WhatsApp. The contact information will be displayed as a shareable contact card.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Array of contact objects. Each contact object contains:
Contact name object with fields: formatted\_name (required), first\_name, last\_name, middle\_name, suffix, prefix.
Array of phone objects with fields: phone (required), type, wa\_id.
Array of email objects with fields: email (required), type.
Array of address objects with fields: street, city, state, zip, country, type.
Array of URL objects with fields: url (required), type.
Organization object with fields: company, department, title.
Birthday in YYYY-MM-DD format.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Document Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/document-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/document/send
Send a document message via WhatsApp
#### Description
This endpoint allows you to send a document message via WhatsApp. You can send documents by providing a media ID (for uploaded media) or a link to hosted media.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Document message object.
Media ID if using uploaded media (previously uploaded via media upload API).
URL of the hosted document if using hosted media. The document must be publicly accessible.
Optional caption for the document. Maximum length: 1024 characters.
Optional filename for the document.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Image Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/image-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/image/send
Send an image message via WhatsApp
#### Description
This endpoint allows you to send an image message via WhatsApp. You can send images by providing a media ID (for uploaded media) or a link to hosted media.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Image message object.
Media ID if using uploaded media (previously uploaded via media upload API).
URL of the hosted image if using hosted media. The image must be publicly accessible.
Optional caption for the image. Maximum length: 1024 characters.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive Address Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-address-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/address/send
Send an interactive address message via WhatsApp
#### Description
This endpoint allows you to send an interactive address message via WhatsApp. Users can select an address from the interactive options provided.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive address message object containing header, body, footer, and action.
Type of interactive message. Use "address" for address messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing address parameters and options.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive List Buttons Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-list-buttons-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/list-buttons/send
Send an interactive list buttons message via WhatsApp
#### Description
This endpoint allows you to send an interactive list buttons message via WhatsApp. Users can select an option from a list of buttons.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive list buttons message object containing header, body, footer, and action.
Type of interactive message. Use "list" for list buttons messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing button list with sections and rows. Each row has an ID, title, and description.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive Location Request Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-location-request-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/location-request/send
Send an interactive location request message via WhatsApp
#### Description
This endpoint allows you to send an interactive location request message via WhatsApp. Users can share their location through the interactive button.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive location request message object containing body, footer, and action.
Type of interactive message. Use "location\_request" for location request messages.
Body object with text content requesting location.
Optional footer object with text.
Action object containing location request button with name.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive Media Carousel Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-media-carousel-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/media-carousel/send
Send an interactive media carousel message via WhatsApp
#### Description
This endpoint allows you to send an interactive media carousel message via WhatsApp. Users can swipe through multiple media cards with interactive buttons.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive media carousel message object containing header, body, footer, and action.
Type of interactive message. Use "carousel" for media carousel messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing carousel cards. Each card can have media, text, and buttons.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive Product Carousel Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-product-carousel-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/product-carousel/send
Send an interactive product carousel message via WhatsApp
#### Description
This endpoint allows you to send an interactive product carousel message via WhatsApp. Users can browse through multiple product cards with interactive buttons.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive product carousel message object containing header, body, footer, and action.
Type of interactive message. Use "product\_carousel" for product carousel messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing product carousel cards. Each card represents a product with product\_retailer\_id, media, text, and buttons.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive Reply Buttons Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-reply-buttons-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/reply-buttons/send
Send an interactive reply buttons message via WhatsApp
#### Description
This endpoint allows you to send an interactive reply buttons message via WhatsApp. Users can tap buttons to send quick replies.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive reply buttons message object containing header, body, footer, and action.
Type of interactive message. Use "button" for reply buttons messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing buttons array. Each button has type (reply) and contains reply object with id and title.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Interactive URL Button Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/interactive-url-button-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/interactive/url-button/send
Send an interactive URL button message via WhatsApp
#### Description
This endpoint allows you to send an interactive URL button message via WhatsApp. Users can tap a button to open a URL.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Interactive URL button message object containing header, body, footer, and action.
Type of interactive message. Use "button" for URL button messages.
Optional header object with type and text/image/video/document.
Body object with text content.
Optional footer object with text.
Action object containing buttons array. Each button has type (url) and contains url object with url.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Location Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/location-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/location/send
Send a location message via WhatsApp
#### Description
This endpoint allows you to send a location message via WhatsApp with latitude and longitude coordinates.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Location message object.
The longitude coordinate of the location.
The latitude coordinate of the location.
Optional name for the location.
Optional address for the location.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Reaction Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/reaction-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/reaction/send
Send a reaction message via WhatsApp
#### Description
This endpoint allows you to send a reaction (emoji) to a specific message via WhatsApp.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Reaction message object.
The ID of the message to react to.
The emoji to use as a reaction (e.g., "👍", "❤️", "😊").
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Sticker Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/sticker-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/sticker/send
Send a sticker message via WhatsApp
#### Description
This endpoint allows you to send a sticker message via WhatsApp. Stickers must be in WebP format and can be sent using a media ID or a link to hosted media.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Sticker message object.
Media ID if using uploaded media (previously uploaded via media upload API).
URL of the hosted sticker if using hosted media. The sticker must be publicly accessible and in WebP format.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Text Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/text-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/text/send
Send a text message via WhatsApp
#### Description
This endpoint allows you to send a text message via WhatsApp. You can include URL previews and format the text content.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Text message object containing the message content.
The text content of the message. Maximum length: 4096 characters. Supports formatting and URLs.
Whether to include a URL preview box. Default: false. URL previews are only rendered after a template message has been sent or the user initiates a conversation.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Video Message
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/messages/video-message
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/message/video/send
Send a video message via WhatsApp
#### Description
This endpoint allows you to send a video message via WhatsApp. You can send videos by providing a media ID (for uploaded media) or a link to hosted media.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
Video message object.
Media ID if using uploaded media (previously uploaded via media upload API).
URL of the hosted video if using hosted media. The video must be publicly accessible.
Optional caption for the video. Maximum length: 1024 characters.
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
# Send WhatsApp Template
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/send-template
POST https://api-ai.zixflow.com/api/ingest/whatsapp/v1/template/send
Send a WhatsApp message using a predefined template
#### Description
This endpoint allows you to send a WhatsApp message using a predefined template. Templates must be approved by WhatsApp and can include variables that are replaced with actual values when the message is sent.
#### Headers
Your API key for authentication.
Your workspace ID for authentication.
#### Body
The recipient's phone number in international format. Must include the country code without the leading plus sign (+) or any spaces, dashes, or other special characters. Format: country code followed by the phone number. Example: `919876543210` (India: country code `91` + number `9876543210`).
The unique Phone ID associated with your WhatsApp Business account. You can find this on the Zixflow WhatsApp Settings page or by calling the Get WhatsApp Accounts endpoint.
The name of the WhatsApp template to use.
Key-value pairs for template variables. Keys should match the variable names in the template, and values will replace the placeholders.
The language code for the template (e.g., "en", "en\_US").
(Optional) To receive post-back notifications about the sent message as it is delivered or failed. When a message status changes (delivered, failed, etc.), a notification will be sent to this URL. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for the payload format and field descriptions.
(Optional) The data that will be sent back on the post\_back\_url with the notification. This can be used as an identifier for the sent message if the client wants to track or correlate messages with their own system. See [Postback Notifications](/api-reference/zixflow-ai/postback-notifications) for how it appears in the notification.
#### Response
Indicates whether the request was successful. true if the request was accepted and queued, false otherwise.
Success message when the request succeeded, or error message when it failed.
Present only when success is true. Unique identifier for this request; use it for tracking and for correlating with postback notifications.
```json 200-Success theme={null}
{
"success": true,
"message": "WhatsApp request queued successfully",
"request_id": "f8df74d1-11fa-4485-b164-b9a65358f4fc"
}
```
```json 400-Bad Request theme={null}
{
"success": false,
"message": "Invalid template or missing variables"
}
```
# Authentication Templates
Source: https://docs.zixflow.com/api-reference/zixflow-ai/whatsapp/whatsapp-auth-template
If your mobile app offers users the option to receive one-time passwords or verification codes via WhatsApp, you must use an authentication template.
##### How to Create an Auth Template in Zixflow
* Navigate to the **WhatsApp Templates** section.
* Click on **Authentication Template**.
* Choose the desired template type.
* Submit the template for approval.
Once approved, you can start using it!
##### Authentication templates consist of:
* Fixed preset text: `` is your verification code.
* An optional security disclaimer: For your security, do not share this code.
* An optional expiration warning: This code expires in `` minutes.
* Either a one-tap autofill button, a copy code button, or no button at all if using zero-tap.
One-tap autofill buttons are the preferred solution as they offer the best user experience. However, one-tap autofill buttons are currently only supported on Android and require additional changes to your app's code.
See [additional guidelines](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fupdates-to-pricing%2Fnew-template-guidelines%23authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) for when it's appropriate to use an authentication template.
#### One-Tap Autofill Authentication Templates
Authentication templates include a one-tap autofill button.
When a WhatsApp user taps the autofill button, the WhatsApp client triggers an activity which opens your app and delivers it the password or code.
See [One-Tap Autofill Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fautofill-button-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Copy Code Authentication Templates
Copy code authentication templates allow you to send a one-time password or code along with a copy code button to your users.
When a WhatsApp user taps the copy code button, the WhatsApp client copies the password or code to the device's clipboard. The user can then switch to your app and paste the password or code into your app.
See [Copy Code Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fcopy-code-button-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Zero-Tap Authentication Templates
Zero-tap authentication templates allow your users to receive one-time passwords or codes via WhatsApp without having to leave your app.
When a user in your app requests a password or code and you deliver it using a zero-tap authentication template, the WhatsApp client broadcasts the included password or code, which your app can then capture with a broadcast receiver.
See [Zero-Tap Authentication Templates](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2Fzero-tap-authentication-templates\&event_type=click\&last_nav_impression_id=0Yzyj1quUvU8pbXEv\&max_percent_page_viewed=50\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fbusiness-management-api%2Fauthentication-templates%2F\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dbusiness-management-api%26path3%3Dauthentication-templates\®ion=apac\&scrolled=true\&session_id=1fHwFuthKYspVy1CX\&site=developers) to learn how to use them.
#### Best Practices
* Confirm the user's WhatsApp phone number before sending the one-time password or code to that number.
* Make it clear to your user that the password or code will be delivered to their WhatsApp phone number, especially if you offer multiple ways for the user to receive password or code delivery. See Getting Opt-In for additional tips.
* When the user pastes the password or code into your app, or your app receives it as part of the one-tap autofill button flow, make it clear to the user that your app has captured it.
See also [Best Practices for Authenticating Users via WhatsApp](https://developers.facebook.com/docs/whatsapp/business-management-api/authentication-templates/authentication-best-practices).
# Whatsapp Authentication-International Rates
Specific countries have an authentication-international rate in our whatsapp pricing. If you open an authentication conversation with a WhatsApp user whose country calling code is for a country that has an authentication-international rate, you will be billed that country's authentication–international rate if:
* your business is eligible for authentication-international rates
* your business is based in another country (see Primary Business Location explained below)
* the conversation was opened on or after your start time for that country
For example, if your business is based in India and you open an authentication conversation with a WhatsApp user who has a +91 (India) country calling code, you will not be billed the authentication-international rate, since you are based in the same country as the user. If your business is based in Indonesia, however, you will be billed the authentication-international rate, if you meet all of the criteria above.
#### Eligibility
If your business opens more than 750K conversations in a moving 30-day period across all of your WhatsApp Business Accounts with WhatsApp users whose country calling codes are for a country that has an authentication-international rate, it will be deemed eligible for authentication-international rates.
Once deemed eligible, WhatsApp will set your start times 30 days out for each country that has an authentication-international rate. In addition, WhatsApp will attempt to determine your primary business location using publicly-available information.
WhatsApp will then send you an eligibility email that includes these start times and the country that WhatsApp set as your primary business location (if WhatsApp were able to determine the country). This provides you with 30 days notice before authentication-international rates apply.
Note that eligibility is permanent. Once your business is deemed eligible, all authentication conversations opened on or after your start time will be charged the authentication-international rate in markets where authorization-international rates apply.
#### Start Times
Start times are business- and country-specific timestamps. They indicate when newly-opened authentication conversations are subject to authentication-international rates. Authentication conversations opened by your business with WhatsApp users in these countries on or after these dates only will be charged authentication-international rates.
Start times are set when your business is first deemed eligible for authentication-international rates, and are 30 days from your eligibility date, so you will always have 30-days notice before the authentication-international rate applies.
Start times are included in your eligibility email.
#### Primary Business Location
Your primary business location is the country where your business is based. It will appear in the Business Manager under the Primary Business Location field starting May 1, 2024, if WhatsApp are able to determine where your business is based using publicly-available information.
Examples of publicly-available information include:
* Where your business may be publicly-traded and listed
* Your business's corporate structure (where a parent or may be based or publicly-traded)
WhatsApp will attempt to determine where your business is based when:
* It is deemed eligible for authentication-international rates
* You edit your primary business location using the Business Manager.
This process can take up to 3 business days. The outcome of this determination can be:
* Verified — WhatsApp determined where your business is based and set your primary business location to this country.
* Need more information — WhatsApp require more information in order to make a determination.
* Rejected — WhatsApp disagreed with the country you designated in the Business Manager (if you used it to edit the Primary Business Location field)
You will be notified of the outcome in your initial eligibility email, or in a separate email if you used the Business Manager to edit your location.
If rejected or if WhatsApp need more information, or if you disagree with the country it determined to be the primary business location, you can use the Business Manager to edit your location.
Note that if your primary business location status is not verified but you are past your start time for a given country, any authentication conversation that you open with a WhatsApp user in that country will be billed the authentication-international rate.
#### Editing Your Primary Business Location
You can edit your business's primary business location at any time using WhatsApp Business Manager:
[Business Settings](https://developers.facebook.com/micro_site/url/?click_from_context_menu=true\&country=IN\&destination=https%3A%2F%2Fbusiness.facebook.com%2Fsettings%2F\&event_type=click\&last_nav_impression_id=0cpNYUEOYwPxwso8D\&max_percent_page_viewed=56\&max_viewport_height_px=1052\&max_viewport_width_px=1920\&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fpricing%2Fauthentication-international-rates\&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fdocs%2Fnav%2F%3Fpath1%3Dwhatsapp%26path2%3Dpricing%26path3%3Dauthentication-international-rates\®ion=apac\&scrolled=true\&session_id=0txnhiCp2uAynN61d\&site=developers) > Business info > Primary Business Location > Edit (button)
The Primary Business Location field in the Business Manager will also display a status:
* Verified — WhatsApp have verified your business's primary location.
* Pending verification — WhatsApp in the process of determining your business's primary location.
* Rejected — WhatsApp disagreed with the country you designated, based on publicly available information and what you included when you edited your location. You can manually edit your location again and include different information as part of your submission.
#### Examples
A business with an Indonesia primary business location opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| Indonesia | - | - | Authentication |
| India | No | - | Authentication |
| India | Yes | No | Authentication |
| India | Yes | Yes | Authentication-International |
A business with an India primary business location opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| India | - | - | Authentication |
| Indonesia | No | - | Authentication |
| Indonesia | Yes | No | Authentication |
| Indonesia | Yes | Yes | Authentication-International |
A business with a primary business location that does not have an authentication-international rate opens an authentication conversation with a WhatsApp user:
| User location | Is business eligible? | Is on/after start time? | Rate billed |
| ------------- | --------------------- | ----------------------- | ---------------------------- |
| Indonesia | No | - | Authentication |
| Indonesia | Yes | No | Authentication |
| Indonesia | Yes | Yes | Authentication-International |
| India | No | - | Authentication |
| India | Yes | No | Authentication |
| India | Yes | Yes | Authentication-International |
# Attributes
Source: https://docs.zixflow.com/documentation/events/attributes
Profile, device, and event attributes — when to use attributes vs events.
Attributes store the current state of users, devices, and events. Understanding when to use attributes vs events is critical for building an effective data model that supports both analytics and personalization.
***
## What are Attributes?
Attributes are **facts** that describe who a user is, what device they're on, or what happened in an event. Think of them as the labels on a record — name, email, plan type, device model, event revenue.
While events are verbs (actions), attributes are adjectives (descriptions).
### The Simple Definition
If you can describe it as "This user IS X" or "This event HAS property Y," it's an attribute.
**Examples:**
* "Sarah's email **is** [sarah@example.com](mailto:sarah@example.com)" → User attribute: `email`
* "John's plan type **is** Enterprise" → User attribute: `plan_type`
* "This device **is** an iPhone 15 Pro on iOS 17" → Device attribute: `model`, `os_version`
* "This purchase **had** revenue of \$89.99" → Event property: `revenue`
### Why Attributes Matter
Attributes are essential because they enable:
1. **Personalization**: `"Hi {{name}}, your {{plan}} trial expires in {{days_remaining}} days"`
2. **Targeting**: Send campaigns only to users matching specific criteria
3. **Context**: Understand who's behind each event ("This purchase was from an Enterprise customer")
4. **Enrichment**: Connect user data from your CRM, support system, or other tools
5. **Current state queries**: "How many Pro plan users do we have right now?"
### The Key Difference: State vs Action
**Attributes store current state** (one value that changes over time):
* User's current email: `sarah@example.com` → changes if they update it
* User's current plan: `pro` → changes when they upgrade to `enterprise`
* Last login date: `2026-05-04` → updates every time they log in
**Events record actions** (immutable history):
* Event: `Updated Email` at `2026-01-15` → never changes, always part of history
* Event: `Upgraded Plan` at `2026-03-20` → permanent record of this action
* Event: `Logged In` at `2026-05-04 10:30:15` → specific moment captured
**The rule:** Use attributes when you need to know "what IS true about this user right now." Use events when you need to know "what DID this user do."
```mermaid theme={null}
flowchart LR
subgraph state [Attributes current state]
plan[plan enterprise]
email[email sarah]
end
subgraph actions [Events immutable history]
signedUp[Signed Up]
upgraded[Upgraded Plan]
purchased[Purchased]
end
upgraded -.->|updates| plan
```
### Real-World Example
**Sarah's user attributes:**
```javascript theme={null}
{
email: "sarah@example.com", // Current email
name: "Sarah Johnson", // Current name
plan_type: "enterprise", // Current plan (was 'pro' before)
company: "Acme Corp", // Current company
signup_date: "2026-01-15", // Doesn't change
total_purchases: 12, // Increments with each purchase
lifetime_value: 1247.88, // Calculated from all purchases
last_login_at: "2026-05-04T10:30:15Z" // Updates each login
}
```
**Sarah's event history:**
```javascript theme={null}
[
{ event: "Signed Up", timestamp: "2026-01-15T09:00:00Z" },
{ event: "Purchased", timestamp: "2026-01-20T14:30:00Z", amount: 89.99 },
{ event: "Upgraded Plan", timestamp: "2026-03-20T11:15:00Z", from: "pro", to: "enterprise" },
{ event: "Purchased", timestamp: "2026-05-01T16:45:00Z", amount: 149.99 },
{ event: "Logged In", timestamp: "2026-05-04T10:30:15Z" }
// ... 100 more events
]
```
**Notice:**
* Attributes show her **current state**: She's enterprise now (not pro)
* Events show her **complete history**: We can see she upgraded on March 20th
* Together they give the full picture: Who she is + What she's done
***
## Attributes vs Events: The Data Modeling Decision
**Events** = Actions (verbs) → `user.track('Upgraded Plan')`\
**Attributes** = State (adjectives) → `user.plan_type = 'enterprise'`
### The Rule
**Use attributes when:**
* You need current state: "What plan are they on right now?"
* You need to personalize: `"Hi {{name}}, your trial ends {{trial_ends_at}}"`
* It's a fact about the user: Demographics, account info, preferences
**Use events when:**
* You need to trigger workflows: "When user upgrades → send thank-you email"
* You need analytics: "How many users upgraded this month?"
* You need history: "When did they last log in?"
* It's an action: Clicked, viewed, purchased, upgraded
### Common Mistake: Tracking State as Events
**❌ Wrong:**
```javascript theme={null}
// Don't do this
track('Email Changed', { new_email: 'user@new-domain.com' });
track('Became Enterprise Customer');
track('Credit Card Updated');
```
**Why it's wrong:** These are state updates, not user actions. You're creating noise in your event stream.
**✅ Right:**
```javascript theme={null}
// Update the attribute
setProfileAttributes({
email: 'user@new-domain.com',
plan_type: 'enterprise',
payment_method_updated_at: '2026-05-04T10:00:00Z'
});
// Track the action that caused the change (if relevant)
track('Upgraded Plan', {
from_plan: 'pro',
to_plan: 'enterprise',
mrr_change: 200
});
```
**Why it's right:** Attribute stores current state for queries and personalization. Event records the action for analytics and workflow triggers.
***
## Three Types of Attributes
### 1. **User Attributes**
User attributes are facts stored on the user profile, identified by `user_id`. They come in two kinds: **system-defined fields** (always present, set by the platform) and **traits** (custom key-value data you send via `identify()` or `setProfileAttributes()`).
#### System-Defined Fields
These fields exist on every user profile automatically:
| Field | Description |
| ------------------------------------------------ | ---------------------------------------------------------------- |
| `id` | Internal Sonyflake BIGINT primary key |
| `user_id` | Your application's identifier for this user |
| `anonymous_id` | The anonymous ID before identification |
| `name` | From traits |
| `email` | From traits |
| `phone` | From traits |
| `timezone` | IANA timezone, e.g. `America/New_York` |
| `language` | ISO 639-1 code, e.g. `en` |
| `email_opt_out` | Channel opt-out status (1=not opted out, 2=opted out, 3=unknown) |
| `sms_opt_out` | Same 3-state model |
| `whatsapp_opt_out` | Same 3-state model |
| `rcs_opt_out` | Same 3-state model |
| `push_opt_out` | Same 3-state model |
| `*_opt_out_at` | Timestamp when each opt-out occurred |
| `first_seen_at` | Timestamp of first identify call |
| `last_seen_at` | Timestamp of most recent identify call |
| `last_activity_at` | Timestamp of most recent event |
| `is_deleted` | Set to true by `User Deleted` lifecycle event |
| `is_suppressed` | Set to true by `User Suppressed` lifecycle event |
| `deleted_at` / `suppressed_at` | When those states were applied |
| `first_campaign_source/medium/name/term/content` | First-touch UTM attribution |
| `first_referrer` | Referring URL from first visit |
| `signup_platform` | Platform at time of first identify |
| `creation_source` | How the profile was created |
| `external_id` | Optional external system identifier |
| `customer_created_at` | User's creation time in your own system |
| `created_at` / `updated_at` / `processed_at` | System timestamps |
#### Traits (Custom Attributes)
Anything you pass in `traits` via `identify()` or via `setProfileAttributes()` is stored as a custom trait and merged with existing data:
```dart theme={null}
// On identify (signup/login)
Zixflow.instance.identify(
userId: 'user_12345',
traits: {
'plan_type': 'enterprise',
'company': 'Acme Corp',
'mrr': 499,
'trial_ends_at': '2026-06-01T00:00:00Z',
},
);
// Update anytime — merges with existing traits
Zixflow.instance.setProfileAttributes({
'last_upgraded_at': '2026-05-04T10:00:00Z',
'feature_flags': ['beta_reports', 'new_dashboard'],
});
```
Traits are schema-less — you define the keys. All trait keys are automatically catalogued for use in queries and targeting.
***
### 2. **Device Attributes**
Device attributes are facts stored on a device record, identified by `device_id` (derived from the push token). Like user attributes, they split into system-defined fields and custom traits.
#### System-Defined Fields
| Field | Description |
| --------------------------------------------------- | ---------------------------------------------- |
| `device_id` | Internal device ID (derived from token hash) |
| `token` | Push token (FCM/APNs) |
| `platform` | `ios` or `android` |
| `manufacturer` | e.g. `Apple`, `Samsung` |
| `model` | e.g. `iPhone 15 Pro`, `Pixel 8` |
| `os_name` | e.g. `iOS`, `Android` |
| `os_version` | e.g. `17.4.1`, `14` |
| `app_version` | Your app's version string |
| `sdk_version` | Zixflow SDK version |
| `screen_width` / `screen_height` / `screen_density` | Display info |
| `locale` | e.g. `en-US` |
| `timezone` | IANA timezone |
| `carrier` | Mobile carrier name |
| `active` | `false` after `Device Deleted` lifecycle event |
| `push_enabled` | Whether the device can receive push |
| `last_used_at` | Last time SDK sent an event from this device |
| `created_at` / `updated_at` / `processed_at` | System timestamps |
#### Traits (Custom Attributes)
Custom key-value data you attach to a specific device:
```dart theme={null}
Zixflow.instance.setDeviceAttributes({
'notification_categories': ['promotions', 'transactional'],
'dark_mode_enabled': true,
'preferred_locale': 'en-US',
});
```
**Use cases:**
* Target specific app versions: "Send 'Update Available' push to devices on v1.x"
* Platform-specific campaigns: "iOS-only feature announcement"
* Notification category filtering
***
### 3. **Event Properties**
Event properties are the custom key-value pairs attached to individual events. Unlike user or device attributes (which describe current state), event properties are **immutable** — they record the details of a specific moment in time.
#### Anatomy of an Event with Properties
```json theme={null}
{
"event_name": "Order Completed",
"user_id": "user_12345",
"anonymous_id": null,
"timestamp": "2026-05-04T14:32:15Z",
"properties": {
"order_id": "ord_789",
"revenue": 129.99,
"currency": "USD",
"product_name": "Nike Air Max 90",
"payment_method": "credit_card"
},
"context": {
"platform": "ios",
"app": { "version": "2.1.0" },
"os": { "name": "iOS", "version": "17.4.1" },
"device": { "model": "iPhone 15 Pro" },
"locale": "en-US",
"timezone": "America/New_York"
}
}
```
#### System-Defined Event Fields
Every event has these fields set automatically:
| Field | Description |
| -------------- | --------------------------------------------------------------------- |
| `event_name` | The name you pass to `track()` / `screen()` / `page()` |
| Event type | Track, screen, page, or identify (set by which SDK method you call) |
| `user_id` | Set if user is identified |
| `anonymous_id` | Set if user is anonymous |
| `timestamp` | When the event occurred (millisecond precision) |
| `context` | Platform, device, OS, app version, locale, timezone — captured by SDK |
#### Custom Properties
The `properties` object is fully custom — you define the keys and values:
```dart theme={null}
Zixflow.instance.track('Purchased', properties: {
'order_id': 'ord_123',
'revenue': 89.99,
'currency': 'USD',
'product_name': 'Red Sneakers',
'payment_method': 'credit_card',
});
```
All property keys are automatically catalogued for use in queries and campaign conditions.
**Note:** Event properties live on the event record (with their own retention), not on the user profile. User attributes store current state; event properties describe that specific action.
***
## Automatic Attribute Discovery
Zixflow automatically learns what attributes exist based on the data you send - no need to define a schema upfront.
### How It Works
**You track an event with properties:**
```javascript theme={null}
Zixflow.track('Purchased', {
order_id: 'ord_123',
product_name: 'Enterprise Plan',
revenue: 499.00,
currency: 'USD',
payment_method: 'credit_card'
});
```
**Zixflow automatically:**
1. Discovers all the property names (order\_id, product\_name, revenue, etc.)
2. Figures out their data types (order\_id is text, revenue is a number)
3. Makes them available for querying and filtering
**Now you can:**
* Segment: "Users who purchased where product\_name = 'Enterprise Plan'"
* Analyze: "Top 10 products by revenue"
* Filter: "Show events where payment\_method = 'credit\_card'"
**Benefits:**
✅ **No schema setup** — Just start tracking, Zixflow learns automatically\
✅ **Self-documenting** — See what attributes you're actually using\
✅ **Type checking** — Zixflow warns if data types don't match\
✅ **Easy filtering** — All attributes available for querying
***
## Reserved Attribute Names
These attribute names are reserved for system use. Don't use them as custom attributes:
**User-level:**
* `id`, `workspace_id`, `user_id`, `email`, `anonymous_id`
* `created_at`, `updated_at`, `last_seen_at`
**Device-level:**
* `device_id`, `device_token`, `platform`, `last_seen_at`
**If you try to set a reserved attribute:**
* System logs a warning
* Value is ignored (not written)
* Original system value preserved
***
**Next:** Understand how events flow through Zixflow → [The Data Journey](/documentation/events/data-journey)
# Communication Channels Overview
Source: https://docs.zixflow.com/documentation/events/channels-overview
Available communication channels and when to use each.
How you reach users — push, email, SMS, and more — and how channel preferences work in Zixflow.
**Available today:** Mobile push notifications (iOS / Android). Other channels are planned; opt-out fields already exist on profiles so you can store consent early.
***
## What Are Communication Channels?
A channel is a pathway for outbound messages:
| Channel | Best for |
| ------------------ | ------------------------------------------ |
| **Push** | Urgent, short alerts on mobile |
| **Email** | Rich content, newsletters, receipts |
| **SMS** | Critical, high-urgency short messages |
| **In-app** | Guidance while the app is open |
| **Web push** | Browser alerts for web users |
| **WhatsApp / RCS** | Conversational messaging (where available) |
Different channels trade off reach, speed, visibility, cost, and content richness. Use the channel that matches urgency and message complexity.
***
## Channel Opt-Out Model
Every profile stores opt-out status per channel:
| Status | Value | Meaning |
| --------------- | ----- | ------------------------------------------------ |
| `NOT_OPTED_OUT` | 1 | Reachable (default for Push, SMS, WhatsApp, RCS) |
| `OPTED_OUT` | 2 | Explicitly opted out — sends suppressed |
| `UNKNOWN` | 3 | Intent not established (default for Email) |
Each channel also has an `_opt_out_at` timestamp.
```dart theme={null}
Zixflow.instance.setProfileAttributes({
'sms_opt_out': 2,
'sms_opt_out_at': DateTime.now().toIso8601String(),
});
Zixflow.instance.setProfileAttributes({
'push_opt_out': 1, // NOT_OPTED_OUT
});
```
**User Suppression** blocks **all** channels at once (server-side lifecycle events `User Suppressed` / `User Unsuppressed`). See [User Preferences & Consent](/documentation/events/user-preferences).
***
## Push Notifications (Available)
**Platform:** iOS, Android\
**Delivery:** APNs / FCM\
**Latency:** Typically under 5 seconds\
**Opt-in:** System permission dialog\
**Opt-out field:** `push_opt_out` (default `NOT_OPTED_OUT`)
**Supported today:**
* Device token registration and multi-device profiles
* Platform routing (iOS → APNs, Android → FCM)
* Profile `push_opt_out` / `push_opt_out_at`
* Deep linking
* Delivery / open / action tracking — [Push Notification Tracking](/documentation/events/push-notification-tracking)
**When to use:** Order updates, payment failures, cart reminders, real-time alerts.
Setup guides: [Devices & Push](/documentation/events/devices-and-push-notifications) and the [SDK push docs](/documentation/sdk/flutter/push-notifications).
***
## Coming Soon
These channels are not live for delivery yet. Opt-out fields are already on the profile so you can capture consent ahead of launch:
| Channel | Opt-out fields | Notes |
| --------------- | ------------------------------------------ | ------------------------------------ |
| Email | `email_opt_out` / `email_opt_out_at` | Default `UNKNOWN` until confirmation |
| SMS | `sms_opt_out` / `sms_opt_out_at` | Default `NOT_OPTED_OUT` |
| WhatsApp | `whatsapp_opt_out` / `whatsapp_opt_out_at` | Default `NOT_OPTED_OUT` |
| RCS | `rcs_opt_out` / `rcs_opt_out_at` | Default `NOT_OPTED_OUT` |
| In-app messages | — | Rendered in-app when available |
| Web push | — | Browser Push API |
***
## Choosing a Channel (Pattern)
Ask:
1. **How urgent?** Critical → Push or SMS · Important but not urgent → Email · Contextual → In-app
2. **How much detail?** Quick alert → Push/SMS · Long explanation → Email · Interactive → In-app
3. **Where is the user?** In app → In-app · Away → Push/Email
### Example: Cart recovery (orchestration pattern)
This is a common multi-channel pattern. **Today you can implement the push steps**; email/SMS steps apply when those channels ship.
```mermaid theme={null}
flowchart TD
cart[User adds to cart] --> wait1[Wait 2 hours]
wait1 --> noBuy{Purchased?}
noBuy -->|No| push[Send push reminder]
noBuy -->|Yes| stop[Stop]
push --> wait2[Wait 24 hours]
wait2 --> stillNo{Purchased?}
stillNo -->|No| email[Send email with product images]
stillNo -->|Yes| stop
email --> wait3[Wait 72 hours]
wait3 --> final{Purchased?}
final -->|No| offer[Push plus offer]
final -->|Yes| stop
```
| Use case | Primary | Backup |
| -------------------- | ------------ | ------ |
| Order shipped | Push | Email |
| Cart abandoned (2h) | Push | Email |
| Cart abandoned (24h) | Email | Push |
| Password reset | Email + Push | SMS |
| Weekly newsletter | Email | — |
| Flash sale | Push | SMS |
| Feature tip | In-app | Push |
| Payment failed | Email | Push |
***
## Push Best Practices
**Do:** Request permission after showing value; personalize; clear CTA + deep link; respect quiet hours.
**Don't:** Ask on first launch; send generic spam; ignore opt-outs or quiet hours.
***
**Next:** [User Preferences & Consent](/documentation/events/user-preferences)
# The Data Journey
Source: https://docs.zixflow.com/documentation/events/data-journey
End-to-end flow of event data from SDK capture to availability in Zixflow.
What happens when you track an event in your app? This guide walks through the path from capture to campaigns and analytics.
***
## The Big Picture
```mermaid theme={null}
flowchart TD
action[User action] --> capture[SDK captures event]
capture --> batch[Smart batching]
batch --> transmit[Secure HTTPS transmission]
transmit --> process[Validate, enrich, resolve identity]
process --> store[Stored and queryable]
store --> use[Campaigns, segments, analytics]
```
**Typical time to availability:** 2–5 seconds after the event leaves the device.
***
## What Happens at Each Stage
### 1. Event Capture (In Your App)
```javascript theme={null}
// User clicks "Add to Cart"
Zixflow.track('Product Added', {
product_id: 'prod_123',
product_name: 'Red Sneakers',
price: 89.99,
currency: 'USD'
});
```
The SDK automatically adds:
* Timestamp (millisecond precision)
* Device information (model, OS, screen size)
* User identification (`userId` if logged in, otherwise `anonymousId`)
* Location context (timezone, country, language when available)
Capture is near-instant and does not block your UI.
***
### 2. Smart Batching
Sending every event immediately would drain batteries and waste bandwidth. The SDK batches instead:
* Holds events in memory (typically up to \~20)
* Flushes when the batch is full, after \~30 seconds, or when the app backgrounds
* You can force an immediate send with `flush()` for high-priority actions (e.g. purchases)
```javascript theme={null}
Zixflow.track('Purchased', { order_id: 'ord_123', total: 499.99 });
Zixflow.flush(); // Send now — don't wait for the batch
```
***
### 3. Offline Support
```mermaid theme={null}
flowchart LR
offline[No network] --> queue[Events saved locally]
queue --> online[Connectivity returns]
online --> retry[Send in order with backoff]
retry --> zixflow[Zixflow]
```
* Events are stored securely on device when offline
* Retries use exponential backoff (avoids battery drain)
* Chronological order is preserved
***
### 4. Secure Transmission
* HTTPS / TLS in transit
* Workspace API key authenticates each request
* Invalid or tampered requests are rejected
* Keys can be rotated without downtime
***
### 5. Processing & Enrichment
**Validation:** Required fields and types are checked; malformed events are rejected.
**Identity resolution:**
* Anonymous events attach to a consistent anonymous profile
* Devices registered before signup migrate on `identify()` — see [Users & Identity](/documentation/events/users-and-identity)
* Multi-device activity links via shared `userId`
**Enrichment:** Server timestamp, geolocation from IP (when available), and campaign attribution tags.
**Deduplication:** Duplicate sends from network retries are collapsed so reports are not double-counted.
***
### 6. Storage & Availability
Once stored, events power:
* **Analytics** — funnels, retention, feature usage
* **Campaign triggers** — e.g. cart add without purchase within 2 hours → push reminder
* **Segments** — audiences based on behavior and attributes
***
**Next:** See real-world implementation examples → [E-commerce Tracking](/documentation/events/scenarios/ecommerce)
# Devices & Push Notifications
Source: https://docs.zixflow.com/documentation/events/devices-and-push-notifications
Device tracking, push notification tokens, and multi-device user profiles.
Every interaction with your product happens on a physical device—a phone, tablet, or computer. Zixflow tracks these devices so you can understand cross-device behavior, send timely push notifications, and deliver personalized experiences on the right screen at the right time.
This guide explains what devices are in the Zixflow system, how device tracking works, and how to leverage push notifications to re-engage users.
To register device tokens and configure push in your app, use the [SDK](/documentation/sdk/flutter/introduction) integration guides for [Flutter](/documentation/sdk/flutter/push-notifications), [React Native](/documentation/sdk/react-native/push-notifications), [iOS](/documentation/sdk/ios/push-notifications), [Android](/documentation/sdk/android/push-notifications), or [JavaScript (Web Push)](/documentation/sdk/javascript/web-push-notifications).
***
## What is a Device?
In Zixflow, a **device** represents a specific physical piece of hardware or browser that a user interacts with your product on. Think of it as one "endpoint" where your app or website runs.
### Examples of Devices:
* **Mobile phones**: iPhone 15 Pro, Samsung Galaxy S24, Google Pixel 8
* **Tablets**: iPad Pro, Samsung Tab, Amazon Fire Tablet
* **Desktop/Laptop browsers**: Chrome on MacBook, Firefox on Windows PC, Safari on iMac
* **Wearables** (if your app supports them): Apple Watch, Android smartwatches
### Why Track Devices?
Understanding devices gives you powerful capabilities:
1. **Cross-device user journey**: See how users move between phone, tablet, and desktop
2. **Device-specific experiences**: Optimize onboarding for small screens vs large screens
3. **Push notification delivery**: Reach users wherever they are, on any device
4. **Platform-specific analytics**: Compare iOS vs Android engagement
5. **App version tracking**: Know who's on old versions (for deprecation planning)
6. **Device health monitoring**: Identify devices that haven't been active (potential churn signals)
### How Zixflow Identifies Devices
Each device is uniquely identified by combining:
* **Platform**: iOS, Android, or Web
* **Device token**: A unique string from Apple (APNs), Google (FCM), or browser (Web Push)
* **User association**: Which user (`userId`) is currently logged in on this device — or, before login, which `anonymousId` belongs to this device
Zixflow accepts push tokens under any of the following property names, so your SDK doesn't need to use a specific field name:
| Property name | Description |
| -------------------- | --------------------------------- |
| `device_token` | Standard Flutter/mobile SDK field |
| `notification_token` | Alternative field name |
| `token` | Short-form fallback |
| `fcm_token` | Explicit FCM token field |
**Example:**
```json theme={null}
{
"deviceId": "dev_abc123",
"userId": "sarah@example.com",
"platform": "ios",
"deviceToken": "eA5w7X8yZ9K2...",
"model": "iPhone 15 Pro",
"osVersion": "17.4.1",
"appVersion": "2.1.0",
"lastSeen": "2026-06-24T14:30:00Z"
}
```
This device belongs to Sarah, runs on iOS, and can receive push notifications at token `eA5w7X8yZ9K2...`.
### Device Characteristics
Every device has attributes that help you understand and target it:
**System attributes** (automatically captured):
* **Platform**: iOS, Android, Web
* **Device model**: iPhone 15 Pro, Pixel 8, MacBook Pro
* **OS version**: iOS 17.4.1, Android 14, macOS 14.2
* **App version**: 2.1.0 (which version of your app they're using)
* **Screen size**: Useful for optimizing layouts
* **Timezone**: Send notifications at the right local time
* **Language**: Localize content appropriately
* **First seen**: When device was first registered
* **Last seen**: Last time device was active
**Custom attributes** (you can set):
* **Notification preferences**: Which types of notifications user wants
* **Device nickname**: "Sarah's iPhone", "Work Laptop"
* **Quiet hours**: Don't notify between 10 PM and 7 AM
* **Beta tester status**: Is this device part of your beta program?
### One User, Multiple Devices
Modern users interact with your product across multiple devices throughout the day:
```mermaid theme={null}
flowchart TB
user[User profile]
user --> morning[Morning: phone]
user --> afternoon[Afternoon: work laptop]
user --> evening[Evening: tablet]
```
Zixflow tracks each as a separate device but links them to the same user profile. This gives you:
* **Complete activity timeline**: See what they did across all devices
* **Smart notification routing**: Send push to the device they use most
* **Cross-device continuity**: "Continue where you left off" experiences
* **Platform comparison**: Does iOS or Android engage more?
***
## What are Push Notifications?
Push notifications are messages that appear on a user's device **even when your app isn't open**. They're the dings, buzzes, and banners that grab attention and bring users back to your product.
### Anatomy of a Push Notification
A push notification has several components:
**On iOS:**
```
┌────────────────────────────────┐
│ 📱 YourApp 5:45 PM │
│ Your trial expires in 3 days │ ← Title
│ Upgrade now to keep using │ ← Body
│ premium features │
└────────────────────────────────┘
```
**Components:**
* **App icon**: Your app's logo (automatic)
* **Title**: Main headline (bold, 40-50 chars)
* **Body**: Supporting message (120-150 chars)
* **Image** (optional): Thumbnail or hero image
* **Action buttons** (optional): "Yes" / "No", "View" / "Dismiss"
* **Deep link**: Where to take user when they tap
* **Badge count**: Number on your app icon (iOS)
* **Sound**: Alert sound (can be custom)
### How Push Notifications Work
```mermaid theme={null}
sequenceDiagram
participant User
participant App
participant Platform as APNs_or_FCM
participant Zixflow
User->>App: Grants notification permission
Platform->>App: Device token
App->>Zixflow: Register device token
Note over Zixflow: Campaign targets audience
Zixflow->>Platform: Send push
Platform->>User: Deliver notification
User->>App: Tap or dismiss
App->>Zixflow: Track delivered / opened / action
```
**Step 1: User grants permission**
```
Your App: "YourApp would like to send you notifications"
User: [Taps "Allow"]
```
**Step 2: Platform generates device token**
* iOS: Apple Push Notification service (APNs) generates token
* Android: Firebase Cloud Messaging (FCM) generates token
* Web: Browser generates subscription endpoint
**Step 3: Your app registers token with Zixflow**
```dart theme={null}
// Your app calls Zixflow SDK
Zixflow.registerDeviceToken('eA5w7X8yZ9K2...');
```
**Step 4: Zixflow stores device information**
* Links token to user profile (or anonymous profile before signup)
* Saves platform type (iOS/Android/Web)
* Records device attributes (model, OS version)
**Step 5: You trigger a notification campaign**
* Create campaign in Zixflow dashboard
* Define audience and message
**Step 6–7: Delivery**
* iOS → APNs, Android → FCM, Web → Browser Push
* Device shows banner/badge/sound based on user settings
**Step 8: User interacts**
* **Taps** → Opens app at deep link → track open metric
* **Action button** → Track action click
* See [Push Notification Tracking](/documentation/events/push-notification-tracking)
### Why Push Notifications Matter
Push notifications are incredibly powerful for engagement:
**Immediate reach**: Messages arrive in seconds, even if app is closed\
**High visibility**: Appear on lock screen, notification center, top of screen\
**Action-oriented**: Users can respond with one tap\
**Personalized**: Tailored to each user's behavior and preferences\
**Timely**: Triggered by user actions or schedules\
**Cross-platform**: Works on mobile, tablet, desktop, smartwatch
### Types of Push Notifications
**1. Transactional (time-sensitive, always sent)**
* "Your order has shipped!"
* "Payment failed, please update your card"
* "Someone mentioned you in a comment"
* **Characteristic**: User expects these, usually can't opt out
**2. Marketing (promotional, user can opt out)**
* "50% off this weekend only!"
* "New products you might like"
* "Your abandoned cart is waiting"
* **Characteristic**: User must opt in, respect preferences
**3. Lifecycle (product usage, educational)**
* "Complete your profile to unlock features"
* "Your trial expires in 3 days"
* "You haven't visited in a while—here's what's new"
* **Characteristic**: Help user get value, timing matters
**4. Alert/Monitoring (important updates)**
* "Your server is down"
* "Suspicious login detected"
* "Price drop on item you're watching"
* **Characteristic**: Time-critical, requires immediate attention
### Push Notification Best Practices
**✅ DO:**
* Ask for permission at the right moment (not immediately on first app open)
* Explain the value: "Get notified when your friends share photos"
* Personalize messages: Use names, reference their actions
* Respect time zones: Send during waking hours
* Make it actionable: Include clear next steps
* Test on real devices before sending
**❌ DON'T:**
* Spam users with too many notifications (leads to opt-outs)
* Send generic broadcasts: "Check out our app!"
* Forget to segment: Not everyone wants every notification
* Ignore user preferences: Respect opt-outs and quiet hours
* Use ALL CAPS or excessive emojis 🎉🔥💯 (looks spammy)
* Send without testing (broken deep links = bad experience)
***
## Device Registration Flow
### Anonymous device → identified user
```mermaid theme={null}
sequenceDiagram
participant App
participant Zixflow
App->>Zixflow: Register token with anonymousId
Note over Zixflow: Push works immediately
App->>Zixflow: identify userId + anonymousId
Note over Zixflow: Device migrates to user profile
```
### Step 1: SDK Initialization
When your app launches, the Zixflow SDK automatically:
1. Creates a unique ID for this device (for anonymous tracking before signup)
2. Collects device information (phone model, OS version, app version)
3. Tracks the device's timezone and language preferences
**Platform detection is automatic.** Zixflow infers the platform from context in this priority order:
1. `context.device.type` set by the SDK (`"ios"` / `"android"` / `"web"`)
2. `context.os.name` (e.g. `"iOS"`, `"Android"` → mapped to platform)
3. `context.library.name` (e.g. `"analytics.js"` or `"@zixflow/analytics-browser"` → `"web"`)
4. Presence of a browser `userAgent` → `"web"`
You do not need to manually set the platform — it is resolved automatically.
**What gets captured automatically:**
```json theme={null}
{
"device": {
"manufacturer": "Apple",
"model": "iPhone 14 Pro",
"name": "John's iPhone"
},
"os": {
"name": "iOS",
"version": "17.4.1"
},
"app": {
"version": "2.1.0",
"build": "403"
},
"locale": "en-US",
"timezone": "America/New_York"
}
```
**Why this matters:** You can segment users by device type, target specific app versions, and send messages at the right time in their timezone.
***
### Step 2: Push Token Registration (Mobile Only)
For push notifications, you need to register the device:
**Flutter SDK:**
```dart theme={null}
// Register for push notifications
Zixflow.registerDeviceToken('push_token_abc123...');
// Set device preferences (optional)
Zixflow.setDeviceAttributes({
'notification_categories': ['promotions', 'transactional'],
'preferred_language': 'en',
'quiet_hours_start': '22:00',
'quiet_hours_end': '08:00'
});
```
**What happens:**
* Device is registered in Zixflow with its push token
* You can now send push notifications to this specific device
* Device preferences are saved (so you can respect quiet hours, notification categories, etc.)
***
## Multi-Device Tracking
### Anonymous User with Multiple Devices
**Scenario:** User browses your website on desktop, then installs your mobile app (pre-signup).
**Device 1 (Web):**
```javascript theme={null}
// Browser generates anonymousId
anonymousId: "anon_web_abc-123"
// Events tracked
track('Viewed Product', { product_id: 'prod_456' });
track('Added to Cart', { product_id: 'prod_456' });
```
**Device 2 (Mobile):**
```dart theme={null}
// Mobile SDK generates different anonymousId
anonymousId: "anon_mobile_xyz-789"
// Events tracked
track('App Opened');
track('Viewed Product', { product_id: 'prod_456' });
```
**State:** Two separate anonymous profiles until the user identifies themselves. Once they sign up or log in, Zixflow automatically links both devices to the same user profile and enables push notifications on all of them.
***
### Device Registration Before Sign-Up
Devices do **not** need to be registered after sign-up. If the SDK registers a push token while the user is still anonymous, that device is fully active — you can send push notifications to it right away, just like any identified device. When `identify()` is later called, the device is automatically migrated to the identified user with no re-registration required.
```
Anonymous flow:
App opens → Device token registered (anonymous_id: anon_123)
← Push notifications can be sent to this device immediately
Sign-up (later):
identify(userId: "sarah", anonymousId: "anon_123")
← Device migrated to sarah's profile automatically
← Push notifications continue working — nothing changes for the device
```
This works automatically with no extra code in your app.
***
### Identified User with Multiple Devices
**Scenario:** User signs up on mobile, later logs in on desktop.
**Step 1: User signs up on mobile**
```dart theme={null}
// Mobile app
identify({
userId: 'user_12345',
anonymousId: 'anon_mobile_xyz-789',
traits: { email: 'user@example.com' }
});
// Register push token
registerDeviceToken('apns_token_mobile');
```
**Result:**
* Mobile device linked to this user
* Mobile anonymous history merged to user profile
* Device can now receive push notifications
**Step 2: User logs in on desktop**
```javascript theme={null}
// Web app
identify({
userId: 'user_12345',
anonymousId: 'anon_web_abc-123',
traits: { email: 'user@example.com' }
});
```
**Result:**
* Desktop anonymous history merged to user profile
* Both devices now linked to same user
* You can see complete cross-device journey
**What you can see for each user:**
* All registered devices (phone, tablet, desktop)
* Platform for each device (iOS/Android/Web)
* App version on each device
* When each device was last active
* Push notification status for each device
***
## Device System Events
The SDK and system automatically fire predefined device-activity events. They are separate from your business events and use a shorter retention window (\~30 days).
### Device Lifecycle Events (Auto-Tracked)
| Event Name | Triggered by |
| --------------------------- | ------------------------------------------------------------------ |
| `Application Installed` | SDK on first launch after install |
| `Application Opened` | App comes to foreground (cold start) |
| `Application Foregrounded` | App returns from background (warm open) |
| `Application Backgrounded` | App moves to background |
| `Application Crashed` | SDK detects unhandled crash on next launch |
| `Device Created or Updated` | SDK registers or refreshes device info |
| `Device Registered` | Push token successfully registered with Zixflow |
| `Device Updated` | Device attributes changed (OS upgrade, app update, token rotation) |
You do not need to call `track()` for any of these — the SDK fires them automatically.
Device lifecycle events are processed even when the user is **not yet identified** (anonymous). The device is registered and linked to the `anonymousId`, push notifications can be sent to it immediately, and it is automatically migrated to the user's profile when `identify()` is called.
### Device Deletion
`Device Deleted` is a server-side lifecycle event (not an auto-tracked SDK event). When triggered, it soft-deletes the device record:
* `active` is set to `false`
* `push_enabled` is set to `false`
* The device record is preserved (not removed) for audit purposes
This stops all push notifications to that device without destroying historical data.
***
**Next:** Learn about profile attributes and how to enrich user data → [Profile Attributes](/documentation/events/attributes)
# Event API Reference
Source: https://docs.zixflow.com/documentation/events/event-api-reference
HTTP API for server-side event tracking, user lifecycle, and device management.
> **Who this is for:** Backend developers and integrators sending server-side events. Mobile/web app actions should use an [SDK](/documentation/sdk/javascript/introduction) instead.
Complete reference for all Zixflow event ingress API endpoints — what each one does, when to use it, how to authenticate, and what to send.
For Mintlify API playground pages (request/response fields), see the [Events API Reference](/api-reference/zixflow-ai/events/introduction) under Zixflow AI.
***
## API vs SDK: When to Use Which
Zixflow exposes two surfaces for sending data: the **client/server SDKs** and the **HTTP API**.
| | SDKs | HTTP API |
| ------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Who uses it** | Web and mobile apps (JavaScript, Flutter, React Native, iOS, Android, Node.js) | Backend servers, scripts, integrations |
| **Auth** | API key in SDK initialization | `Authorization: Basic base64(api_key:)` |
| **Batching** | Automatic (typically \~20 events or \~30s) | Manual (you control when to call `/batch`) |
| **Offline support** | Built-in queue + retry on mobile/web SDKs | Your responsibility |
| **When to use** | User-facing app actions | Server-side events (order shipped, subscription renewed), user lifecycle management (delete, suppress), device registration from backend |
**Rule of thumb:** If the event happens because a user tapped something in your app, use an SDK. If the event happens on your server (fulfillment, billing, fraud detection), use the API directly.
***
## Authentication
Both API groups use the same scheme:
```
Authorization: Basic
```
The API key goes in the **username** field of HTTP Basic Auth. The password is always empty — note the trailing colon before base64 encoding.
**Example:**
```bash theme={null}
# API key: ws_abc123
echo -n "ws_abc123:" | base64
# → d3NfYWJjMTIzOg==
curl -X POST https://api-events.zixflow.com/v1/track \
-H "Authorization: Basic d3NfYWJjMTIzOg==" \
-H "Content-Type: application/json" \
-d '{"userId": "user_123", "event": "Button Clicked"}'
```
**Getting your API key:** Dashboard → Workspace Settings → API Keys
**Auth error responses:**
| HTTP Status | Reason |
| ------------------------- | ------------------------------------------ |
| `401 Unauthorized` | Missing, invalid, or unrecognised API key |
| `503 Service Unavailable` | Auth cache (Redis) temporarily unavailable |
***
## Two API Groups
The service exposes two distinct API groups, each serving a different use case:
| Group | Base path | Used by | Traffic |
| ---------------------- | -------------------- | ---------------------------------------------------- | ------- |
| **Data Pipelines API** | `/v1/` | Mobile/web SDKs, server-to-server event tracking | \~98% |
| **Track API v1** | `/api/v1/customers/` | Backend servers for user/device lifecycle management | \~2% |
***
## Data Pipelines API (`/v1/`)
These endpoints accept event data in Segment-compatible format. All endpoints under `/v1/` share the same auth middleware and rate limiter.
**All requests accept:** `Content-Type: application/json`\
**All success responses:** `HTTP 200` with empty body `{}`
***
### POST `/v1/identify` (alias: `/v1/i`)
**Purpose:** Create or update a user profile. Call this when a user signs up, logs in, or when you want to update their attributes.
**When to use:**
* User signs up or logs in → call identify with their userId and traits
* User updates their profile (name, email, plan) → call identify with updated traits
* Merging anonymous pre-signup activity → call identify with both `userId` and `anonymousId`
**Request body:**
```json theme={null}
{
"userId": "user_12345",
"anonymousId": "anon_abc-123",
"traits": {
"email": "sarah@example.com",
"name": "Sarah Johnson",
"plan": "enterprise",
"company": "Acme Corp"
},
"timestamp": "2026-05-04T10:00:00.000Z",
"messageId": "msg_unique_id",
"context": {
"ip": "203.0.113.1"
}
}
```
**Fields:**
| Field | Type | Required | Description |
| ------------- | -------- | -------- | ---------------------------------------------------------- |
| `userId` | string | Yes\* | Your application's stable identifier for this user |
| `anonymousId` | string | Yes\* | Anonymous ID from SDK (required if no userId) |
| `traits` | object | No | Key-value user attributes to set/update |
| `timestamp` | ISO 8601 | No | When the identify happened (defaults to server time) |
| `messageId` | string | No | Deduplication ID (SDK generates this automatically) |
| `context` | object | No | Device/platform context (SDK populates this automatically) |
\*At least one of `userId` or `anonymousId` is required. Providing both triggers identity merge.
**What happens:**
1. If `userId` is new → creates a new user profile
2. If `userId` exists → merges `traits` with existing profile (additive, not replace)
3. If both `userId` + `anonymousId` provided → links all anonymous events to the identified profile
***
### POST `/v1/track` (alias: `/v1/t`)
**Purpose:** Record a user action or business event. The most commonly used endpoint for custom event tracking.
**When to use:**
* Any user action: button tapped, purchase completed, feature used
* Business events from your server: subscription renewed, payment failed
* Device lifecycle: `Application Installed`, `Application Opened` (SDK sends these automatically)
**Request body:**
```json theme={null}
{
"userId": "user_12345",
"event": "Order Completed",
"properties": {
"order_id": "ord_789",
"revenue": 129.99,
"currency": "USD",
"product_name": "Nike Air Max 90"
},
"timestamp": "2026-05-04T14:32:15.000Z"
}
```
**Fields:**
| Field | Type | Required | Description |
| ------------- | -------- | -------- | -------------------------------------------- |
| `userId` | string | Yes\* | Identified user ID |
| `anonymousId` | string | Yes\* | Anonymous user ID (if user not identified) |
| `event` | string | **Yes** | Name of the event (e.g. `"Order Completed"`) |
| `properties` | object | No | Event-specific data |
| `timestamp` | ISO 8601 | No | When the event occurred |
\*At least one of `userId` or `anonymousId` is required.
**Semantic events** — certain event names trigger special handling beyond the standard event pipeline:
| Event name | Special behavior |
| --------------------------- | ---------------------------------------------------------- |
| `Device Created or Updated` | Routes to device registration pipeline |
| `Device Deleted` | Soft-deletes device record (sets `active=false`) |
| `Device Registered` | Routes to device logs table |
| `Device Updated` | Routes to device logs table |
| `User Deleted` | Sets `is_deleted=true` on profile, deactivates all devices |
| `User Suppressed` | Sets `is_suppressed=true`, blocks all channel delivery |
| `User Unsuppressed` | Clears suppression flag |
| `Application Installed` | Routes to device logs table |
| `Application Opened` | Routes to device logs table |
| `Application Backgrounded` | Routes to device logs table |
| `Application Foregrounded` | Routes to device logs table |
| `Application Crashed` | Routes to device logs table |
***
### POST `/v1/screen` (alias: `/v1/s`)
**Purpose:** Record a mobile screen view. Used by the Flutter SDK automatically when `autoTrackScreenViews: true` is configured, or manually when you want to attach screen-specific properties.
**When to use:**
* Mobile apps to track navigation flow
* Attribute screen-level data (product ID, category) to the view event
**Request body:**
```json theme={null}
{
"userId": "user_12345",
"name": "Product Detail",
"properties": {
"product_id": "prod_789",
"category": "Footwear",
"price": 129.99
}
}
```
**Fields:**
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------- |
| `userId` | string | Yes\* | Identified user ID |
| `anonymousId` | string | Yes\* | Anonymous user ID |
| `name` | string | No | Screen name (e.g. `"Product Detail"`) |
| `category` | string | No | Screen category |
| `properties` | object | No | Screen-specific properties |
***
### POST `/v1/page` (alias: `/v1/p`)
**Purpose:** Record a web page view. Equivalent to `/v1/screen` but for web apps.
**When to use:**
* Web apps and marketing sites
* Track specific page metadata (title, URL, referrer)
**Request body:**
```json theme={null}
{
"userId": "user_12345",
"name": "Pricing",
"properties": {
"path": "/pricing",
"url": "https://app.example.com/pricing",
"referrer": "https://google.com",
"title": "Pricing — Example"
}
}
```
**Fields:**
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------ |
| `userId` | string | Yes\* | Identified user ID |
| `anonymousId` | string | Yes\* | Anonymous user ID |
| `name` | string | No | Page name |
| `properties` | object | No | Page metadata (path, url, referrer, title) |
***
### POST `/v1/batch` (alias: `/v1/b`)
**Purpose:** Send multiple events in a single HTTP request. This is what the Flutter SDK uses under the hood — the SDK queues events locally and sends them as a batch every 30 seconds or after 20 events accumulate.
**When to use:**
* The SDK handles this for you automatically
* If calling the API directly from a server and you have multiple events to send, prefer `/batch` over individual calls to reduce latency and network overhead
**Request body:**
```json theme={null}
{
"batch": [
{
"type": "identify",
"userId": "user_12345",
"traits": { "plan": "enterprise" }
},
{
"type": "track",
"userId": "user_12345",
"event": "Plan Upgraded",
"properties": { "from_plan": "pro", "to_plan": "enterprise" }
},
{
"type": "screen",
"userId": "user_12345",
"name": "Home"
}
],
"sentAt": "2026-05-04T10:00:30.000Z"
}
```
**Batch item `type` values:**
| Type | Equivalent endpoint | Notes |
| ---------- | ----------------------------------------------------------- | ----------------------------- |
| `identify` | [`/v1/identify`](/api-reference/zixflow-ai/events/identify) | `traits` field |
| `track` | [`/v1/track`](/api-reference/zixflow-ai/events/track) | `event` + `properties` fields |
| `screen` | [`/v1/screen`](/api-reference/zixflow-ai/events/screen) | `name` + `properties` fields |
| `page` | [`/v1/page`](/api-reference/zixflow-ai/events/page) | `name` + `properties` fields |
Group and Alias are **not supported**. Do not send `type: "group"` or `type: "alias"`.
**Limits:**
* Minimum: 1 item per batch
* Maximum: 100 items per batch
* Items are processed concurrently
Each batch item shares the same fields as its corresponding individual endpoint, plus a `type` field to identify the kind.
***
## Track API v1 (`/api/v1/customers/`)
Server-side endpoints for user and device lifecycle management. Use these from your backend — not from mobile apps.
**Auth:** Same as Data Pipelines — `Authorization: Basic base64(api_key:)`\
**User identifier:** Passed in the URL path (`/:identifier`), not the request body
***
### PUT `/api/v1/customers/:identifier`
**Purpose:** Create or update a user profile. Server-side equivalent of `/v1/identify`.
**When to use:**
* Your backend creates a user and wants to push their profile to Zixflow
* Updating user attributes from your server (plan change, billing update)
**Request body:**
```json theme={null}
{
"email": "sarah@example.com",
"name": "Sarah Johnson",
"plan": "enterprise",
"created_at": 1746352800
}
```
**Notes:**
* Attributes are flat key-value (not nested under `traits`)
* Timestamps must be **Unix seconds** (not ISO 8601)
* `:identifier` is your user ID
***
### POST `/api/v1/customers/:identifier/events`
**Purpose:** Track an event for a specific user. Server-side equivalent of `/v1/track`.
**When to use:**
* Order fulfilled (triggered by your fulfillment system, not the user's app)
* Payment received / payment failed (from billing system)
* Subscription renewed (from subscription service)
* Any event your server generates, not the user's device
**Request body:**
```json theme={null}
{
"name": "Order Fulfilled",
"data": {
"order_id": "ord_789",
"tracking_number": "USPS-1Z999AA10123456784",
"carrier": "USPS"
},
"timestamp": 1746352800
}
```
**Notes:**
* Event name goes in `name` (not `event`)
* Event data goes in `data` (not `properties`)
* Timestamp is Unix seconds
***
### PUT `/api/v1/customers/:identifier/devices`
**Purpose:** Register a push notification device token for a user.
**When to use:**
* Your server manages push token registration (less common — the Flutter SDK handles this automatically via `registerDeviceToken()`)
* Server-side integrations that need to register tokens on behalf of users
**Request body:**
```json theme={null}
{
"device": {
"id": "fcm_token_abc123...",
"platform": "android",
"last_used": 1746352800
}
}
```
***
### DELETE `/api/v1/customers/:identifier/devices/:token`
**Purpose:** Remove a specific device token from a user. Call this when a user logs out of a device (so they don't receive push on a device they're no longer signed in to).
**Request body:** Empty
**Effect:** Soft-deletes the device record — sets `active=false`, `push_enabled=false`.
***
### DELETE `/api/v1/customers/:identifier`
**Purpose:** Delete a user profile.
**Request body:** Empty
**Effect:** Sets `is_deleted=true` on the profile and deactivates all linked devices. The profile record and event history are preserved for analytics — this is a soft delete.
**When to use:**
* User requests account deletion (GDPR right to erasure)
* Account cleanup / fraud removal
***
### POST `/api/v1/customers/:identifier/suppress`
**Purpose:** Suppress all outbound messages for a user across every channel simultaneously.
**Request body:** Empty
**Effect:** Sets `is_suppressed=true` on the profile. No push notifications, emails, SMS, WhatsApp, or RCS will be sent to this user until unsuppressed.
**When to use:**
* User has opted out of all communications
* Account is suspended or in bad standing
* Compliance hold
**Note:** Suppression is different from channel opt-out. Channel opt-out (`email_opt_out`, `push_opt_out`, etc.) is per-channel. Suppression blocks everything with a single flag.
***
### POST `/api/v1/customers/:identifier/unsuppress`
**Purpose:** Lift suppression and resume message delivery for a user.
**Request body:** Empty
**Effect:** Clears `is_suppressed` flag. Message delivery resumes on all channels the user hasn't individually opted out of.
***
### GET `/api/v1/customers/:identifier/subscription_preferences`
**Purpose:** Retrieve the current channel opt-out state for a user.
**Query parameters:**
| Parameter | Values | Default | Description |
| --------- | ----------------------- | ------- | ---------------------------------- |
| `id_type` | `id`, `email`, `cio_id` | `id` | How to interpret the `:identifier` |
**Example:**
```
GET /api/v1/customers/sarah@example.com/subscription_preferences?id_type=email
```
**Response:** User's current opt-out state per channel (`email_opt_out`, `sms_opt_out`, `push_opt_out`, `whatsapp_opt_out`, `rcs_opt_out`) with their opt-out timestamps.
***
## Error Responses
All endpoints return consistent error shapes:
```json theme={null}
{
"error": "VALIDATION_FAILED",
"details": [
{
"reason": "REQUIRED",
"field": "userId",
"message": "userId is required"
}
]
}
```
| HTTP Status | Meaning |
| ----------- | --------------------------------------------------------- |
| `200` | Success — event accepted |
| `400` | Bad request — invalid JSON or failed validation |
| `401` | Unauthorized — missing or invalid API key |
| `404` | Not found — user identifier not found (Track API v1 only) |
| `503` | Service unavailable — auth cache temporarily unavailable |
| `500` | Internal error — unexpected failure |
***
## Complete Endpoint Map
API Reference pages: [Events Introduction](/api-reference/zixflow-ai/events/introduction)
### Data Pipelines (`/v1/`)
```
POST /v1/identify (alias: /v1/i) — Create/update user profile
POST /v1/track (alias: /v1/t) — Track event
POST /v1/screen (alias: /v1/s) — Track mobile screen view
POST /v1/page (alias: /v1/p) — Track web page view
POST /v1/batch (alias: /v1/b) — Send multiple events at once
```
### Track API v1 (`/api/v1/customers/`)
```
PUT /api/v1/customers/:id — Create/update user
POST /api/v1/customers/:id/events — Track server-side event
PUT /api/v1/customers/:id/devices — Register device token
DELETE /api/v1/customers/:id/devices/:token — Remove device token
DELETE /api/v1/customers/:id — Delete user (soft)
POST /api/v1/customers/:id/suppress — Suppress all channels
POST /api/v1/customers/:id/unsuppress — Resume delivery
GET /api/v1/customers/:id/subscription_preferences — Get channel opt-out state
```
### Utility
```
GET /health — Health check
GET / — Service info
```
# Getting Started with Events
Source: https://docs.zixflow.com/documentation/events/getting-started
Introduction to Zixflow event tracking — concepts, use cases, and first steps.
Welcome to Zixflow's event tracking system. This guide covers what events are, the core building blocks, and how to track your first event.
***
## What is the Event System?
Zixflow's event tracking system is a **Customer Data Platform (CDP)** that captures and organizes every user interaction in your application. Similar to platforms like Segment or Amplitude, it creates a complete, searchable history of how users engage with your product.
**What you can do with it:**
* Understand user behavior patterns and preferences
* Trigger automated campaigns at the right moments
* Analyze what drives conversions and retention
* Make data-driven product and marketing decisions
### How It Works
Every user action becomes a structured event that includes:
* **Who**: Which user took the action (by ID or anonymous tracking)
* **What**: The specific action they took (like "Purchased" or "Signed Up")
* **When**: The exact time it happened
* **Context**: Details like what device they used, where they were, etc.
```mermaid theme={null}
flowchart TD
action[User action in your app] --> sdk[SDK captures event]
sdk --> enrich[Adds timestamp, user, device context]
enrich --> send[Sends securely to Zixflow]
send --> process[Validated and linked to profile]
process --> ready[Ready for campaigns, segments, analytics]
```
Once tracked, events are typically available within **2–5 seconds** for campaigns, segments, and analytics.
***
## Why Track Events?
| Goal | Examples |
| ------------------------ | ----------------------------------------------------- |
| **Product analytics** | Feature adoption, activation, retention, cohorts |
| **Marketing** | Attribution, personalization, abandoned-cart recovery |
| **Revenue** | Trial-to-paid funnels, upgrades, churn risk |
| **Lifecycle automation** | Onboarding, re-activation, upsell, win-back |
For industry-specific patterns, see the [scenario guides](/documentation/events/scenarios/ecommerce).
***
## Key Concepts at a Glance
### Events
Structured records of user actions — think Segment's `track()` calls or Amplitude events.
**Examples:** `Signed Up`, `Feature Used`, `Trial Extended`, `Payment Failed`
### Users
Unified profiles across the anonymous → identified journey.
* **Anonymous:** Pre-signup visitors tracked via browser/device ID — still useful for funnels and first-touch attribution
* **Identified:** Post-signup/login users — cross-device tracking, messaging, and full journey merge
See [Users & Identity](/documentation/events/users-and-identity) for how merging works.
### Attributes
Profile fields that describe who a user is and their current state (`plan_type`, `trial_ends_at`, etc.).
**Attributes = state. Events = changes to that state.**
See [Attributes](/documentation/events/attributes).
### Devices
Endpoints (phone, tablet, browser) linked to a user or anonymous visitor so you can send push and understand cross-device behavior.
See [Devices & Push Notifications](/documentation/events/devices-and-push-notifications).
***
## Your First Event
### JavaScript (Web)
```javascript theme={null}
// After user creates account
Zixflow.track({
name: 'Signed Up',
properties: {
plan: 'free',
source: 'homepage'
}
});
```
### Flutter (Mobile)
```dart theme={null}
// After user creates account
Zixflow.instance.track(
name: 'Signed Up',
properties: {
'plan': 'free',
'source': 'homepage',
},
);
```
**What happens:** The SDK enriches the event, sends it securely, and Zixflow links it to the user profile. Your app gets an instant response; events are ready for use within a few seconds. Offline events are queued and sent when connectivity returns.
For the full pipeline, see [The Data Journey](/documentation/events/data-journey).
***
## What You Can Do With Your Data
### Trigger automated campaigns
* User signs up → welcome series
* User abandons cart → reminder after 2 hours
* User hits usage limit → upgrade prompt
### Analyze journeys
Conversion funnels, path analysis, and cohorts by signup source or plan.
### Measure what matters
Activation rate, retention (D1/D7/D30), feature adoption, time to value, revenue metrics.
***
## Reliability & Privacy
* Events are queued locally if the network drops, then retried automatically
* Transmission uses encrypted HTTPS
* GDPR/CCPA-oriented controls: export, deletion, and opt-out support
***
## Next Steps
### Concepts
* [Understanding Events](/documentation/events/understanding-events) — Event types, naming, best practices
* [Users & Identity](/documentation/events/users-and-identity) — Anonymous → identified merging
* [Attributes](/documentation/events/attributes) — State vs actions
* [Devices & Push Notifications](/documentation/events/devices-and-push-notifications) — Tokens and multi-device
### Implementation
* [E-commerce Tracking](/documentation/events/scenarios/ecommerce)
* [Mobile App Analytics](/documentation/events/scenarios/mobile-app)
* [SaaS Product Analytics](/documentation/events/scenarios/saas)
### SDK & API
* [SDK overview](/documentation/sdk/flutter/introduction) — [JavaScript](/documentation/sdk/javascript/introduction), [Node.js](/documentation/sdk/nodejs/introduction), [React Native](/documentation/sdk/react-native/introduction), [Flutter](/documentation/sdk/flutter/introduction), [iOS](/documentation/sdk/ios/introduction), [Android](/documentation/sdk/android/introduction)
* [Event API Reference](/documentation/events/event-api-reference) — Server-side tracking
* [Data Journey](/documentation/events/data-journey) — What happens after `track()`
***
**Ready to dive deeper?** Continue to [Understanding Events →](/documentation/events/understanding-events)
# Push Notification Tracking
Source: https://docs.zixflow.com/documentation/events/push-notification-tracking
Track push delivery, opens, and action clicks with the Zixflow SDK.
**Audience:** Client developers integrating a Zixflow SDK\
**Purpose:** How to report push delivery, opens, and action clicks so campaign analytics stay accurate.
For step-by-step platform code, use the SDK guides:
* [Flutter — Push Notification Tracking](/documentation/sdk/flutter/push-notification-tracking)
* [React Native — Push Notification Tracking](/documentation/sdk/react-native/push-notification-tracking)
* [iOS — Push Notification Tracking](/documentation/sdk/ios/push-notification-tracking)
* [Android — Push Notification Tracking](/documentation/sdk/android/push-notification-tracking)
* [JavaScript — Push Notification Tracking](/documentation/sdk/javascript/push-notification-tracking)
***
## Why Push Tracking Matters
When Zixflow **sends** a push, it records the send. Your app must report what happens on the device:
* Did it **arrive**?
* Did the user **open** it?
* Did they tap an **action button**?
Those callbacks power open rates, click rates, and post-push conversion funnels.
***
## Delivery Lifecycle
```mermaid theme={null}
sequenceDiagram
participant Campaign as ZixflowCampaign
participant Platform as FCM_or_APNs
participant Device
participant App
participant Zixflow
Campaign->>Platform: Send push
Platform->>Device: Deliver payload
Device->>App: onMessage / willPresent
App->>Zixflow: trackMetric delivered
Device->>App: User taps banner
App->>Zixflow: trackMetric opened
Device->>App: User taps action button
App->>Zixflow: track Action Clicked
```
***
## The Push Payload
Zixflow injects two tracking fields into every push. `trackMetric()` requires both.
| Field | Example | Purpose |
| ------------------------ | ------------------------------ | -------------------------- |
| `Zixflow-Delivery-ID` | `"626533406292836846"` | Unique delivery ID |
| `Zixflow-Delivery-Token` | `"dcFRlDhiRbehM1vg-Lx_yn:..."` | Token the push was sent to |
They appear in `message.data` (Flutter/Android/RN) or `userInfo` (iOS).
The rest of the payload depends on the dashboard **rendering mode**:
* **Native (OS-rendered):** display content lives in FCM `notification` / APNs `aps.alert`; `data` (or top-level iOS keys) carries tracking + routing (`deeplink_url`, `action_buttons`, `template_id`, `notif_id`, `workspace_id`).
* **Custom (app-rendered):** no `notification` block — title, body, image, sticky, and other display keys live in `data`.
**Custom `data` example:**
```json theme={null}
{
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"title": "Flash Sale! 70% OFF",
"body": "Limited time only — grab your deal now!",
"deeplink_url": "https://yourapp.com/sale",
"image_url": "https://cdn.yourapp.com/banner.png",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"}]",
"template_id": "469935"
}
```
Use `template_id` to branch to a template-specific renderer. Full key reference and Native vs Custom wire format: [Android](/documentation/sdk/android/push-notification-tracking#fcm-android-wire-format) · [iOS](/documentation/sdk/ios/push-notification-tracking#apns-ios-wire-format) · [Flutter](/documentation/sdk/flutter/push-notification-tracking#the-push-payload).
***
## The Three Tracking Events
```mermaid theme={null}
flowchart LR
delivered[Delivered] --> opened[Opened]
opened --> action[Action Clicked]
```
> **Event naming — read this first:** `trackMetric()` used to always send a single generic internal event name, `Report Delivery Event`, for every metric type. The SDK now sends the **metric name itself as the event name** — `Delivered`, `Opened`, `Clicked` — so each lifecycle stage is directly filterable/reportable by name in analytics and Journeys. You still call `trackMetric(event: MetricEvent.delivered)` exactly as before; only the resulting event name on the backend changed. If you have older dashboards/segments filtering on `"Report Delivery Event"`, update them to filter on `"Delivered"` / `"Opened"` / `"Clicked"` instead. Both old and new names are still recognized by the backend.
| Moment | Event / method | When to fire |
| --------------------- | ---------------------------------------------- | ------------------------------------------------------------------- |
| **1. Delivered** | `trackMetric(... delivered)` → `Delivered` | Payload arrives (`onMessage` / `willPresent` / `onMessageReceived`) |
| **2. Opened** | `trackMetric(... opened)` → `Opened` | User taps the notification (body **or** action). Fire for both. |
| **3. Action clicked** | `track('Push Notification Action Clicked', …)` | User taps a specific action button (after opened) |
### Delivered / Opened parameters
| Parameter | Required | Description |
| ------------- | -------- | --------------------------------------------------- |
| `deliveryID` | Yes | `Zixflow-Delivery-ID` |
| `deviceToken` | Yes | `Zixflow-Delivery-Token` (or cached FCM/APNs token) |
| `event` | Yes | `MetricEvent.delivered` or `MetricEvent.opened` |
### Action Clicked properties
| Property | Required | Description |
| ------------------------ | ----------- | -------------------------------- |
| `Zixflow-Delivery-ID` | Yes | Links to the delivery |
| `action_index` | Yes | 0-based button index |
| `action_name` | Yes | Button label (e.g. `"Shop Now"`) |
| `Zixflow-Delivery-Token` | Recommended | Token used for the send |
| `action_deeplink` | Recommended | URL for that button |
> Action clicks use `track()`, not `trackMetric()`. Always fire **opened** first, then the action event.
### Minimal Flutter example
```dart theme={null}
// Delivered
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.delivered,
);
// Opened
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.opened,
);
// Action clicked
Zixflow.instance.track(
name: 'Push Notification Action Clicked',
properties: {
'Zixflow-Delivery-ID': deliveryId,
'Zixflow-Delivery-Token': deliveryToken,
'action_index': 0,
'action_name': 'Shop Now',
'action_deeplink': 'https://yourapp.com/sale',
},
);
```
Full handlers for every platform (foreground, background, terminated, service worker) are in the SDK links at the top of this page.
***
## Action Buttons Format
Not a Zixflow-defined field. If you add buttons in custom data, one convention is a JSON array of `{name, deeplink}`:
```json theme={null}
[
{ "name": "Shop Now", "deeplink": "https://yourapp.com/sale" },
{ "name": "Remind Me", "deeplink": "" }
]
```
* `name` — button label
* `deeplink` — optional URL; empty string means no navigation
***
## Deep Links
Zixflow sends `deeplink_url` for body taps and per-button `deeplink` values inside `action_buttons`. Route them in your app. Tracking and navigation are separate: always report metrics even if routing fails. FCM `click_action` is a platform field that needs a matching `` on the Native path. Sample apps may also read `data.click_action` tokens (`OPEN_SALE` / `OPEN_DASHBOARD`) for in-app routing, taking priority over `deeplink_url`.
***
## Decision Flow
```mermaid theme={null}
flowchart TD
receive[Push received] --> hasId{Has Zixflow-Delivery-ID?}
hasId -->|No| skip[Skip Zixflow metrics]
hasId -->|Yes| delivered[trackMetric delivered]
delivered --> tap{User taps?}
tap -->|No| endNode[Done]
tap -->|Yes| opened[trackMetric opened]
opened --> button{Action button?}
button -->|Yes| action[track Action Clicked]
button -->|No| endNode
action --> endNode
```
### Fallback (no Delivery ID)
If `Zixflow-Delivery-ID` is missing (e.g. a push not sent by Zixflow), do **not** call `trackMetric`. Reserved names (`Delivered`, `Opened`, `Push Notification Action Clicked`) are only treated as delivery reports when properties include a Zixflow delivery identifier. You may still show the notification and handle deep links locally.
***
**Related:** [Devices & Push Notifications](/documentation/events/devices-and-push-notifications) · [Channels Overview](/documentation/events/channels-overview)
# E-commerce Tracking
Source: https://docs.zixflow.com/documentation/events/scenarios/ecommerce
Tracking patterns for online retail — product views, carts, purchases, and funnels.
Imagine you're running an online store — users browse products, add items to a cart, check out, and come back to buy again. This guide walks through that full journey, showing which events to fire at each step, what data to capture, and how to use it to understand user behavior, trigger campaigns, and measure revenue.
```mermaid theme={null}
flowchart LR
view[Product Viewed] --> cart[Product Added]
cart --> checkout[Checkout Started]
checkout --> order[Order Completed]
```
***
## Core E-commerce Events
### 1. **Product Catalog Events**
#### Product Viewed
**When:** User lands on product detail page
```javascript theme={null}
track('Product Viewed', {
product_id: 'prod_nike_air_max_90',
sku: 'NIK-AM90-BLK-10',
name: 'Nike Air Max 90 - Black',
category: 'Footwear > Sneakers > Lifestyle',
price: 129.99,
currency: 'USD',
brand: 'Nike',
variant: 'Black / Size 10',
image_url: 'https://cdn.acme.com/products/nike-am90-blk.jpg',
inventory_status: 'in_stock',
inventory_quantity: 47,
url: '/products/nike-air-max-90-black'
});
```
**Analytics use cases:**
* **Product performance:** Views by product, conversion rate (viewed → purchased)
* **Browse abandonment:** Users who viewed but didn't add to cart
* **Inventory insights:** High-demand products (views vs stock)
**Query example (top viewed products this week):**
```sql theme={null}
SELECT
properties->>'name' AS product_name,
properties->>'category' AS category,
COUNT(*) AS views,
COUNT(DISTINCT user_id) AS unique_viewers
FROM events
WHERE event_name = 'Product Viewed'
AND workspace_id = 'ws_123'
AND timestamp >= DATE_TRUNC('week', NOW())
GROUP BY product_name, category
ORDER BY views DESC
LIMIT 20;
```
***
#### Product List Viewed
**When:** User views category page, search results, or collection
```javascript theme={null}
track('Product List Viewed', {
list_id: 'category_sneakers',
category: 'Footwear > Sneakers',
filters: {
brand: ['Nike', 'Adidas'],
price_range: '100-200',
size: [10, 10.5, 11]
},
sort_by: 'price_low_to_high',
products: [
{
product_id: 'prod_nike_air_max_90',
position: 1,
price: 129.99
},
{
product_id: 'prod_adidas_ultraboost',
position: 2,
price: 149.99
}
// ... (include all products in viewport, max 50)
],
total_products: 247 // Total matching products (pagination)
});
```
**Analytics use cases:**
* **Filter effectiveness:** Which filters lead to purchases
* **Sort behavior:** Does "price low-to-high" convert better?
* **Zero-results search:** Queries with no results (product gap analysis)
***
#### Product Searched
**When:** User submits search query
```javascript theme={null}
track('Product Searched', {
query: 'black running shoes size 10',
results_count: 34,
filters_applied: {
brand: ['Nike'],
price_max: 150
},
sort_by: 'relevance',
search_type: 'full_text', // vs autocomplete, voice
results: [
{ product_id: 'prod_123', position: 1 },
{ product_id: 'prod_456', position: 2 }
// Top 10 results
]
});
```
**Analytics use cases:**
* **Search effectiveness:** Click-through rate by query
* **Failed searches:** Queries with 0 results or no clicks
* **Search → Purchase attribution:** Revenue by search query
***
### 2. **Cart Management Events**
#### Product Added
**When:** User clicks "Add to Cart"
```javascript theme={null}
track('Product Added', {
cart_id: 'cart_abc-123',
product_id: 'prod_nike_air_max_90',
sku: 'NIK-AM90-BLK-10',
name: 'Nike Air Max 90 - Black',
price: 129.99,
quantity: 1,
category: 'Footwear > Sneakers',
variant: 'Black / Size 10',
position: 1, // Position in product list where added from
source: 'product_page' // vs 'search_results', 'recommendations', 'quick_add'
});
```
**Trigger campaign:**
```yaml theme={null}
Campaign: Cart Abandonment Sequence
Trigger: Product Added + NOT Purchased within 2 hours
Channel: Email + Push
Message: "You left {product_name} in your cart. Complete checkout now!"
```
***
#### Product Removed
**When:** User removes item from cart
```javascript theme={null}
track('Product Removed', {
cart_id: 'cart_abc-123',
product_id: 'prod_nike_air_max_90',
name: 'Nike Air Max 90 - Black',
price: 129.99,
quantity: 1,
reason: 'user_action' // vs 'out_of_stock', 'price_change'
});
```
**Analytics:** Cart removal reasons, products frequently removed
***
#### Cart Viewed
**When:** User opens cart page or mini-cart
```javascript theme={null}
track('Cart Viewed', {
cart_id: 'cart_abc-123',
products: [
{ product_id: 'prod_123', name: 'Nike Air Max 90', price: 129.99, quantity: 1 },
{ product_id: 'prod_456', name: 'Adidas Ultraboost', price: 149.99, quantity: 2 }
],
subtotal: 429.97,
tax: 34.40,
shipping: 0.00,
discount: -50.00, // Coupon applied
total: 414.37,
cart_age_minutes: 45, // Time since first item added
item_count: 3
});
```
**Analytics:** Cart value distribution, abandoned cart value, time to purchase
***
### 3. **Checkout Flow Events**
#### Checkout Started
**When:** User clicks "Proceed to Checkout" from cart
```javascript theme={null}
track('Checkout Started', {
checkout_id: 'chk_xyz-789',
cart_id: 'cart_abc-123',
products: [...], // Same structure as Cart Viewed
subtotal: 429.97,
tax_estimated: 34.40,
shipping_estimated: 0.00,
total_estimated: 414.37,
checkout_step: 1, // Step in multi-step checkout
checkout_url: '/checkout/shipping'
});
```
**Funnel definition:**
```
Checkout funnel:
1. Cart Viewed (5,000 users)
2. Checkout Started (2,500 users) → 50% drop-off
3. Payment Info Added (2,000 users) → 20% drop-off
4. Order Completed (1,800 users) → 10% drop-off
Overall conversion: 36% (1,800 / 5,000)
```
***
#### Checkout Step Viewed/Completed
**When:** User progresses through checkout steps
**Step 1: Shipping Info**
```javascript theme={null}
track('Checkout Step Viewed', {
checkout_id: 'chk_xyz-789',
step: 1,
step_name: 'shipping_info'
});
// After entering address
track('Checkout Step Completed', {
checkout_id: 'chk_xyz-789',
step: 1,
step_name: 'shipping_info',
shipping_method: 'standard',
shipping_price: 0.00,
estimated_delivery_date: '2026-05-10'
});
```
**Step 2: Payment Info**
```javascript theme={null}
track('Checkout Step Viewed', {
checkout_id: 'chk_xyz-789',
step: 2,
step_name: 'payment_info'
});
track('Checkout Step Completed', {
checkout_id: 'chk_xyz-789',
step: 2,
step_name: 'payment_info',
payment_method: 'credit_card', // vs 'paypal', 'apple_pay', 'affirm'
card_type: 'visa' // Don't store card numbers!
});
```
**Analytics:** Drop-off by step, completion time by step, payment method conversion rates
***
#### Order Completed
**When:** Payment successful, order created
```javascript theme={null}
track('Order Completed', {
order_id: 'ord_1234567890',
checkout_id: 'chk_xyz-789',
cart_id: 'cart_abc-123',
revenue: 414.37, // Total paid (after discounts, including tax/shipping)
subtotal: 429.97,
tax: 34.40,
shipping: 0.00,
discount: -50.00,
coupon: 'SPRING2026',
currency: 'USD',
products: [
{
product_id: 'prod_nike_air_max_90',
sku: 'NIK-AM90-BLK-10',
name: 'Nike Air Max 90 - Black',
price: 129.99,
quantity: 1,
category: 'Footwear > Sneakers',
brand: 'Nike'
},
{
product_id: 'prod_adidas_ultraboost',
sku: 'ADI-UB-WHT-10',
name: 'Adidas Ultraboost - White',
price: 149.99,
quantity: 2,
category: 'Footwear > Running',
brand: 'Adidas'
}
],
payment_method: 'credit_card',
shipping_method: 'standard',
is_first_purchase: true, // Important for LTV analysis
attribution: {
source: 'google',
medium: 'cpc',
campaign: 'spring_sale_2026',
referring_domain: 'google.com'
}
});
```
**Critical: Update user profile attributes:**
```javascript theme={null}
setProfileAttributes({
lifetime_orders: 1,
lifetime_revenue: 414.37,
first_purchase_date: '2026-05-04T10:30:00Z',
last_purchase_date: '2026-05-04T10:30:00Z',
average_order_value: 414.37,
preferred_payment_method: 'credit_card',
favorite_category: 'Footwear > Sneakers'
});
```
**Trigger campaigns:**
* Post-purchase thank-you email
* Product review request (7 days later)
* Upsell campaign (complementary products)
* Repeat purchase incentive (30 days later)
***
### 4. **Post-Purchase Events**
#### Order Fulfilled
**When:** Order ships (server-side event from fulfillment system)
```javascript theme={null}
track('Order Fulfilled', {
order_id: 'ord_1234567890',
shipped_at: '2026-05-05T14:00:00Z',
tracking_number: 'USPS-1Z999AA10123456784',
carrier: 'USPS',
estimated_delivery: '2026-05-10',
products: [...] // Same as Order Completed
});
```
**Trigger:** Shipping confirmation email with tracking link
***
#### Order Delivered
**When:** Carrier confirms delivery
```javascript theme={null}
track('Order Delivered', {
order_id: 'ord_1234567890',
delivered_at: '2026-05-09T16:45:00Z',
delivery_time_days: 5,
on_time: true // vs late delivery
});
```
**Trigger:** Review request email (product arrived, ask for feedback)
***
#### Product Reviewed
**When:** User submits product review
```javascript theme={null}
track('Product Reviewed', {
order_id: 'ord_1234567890',
product_id: 'prod_nike_air_max_90',
rating: 5,
review_text: 'Best sneakers I've ever owned!',
recommend: true,
verified_purchase: true
});
```
**Analytics:** Average rating by product, review completion rate
***
#### Order Refunded
**When:** Customer returns product
```javascript theme={null}
track('Order Refunded', {
order_id: 'ord_1234567890',
refund_amount: 129.99,
refund_reason: 'wrong_size', // vs 'defective', 'not_as_described', 'changed_mind'
products_returned: [
{
product_id: 'prod_nike_air_max_90',
quantity: 1,
reason: 'wrong_size'
}
],
refund_method: 'original_payment', // vs 'store_credit'
refunded_at: '2026-05-15T10:00:00Z'
});
```
**Update profile attributes:**
```javascript theme={null}
setProfileAttributes({
lifetime_orders: 1, // No change
lifetime_revenue: 284.38, // Subtract refund
total_refunds: 1,
last_refund_date: '2026-05-15T10:00:00Z'
});
```
**Trigger:** Follow-up email (offer size exchange, understand issue)
***
## Advanced Analytics Queries
> These SQL snippets are **illustrative** analytics patterns (how you might think about the data). You analyze events in Zixflow dashboards and APIs — not by querying an internal table directly.
### Revenue Attribution by Channel
```sql theme={null}
SELECT
properties->'attribution'->>'source' AS source,
properties->'attribution'->>'medium' AS medium,
COUNT(DISTINCT user_id) AS customers,
COUNT(*) AS orders,
SUM((properties->>'revenue')::DECIMAL) AS total_revenue,
AVG((properties->>'revenue')::DECIMAL) AS avg_order_value
FROM events
WHERE event_name = 'Order Completed'
AND workspace_id = 'ws_123'
AND timestamp >= NOW() - INTERVAL 30 DAY
GROUP BY source, medium
ORDER BY total_revenue DESC;
```
***
### Product Affinity Analysis
```sql theme={null}
-- Products frequently purchased together
SELECT
a.product_id AS product_a,
b.product_id AS product_b,
COUNT(*) AS co_purchase_count
FROM (
SELECT
properties->>'order_id' AS order_id,
jsonb_array_elements(properties->'products')->>'product_id' AS product_id
FROM events
WHERE event_name = 'Order Completed'
AND workspace_id = 'ws_123'
AND timestamp >= NOW() - INTERVAL 90 DAY
) a
JOIN (
SELECT
properties->>'order_id' AS order_id,
jsonb_array_elements(properties->'products')->>'product_id' AS product_id
FROM events
WHERE event_name = 'Order Completed'
AND workspace_id = 'ws_123'
AND timestamp >= NOW() - INTERVAL 90 DAY
) b ON a.order_id = b.order_id AND a.product_id < b.product_id
GROUP BY product_a, product_b
ORDER BY co_purchase_count DESC
LIMIT 50;
```
**Use case:** "Customers who bought X also bought Y" recommendations
***
### Cohort Retention by First Purchase Month
```sql theme={null}
WITH first_purchases AS (
SELECT
user_id,
DATE_TRUNC('month', MIN(timestamp)) AS cohort_month,
MIN(timestamp) AS first_purchase_date
FROM events
WHERE event_name = 'Order Completed'
AND workspace_id = 'ws_123'
GROUP BY user_id
),
repeat_purchases AS (
SELECT
user_id,
timestamp AS purchase_date
FROM events
WHERE event_name = 'Order Completed'
AND workspace_id = 'ws_123'
)
SELECT
fp.cohort_month,
COUNT(DISTINCT fp.user_id) AS cohort_size,
COUNT(DISTINCT CASE WHEN rp.purchase_date >= fp.first_purchase_date + INTERVAL 30 DAY
AND rp.purchase_date < fp.first_purchase_date + INTERVAL 60 DAY
THEN rp.user_id END) AS retained_month_1,
COUNT(DISTINCT CASE WHEN rp.purchase_date >= fp.first_purchase_date + INTERVAL 60 DAY
AND rp.purchase_date < fp.first_purchase_date + INTERVAL 90 DAY
THEN rp.user_id END) AS retained_month_2,
COUNT(DISTINCT CASE WHEN rp.purchase_date >= fp.first_purchase_date + INTERVAL 90 DAY
AND rp.purchase_date < fp.first_purchase_date + INTERVAL 120 DAY
THEN rp.user_id END) AS retained_month_3
FROM first_purchases fp
LEFT JOIN repeat_purchases rp ON fp.user_id = rp.user_id
GROUP BY fp.cohort_month
ORDER BY fp.cohort_month DESC;
```
***
## Implementation Checklist
### Phase 1: Core Tracking (Week 1)
* [ ] Product Viewed (PDP)
* [ ] Product Added to Cart
* [ ] Checkout Started
* [ ] Order Completed
* [ ] Set profile attributes on purchase
### Phase 2: Funnel Optimization (Week 2)
* [ ] Product List Viewed (category/search pages)
* [ ] Product Searched
* [ ] Cart Viewed
* [ ] Checkout Step Viewed/Completed (all steps)
* [ ] Product Removed from Cart
### Phase 3: Lifecycle & Retention (Week 3)
* [ ] Order Fulfilled (server-side)
* [ ] Order Delivered (webhook from carrier)
* [ ] Product Reviewed
* [ ] Order Refunded
* [ ] Cart abandonment campaign
* [ ] Post-purchase email sequence
### Phase 4: Advanced Analytics (Week 4)
* [ ] Revenue attribution by channel
* [ ] Product affinity analysis
* [ ] Cohort retention analysis
* [ ] LTV prediction model
***
**Next:** Mobile app event tracking patterns → [Mobile App Tracking](/documentation/events/scenarios/mobile-app)
# Mobile App Analytics
Source: https://docs.zixflow.com/documentation/events/scenarios/mobile-app
Mobile-specific tracking patterns — screens, features, purchases, and push.
Imagine you've built a mobile app — users install it, sign in, browse content, and eventually make a purchase or complete a key action. This guide walks through tracking the full app experience using the Zixflow Flutter SDK: from first install and screen navigation to feature usage, push notifications, and offline sessions.
```mermaid theme={null}
flowchart LR
install[App Installed] --> open[App Opened]
open --> identify[Identify / Sign In]
identify --> screen[Screen Views]
screen --> feature[Feature Used]
feature --> convert[Purchase or Key Action]
```
***
## SDK Integration
### Flutter SDK Setup
**pubspec.yaml:**
```yaml theme={null}
dependencies:
zixflow: ^1.0.0
```
**Initialize on app launch:**
```dart theme={null}
// lib/main.dart
import 'package:zixflow/zixflow.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure SDK
const config = ZixflowConfig(
siteId: 'your_workspace_id',
apiKey: 'your_api_key',
region: Region.US,
autoTrackScreenViews: true, // Auto-track screen events
autoTrackDeviceAttributes: true, // Capture OS, app version, etc.
backgroundQueueMinNumberOfTasks: 10, // Batch events
backgroundQueueSecondsDelay: 30, // Flush every 30s
);
await Zixflow.instance.initialize(config);
runApp(MyApp());
}
```
**SDK behavior:**
* Events batched in memory (max 20 events or 30 seconds)
* On app background: Immediate flush
* On network failure: Persist to SQLite, retry with exponential backoff
* Battery-conscious: Batching reduces network calls by 90%
***
## App Lifecycle Events
### App Installed
**When:** First app launch after install (SDK auto-tracks)
**What's tracked automatically:**
```javascript theme={null}
{
"event": "App Installed",
"properties": {
"app_version": "2.1.0",
"build_number": "403",
"install_date": "2026-05-04T10:00:00Z",
"install_source": "App Store" // iOS: App Store, Android: Play Store, APK
},
"context": {
"os": { "name": "iOS", "version": "17.4.1" },
"device": { "model": "iPhone 14 Pro", "manufacturer": "Apple" },
"locale": "en-US",
"timezone": "America/New_York"
}
}
```
**Manual tracking (if you need attribution):**
```dart theme={null}
// After retrieving install attribution from AppsFlyer/Branch/Adjust
track('App Installed', {
'install_source': 'App Store',
'attribution_campaign': 'spring_2026_ios',
'attribution_channel': 'google_uac',
'attribution_ad_group': 'sneakers_lookalike',
'install_cost': 2.45 // CPI from attribution provider
});
```
***
### App Opened
**When:** App moves from background to foreground
**Auto-tracked by SDK** (if `autoTrackAppLifecycle: true`):
```javascript theme={null}
{
"event": "App Opened",
"properties": {
"cold_start": true, // vs warm start (from background)
"push_notification_triggered": false, // Did user tap notification?
"deeplink_url": null, // If opened via deep link
"session_id": "sess_abc-123",
"time_since_last_open_seconds": 3600
}
}
```
**Manual tracking (if you want custom logic):**
```dart theme={null}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State with WidgetsBindingObserver {
DateTime? _lastBackgroundTime;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// App opened
final sessionGapMinutes = _lastBackgroundTime != null
? DateTime.now().difference(_lastBackgroundTime!).inMinutes
: null;
Zixflow.instance.track('App Opened', {
'cold_start': _lastBackgroundTime == null,
'session_gap_minutes': sessionGapMinutes,
'previous_screen': _lastScreen, // Where user was before backgrounding
});
} else if (state == AppLifecycleState.paused) {
_lastBackgroundTime = DateTime.now();
}
}
}
```
***
### App Backgrounded
**When:** User switches to another app or locks screen
```dart theme={null}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
// Calculate session duration
final sessionDuration = DateTime.now().difference(_sessionStartTime);
Zixflow.instance.track('App Backgrounded', {
'session_duration_seconds': sessionDuration.inSeconds,
'screens_viewed': _screensViewedThisSession,
'events_tracked': _eventsThisSession,
'last_screen': _currentScreen,
'actions_completed': _actionsCompletedThisSession, // Purchases, signups, etc.
});
// Flush event queue immediately (don't wait for batch)
Zixflow.instance.flush();
}
}
```
**Analytics use cases:**
* Session duration distribution
* Screens per session
* Features used per session
* Session → Conversion rate
***
## Screen Tracking
### Automatic Screen Tracking (Recommended)
**Enable in SDK config:**
```dart theme={null}
const config = ZixflowConfig(
autoTrackScreenViews: true, // Auto-track on Navigator route changes
// ...
);
```
**What's tracked:**
```javascript theme={null}
{
"type": "screen",
"name": "ProductDetailScreen",
"properties": {
"route": "/products/nike-air-max-90",
"previous_screen": "HomeScreen",
"screen_index": 3, // 3rd screen in this session
"session_id": "sess_abc-123"
},
"timestamp": "2026-05-04T10:15:30.123Z"
}
```
***
### Manual Screen Tracking (Granular Control)
**Use case:** You want to add screen-specific properties (product ID, category, etc.)
```dart theme={null}
// lib/screens/product_detail_screen.dart
class ProductDetailScreen extends StatefulWidget {
final String productId;
const ProductDetailScreen({required this.productId});
@override
_ProductDetailScreenState createState() => _ProductDetailScreenState();
}
class _ProductDetailScreenState extends State {
late Product _product;
DateTime? _screenEnteredAt;
@override
void initState() {
super.initState();
_screenEnteredAt = DateTime.now();
_loadProduct();
}
Future _loadProduct() async {
_product = await ProductService.getProduct(widget.productId);
// Track screen view with product context
Zixflow.instance.screen(
name: 'Product Detail',
properties: {
'product_id': _product.id,
'product_name': _product.name,
'product_category': _product.category,
'product_price': _product.price,
'inventory_status': _product.inStock ? 'in_stock' : 'out_of_stock',
},
);
}
@override
void dispose() {
// Track time spent on screen
if (_screenEnteredAt != null) {
final timeOnScreen = DateTime.now().difference(_screenEnteredAt!);
Zixflow.instance.track('Screen Exited', {
'screen_name': 'Product Detail',
'product_id': _product.id,
'time_on_screen_seconds': timeOnScreen.inSeconds,
'interaction_count': _interactionCount, // Taps, swipes, etc.
});
}
super.dispose();
}
}
```
***
## Feature Usage Events
### Feature Discovered
**When:** User views feature for first time (onboarding, tutorial, tooltip)
```dart theme={null}
void _showFeatureTutorial() {
setState(() => _tutorialVisible = true);
Zixflow.instance.track('Feature Discovered', {
'feature_name': 'ar_try_on',
'discovery_method': 'tooltip', // vs 'onboarding', 'organic', 'search'
'days_since_signup': _daysSinceSignup,
'onboarding_step': 3,
});
}
```
**Analytics:** Feature discovery rate, time to discovery
***
### Feature Used
**When:** User actively engages with feature
```dart theme={null}
void _launchARTryOn() {
Zixflow.instance.track('Feature Used', {
'feature_name': 'ar_try_on',
'product_id': widget.productId,
'is_first_use': !_userProfile.hasUsedAR,
'days_since_discovery': _daysSinceDiscovered,
});
// Launch AR experience
Navigator.push(context, MaterialPageRoute(
builder: (context) => ARTryOnScreen(product: _product),
));
// Update profile attribute
Zixflow.instance.setProfileAttributes({
'has_used_ar': true,
'ar_first_used_at': DateTime.now().toIso8601String(),
});
}
```
***
### Search Performed
**When:** User submits search query
```dart theme={null}
class SearchScreen extends StatefulWidget {
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State {
final TextEditingController _searchController = TextEditingController();
List _results = [];
Future _performSearch(String query) async {
final results = await ProductService.search(query);
setState(() => _results = results);
Zixflow.instance.track('Search Performed', {
'query': query,
'results_count': results.length,
'search_type': 'full_text', // vs 'barcode_scan', 'voice', 'visual'
'filters_applied': _activeFilters,
'sort_by': _sortOrder,
});
// Track zero-results searches (product gap opportunity)
if (results.isEmpty) {
Zixflow.instance.track('Search No Results', {
'query': query,
'suggestions_shown': _suggestionEngine.getSuggestions(query),
});
}
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _searchController,
onSubmitted: _performSearch,
decoration: InputDecoration(hintText: 'Search products...'),
);
}
}
```
***
### Content Shared
**When:** User shares via native share sheet
```dart theme={null}
void _shareProduct() async {
Zixflow.instance.track('Content Shared', {
'content_type': 'product',
'product_id': _product.id,
'product_name': _product.name,
'share_method': 'native_sheet', // vs 'copy_link', 'qr_code'
});
final result = await Share.share(
'Check out ${_product.name}!\n${_product.url}',
subject: _product.name,
);
// Track share outcome
if (result.status == ShareResultStatus.success) {
Zixflow.instance.track('Content Shared Success', {
'product_id': _product.id,
'platform': result.raw, // 'WhatsApp', 'Messages', 'Twitter', etc.
});
}
}
```
**Analytics:** Viral coefficient, most-shared products
***
## Push Notification Events
### Push Permission Requested
**When:** App asks for notification permission
```dart theme={null}
void _requestPushPermission() async {
Zixflow.instance.track('Push Permission Requested', {
'prompt_type': 'soft_ask', // Pre-permission priming
'days_since_install': _daysSinceInstall,
'screen': 'onboarding_step_3',
});
final granted = await _showPermissionDialog();
if (granted) {
_requestSystemPermission();
} else {
Zixflow.instance.track('Push Permission Soft Declined', {
'reason': _userDeclineReason, // From survey
});
}
}
```
***
### Push Permission Granted/Denied
**When:** User responds to system permission prompt
```dart theme={null}
void _requestSystemPermission() async {
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// Permission granted
Zixflow.instance.track('Push Permission Granted', {
'prompt_number': _promptAttemptCount,
'days_since_install': _daysSinceInstall,
});
// Get device token and register
final token = await FirebaseMessaging.instance.getToken();
Zixflow.instance.registerDeviceToken(token!);
// Update profile
Zixflow.instance.setProfileAttributes({
'push_enabled': true,
'push_enabled_at': DateTime.now().toIso8601String(),
});
// Note: This call works correctly whether the user is identified or still anonymous.
// If the user hasn't signed in yet, the device token is stored under their anonymousId
// and automatically linked to their user profile when they call identify() at sign-up.
// No extra re-registration step is needed after sign-up.
} else {
// Permission denied
Zixflow.instance.track('Push Permission Denied', {
'prompt_number': _promptAttemptCount,
'days_since_install': _daysSinceInstall,
'status': settings.authorizationStatus.toString(),
});
}
}
```
**Analytics:** Permission grant rate, optimal timing for prompt
***
### Push Received
**When:** Push notification arrives (app in foreground or background)
```dart theme={null}
// Configure FCM listener
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// App in foreground
Zixflow.instance.track('Push Received', {
'campaign_id': message.data['campaign_id'],
'message_id': message.messageId,
'title': message.notification?.title,
'app_state': 'foreground',
});
// Show in-app notification (optional)
_showInAppNotification(message);
});
```
***
### Push Opened
**When:** User taps notification (SDK auto-tracks)
**Auto-tracked:**
```javascript theme={null}
{
"event": "Push Opened",
"properties": {
"campaign_id": "camp_spring_sale",
"message_id": "msg_abc-123",
"title": "Your cart is waiting!",
"deep_link": "app://cart",
"time_to_open_seconds": 120, // Time since delivery
"app_state": "background" // App was backgrounded when tapped
}
}
```
**Custom handling:**
```dart theme={null}
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
Zixflow.instance.track('Push Opened', {
'campaign_id': message.data['campaign_id'],
'deep_link': message.data['deep_link'],
});
// Navigate to deep link destination
_navigateToDeepLink(message.data['deep_link']);
});
```
***
## Offline Behavior
### Event Queueing
**SDK handles offline automatically:**
1. **Network unavailable:** Events persisted to SQLite
2. **Battery optimization:** Batch 20 events per network call
3. **Retry logic:** Exponential backoff (1s, 2s, 4s, 8s, 16s)
4. **Max retries:** 10 attempts, then discard (prevents infinite queue growth)
**Developer control:**
```dart theme={null}
// Force immediate flush (critical events)
Zixflow.instance.track('Purchased', {...});
Zixflow.instance.flush(); // Send immediately, don't batch
// Check network status
final networkAvailable = await Connectivity().checkConnectivity();
if (networkAvailable == ConnectivityResult.none) {
// Show "Offline mode" indicator
_showOfflineBanner();
}
```
***
### Offline Analytics
**Track offline sessions separately:**
```dart theme={null}
class ConnectivityService {
bool _isOnline = true;
DateTime? _offlineSince;
void _onConnectivityChange(ConnectivityResult result) {
final wasOffline = !_isOnline;
_isOnline = result != ConnectivityResult.none;
if (_isOnline && wasOffline) {
// Back online
final offlineDuration = DateTime.now().difference(_offlineSince!);
Zixflow.instance.track('Back Online', {
'offline_duration_seconds': offlineDuration.inSeconds,
'events_queued': Zixflow.instance.getQueueSize(),
});
// Flush queued events
Zixflow.instance.flush();
} else if (!_isOnline) {
// Went offline
_offlineSince = DateTime.now();
Zixflow.instance.track('Went Offline', {
'network_type': result.toString(),
'screen': _currentScreen,
});
}
}
}
```
**Analytics:** Offline usage patterns, feature availability gaps
***
## Performance Monitoring
### Screen Load Time
```dart theme={null}
class ProductDetailScreen extends StatefulWidget {
@override
_ProductDetailScreenState createState() => _ProductDetailScreenState();
}
class _ProductDetailScreenState extends State {
DateTime? _loadStartTime;
@override
void initState() {
super.initState();
_loadStartTime = DateTime.now();
_loadProduct();
}
Future _loadProduct() async {
final product = await ProductService.getProduct(widget.productId);
final loadTime = DateTime.now().difference(_loadStartTime!);
// Track performance
Zixflow.instance.track('Screen Loaded', {
'screen_name': 'Product Detail',
'product_id': widget.productId,
'load_time_ms': loadTime.inMilliseconds,
'data_source': product.cachedLocally ? 'cache' : 'api',
'image_count': product.images.length,
});
setState(() => _product = product);
}
}
```
***
### API Call Performance
```dart theme={null}
class ApiClient {
Future get(String endpoint) async {
final startTime = DateTime.now();
try {
final response = await http.get(Uri.parse(endpoint));
final duration = DateTime.now().difference(startTime);
Zixflow.instance.track('API Call Completed', {
'endpoint': endpoint,
'status_code': response.statusCode,
'duration_ms': duration.inMilliseconds,
'response_size_bytes': response.body.length,
'success': response.statusCode \< 400,
});
return response;
} catch (e) {
final duration = DateTime.now().difference(startTime);
Zixflow.instance.track('API Call Failed', {
'endpoint': endpoint,
'duration_ms': duration.inMilliseconds,
'error_type': e.runtimeType.toString(),
'error_message': e.toString(),
});
rethrow;
}
}
}
```
**Analytics:** P50/P95/P99 latency by endpoint, error rates
***
## Implementation Checklist
### Phase 1: Core Tracking (Week 1)
* [ ] SDK integration (initialization, config)
* [ ] App Installed, Opened, Backgrounded
* [ ] Screen tracking (auto or manual)
* [ ] User identification on signup/login
* [ ] Device token registration
### Phase 2: Feature Instrumentation (Week 2)
* [ ] Feature Used events (top 5 features)
* [ ] Search Performed
* [ ] Content Shared
* [ ] Purchase flow events (Product Viewed, Added to Cart, Purchased)
* [ ] Profile attribute updates (plan type, feature usage)
### Phase 3: Push Notifications (Week 3)
* [ ] Permission request flow (soft ask + system prompt)
* [ ] Push Received, Opened tracking
* [ ] Deep link handling
* [ ] Campaign: Onboarding push sequence
* [ ] Campaign: Cart abandonment push
### Phase 4: Optimization (Week 4)
* [ ] Performance monitoring (screen load, API calls)
* [ ] Offline behavior handling
* [ ] Event batching tuning
* [ ] Retention analysis (DAU/MAU, cohorts)
* [ ] Push notification A/B testing
***
**Next:** SaaS product tracking patterns → [SaaS Product Tracking](/documentation/events/scenarios/saas)
# SaaS Product Analytics
Source: https://docs.zixflow.com/documentation/events/scenarios/saas
Tracking patterns for B2B/B2C SaaS — trials, features, teams, and billing.
Imagine you're running a B2B SaaS product — think a project management or analytics tool where companies sign up, invite their team, and pay by subscription. This guide walks through tracking both individual user actions and account-level activity: from signup and activation through feature usage, team collaboration, plan upgrades, and early churn signals.
```mermaid theme={null}
flowchart LR
signup[Signup Completed] --> invite[Invite Teammate]
invite --> activate[User Activated]
activate --> feature[Feature Used]
feature --> upgrade[Plan Upgraded]
```
***
## The SaaS Event Model
### Two-Level Tracking: User + Account
**Unlike B2C**, SaaS requires tracking both:
1. **Individual users** (seats, roles, activity)
2. **Accounts/Organizations** (subscriptions, usage limits, billing)
**Implementation:**
```javascript theme={null}
// Track at user level
track('Feature Used', {
feature_name: 'custom_reports',
user_role: 'admin',
account_id: 'acct_123', // Link to account
workspace_id: 'ws_456'
});
// Track at account level (server-side)
track('Account Upgraded', {
account_id: 'acct_123',
from_plan: 'professional',
to_plan: 'enterprise',
seat_count: 25,
mrr_change: 500,
contract_value: 6000 // Annual contract
});
```
**Profile attributes:**
```javascript theme={null}
// User-level
setProfileAttributes({
account_id: 'acct_123',
role: 'admin',
department: 'marketing',
invited_by: 'user_owner_789',
activated: true,
activation_date: '2026-05-01T10:00:00Z'
});
// Account-level (stored separately)
setAccountAttributes('acct_123', {
plan_type: 'enterprise',
mrr: 1200,
seat_count: 25,
seats_occupied: 18,
trial_ends_at: null, // No longer on trial
billing_status: 'active',
created_at: '2025-01-15T10:00:00Z',
industry: 'E-commerce',
company_size: '100-500'
});
```
***
## Activation Events (PLG Focus)
### Signup Completed
**When:** User creates account (self-serve signup)
```javascript theme={null}
// Client-side
identify({
userId: 'user_12345',
traits: {
email: 'john@acme.com',
name: 'John Smith',
company: 'Acme Corp',
role: 'Marketing Manager',
signup_source: 'organic_search',
signup_flow: 'free_trial', // vs 'demo_request', 'sales_led'
signup_page: '/pricing'
}
});
track('Signup Completed', {
account_id: 'acct_123', // Auto-generated or from invite link
is_account_creator: true,
trial_plan: 'professional',
trial_duration_days: 14,
signup_time_seconds: 45, // Time from form start to completion
utm_source: 'google',
utm_campaign: 'spring_2026_search'
});
```
**Trigger:** Welcome email + onboarding sequence
***
### Activation Milestone Events
**Why activation matters:** Activated users = 3x higher retention, 5x higher conversion to paid.
**Define your activation criteria** (example for project management tool):
* Created first project
* Invited team member
* Created first task
* Assigned task to someone
* Completed at least one task
**Track each milestone:**
```javascript theme={null}
// Milestone 1: First project created
track('First Project Created', {
project_name: 'Q2 Marketing Campaign',
project_type: 'marketing',
days_since_signup: 0, // Happened immediately
time_to_milestone_minutes: 5
});
setProfileAttributes({
activation_step: 1,
activation_milestone_1_completed: true,
activation_milestone_1_at: '2026-05-04T10:05:00Z'
});
// Milestone 2: First team member invited
track('First Team Member Invited', {
invitee_email: 'sarah@acme.com',
invitee_role: 'member',
days_since_signup: 0,
time_to_milestone_minutes: 12,
invite_method: 'email'
});
setProfileAttributes({
activation_step: 2,
activation_milestone_2_completed: true,
activation_milestone_2_at: '2026-05-04T10:12:00Z'
});
// ... continue for all milestones
// Final activation
track('User Activated', {
days_to_activation: 0, // Activated same day as signup
time_to_activation_minutes: 45,
activation_path: 'power_user', // vs 'slow_adopter', 'invited_user'
milestones_completed: 5
});
setProfileAttributes({
activated: true,
activation_date: '2026-05-04T10:45:00Z',
activation_cohort: '2026-05-W18' // Week 18 of 2026
});
```
**Analytics: Activation funnel**
```sql theme={null}
SELECT
COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END) AS signups,
COUNT(DISTINCT CASE WHEN event_name = 'First Project Created' THEN user_id END) AS milestone_1,
COUNT(DISTINCT CASE WHEN event_name = 'First Team Member Invited' THEN user_id END) AS milestone_2,
COUNT(DISTINCT CASE WHEN event_name = 'First Task Created' THEN user_id END) AS milestone_3,
COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) AS activated
FROM events
WHERE workspace_id = 'ws_123'
AND timestamp >= DATE_TRUNC('month', NOW());
```
***
## Feature Usage Events
### Core Feature Used
**When:** User engages with key product feature
```javascript theme={null}
track('Report Generated', {
report_type: 'custom', // vs 'template', 'scheduled'
report_name: 'Monthly Sales Performance',
data_range: 'last_30_days',
filters_applied: ['region: US', 'product_category: Electronics'],
visualization_types: ['line_chart', 'bar_chart', 'data_table'],
export_format: 'pdf', // vs 'csv', 'excel', null (viewed only)
generation_time_seconds: 2.3,
row_count: 15420,
is_first_use: false
});
```
**Use cases:**
* **Usage-based pricing:** Track API calls, reports generated, emails sent
* **Feature adoption:** % of users who've used Feature X
* **Power user identification:** Users who generate 10+ reports/month
***
### Premium Feature Gated
**When:** Free/trial user hits paywall
```javascript theme={null}
track('Premium Feature Gated', {
feature_name: 'advanced_analytics',
user_plan: 'free',
required_plan: 'professional',
gate_type: 'soft', // Showed upgrade CTA vs 'hard' (feature disabled)
interaction: 'clicked_upgrade', // vs 'dismissed', 'closed'
days_since_signup: 7,
usage_this_month: 145 // Free tier allows 100
});
```
**Trigger:** Upgrade prompt email or in-app message
***
### Collaboration Events
**Team collaboration = higher retention + account expansion**
#### Team Member Invited
```javascript theme={null}
track('Team Member Invited', {
invitee_email: 'sarah@acme.com',
invitee_role: 'member', // vs 'admin', 'viewer'
invited_by: 'user_12345',
invite_method: 'email', // vs 'link', 'bulk_csv'
account_seat_count: 5,
seats_occupied_before: 2,
seats_occupied_after: 3
});
```
***
#### Team Member Joined
```javascript theme={null}
// When invitee accepts invitation
identify({
userId: 'user_67890',
traits: {
email: 'sarah@acme.com',
account_id: 'acct_123',
role: 'member',
invited_by: 'user_12345',
joined_via: 'email_invite'
}
});
track('Team Member Joined', {
account_id: 'acct_123',
user_role: 'member',
invited_by: 'user_12345',
days_to_accept: 1, // Time from invite to acceptance
seats_occupied: 3,
seat_limit: 5
});
```
***
#### Mentioned User
```javascript theme={null}
track('User Mentioned', {
mentioned_user_id: 'user_67890',
mention_context: 'task_comment', // vs 'document', 'chat'
entity_id: 'task_456',
entity_type: 'task'
});
```
**Trigger:** Notification to mentioned user
***
#### Shared Content
```javascript theme={null}
track('Content Shared', {
content_type: 'dashboard',
content_id: 'dash_789',
shared_with: ['user_67890', 'user_11111'],
permission_level: 'view', // vs 'edit', 'admin'
share_method: 'internal', // vs 'public_link', 'email'
});
```
**Analytics:** Collaboration index = (mentions + shares + comments) / user count
***
## Usage-Based Pricing Events
### Quota Tracked
**For usage-based billing (API calls, storage, seats, etc.)**
```javascript theme={null}
// Server-side event (triggered by API gateway)
track('API Call Made', {
account_id: 'acct_123',
endpoint: '/api/v1/analytics/reports',
method: 'POST',
user_id: 'user_12345',
response_time_ms: 145,
status_code: 200,
quota_consumed: 1, // 1 API call
quota_remaining: 4850, // 150 calls left this month
quota_limit: 5000,
billing_cycle: '2026-05'
});
```
***
### Quota Warning Triggered
**When:** User approaching usage limit
```javascript theme={null}
// Server-side (quota monitoring service)
if (accountUsage.apiCalls >= accountUsage.quota * 0.8) {
track('Quota Warning Triggered', {
account_id: 'acct_123',
quota_type: 'api_calls',
quota_used: 4000,
quota_limit: 5000,
quota_percentage: 80,
warning_level: '80_percent', // vs '90_percent', '100_percent'
billing_cycle: '2026-05',
days_left_in_cycle: 10
});
// Trigger email to account admin
}
```
**Trigger:** Email to admins + in-app banner
***
### Quota Exceeded
**When:** User hits hard limit
```javascript theme={null}
track('Quota Exceeded', {
account_id: 'acct_123',
quota_type: 'api_calls',
quota_limit: 5000,
attempted_action: 'generate_report',
days_left_in_cycle: 8,
overage_by: 1,
current_plan: 'professional',
upgrade_offer_shown: true
});
```
**Trigger:** Upgrade prompt + sales notification (high-value account)
***
## Account Expansion Events
### Seat Added
**When:** Account increases seat count
```javascript theme={null}
track('Seat Added', {
account_id: 'acct_123',
seats_before: 5,
seats_after: 10,
added_by: 'user_12345', // Account admin
reason: 'new_hires', // vs 'seasonal_growth', 'expansion'
mrr_change: 200, // $40/seat/month * 5 seats
prorated_charge: 150 // Mid-cycle addition
});
```
***
### Plan Upgraded
**When:** Account moves to higher tier
```javascript theme={null}
track('Plan Upgraded', {
account_id: 'acct_123',
from_plan: 'professional',
to_plan: 'enterprise',
upgrade_trigger: 'quota_exceeded', // vs 'sales_call', 'self_serve'
seat_count: 10,
mrr_before: 400,
mrr_after: 1200,
contract_length_months: 12,
contract_value: 14400,
discount_applied: 10, // 10% annual discount
upgraded_by: 'user_12345',
sales_rep: 'rep_sarah_jones' // If sales-assisted
});
// Update account attributes
setAccountAttributes('acct_123', {
plan_type: 'enterprise',
mrr: 1200,
contract_value: 14400,
billing_cycle: 'annual',
upgraded_at: '2026-05-04T10:00:00Z'
});
```
**Trigger:** Thank-you email + account manager introduction (enterprise)
***
### Feature Tier Unlocked
**When:** Upgrade unlocks new features
```javascript theme={null}
track('Feature Tier Unlocked', {
account_id: 'acct_123',
plan: 'enterprise',
features_unlocked: [
'sso_authentication',
'custom_reports',
'api_access',
'dedicated_support'
],
previous_limitations: {
'reports_per_month': 50,
'api_calls_per_month': 5000
},
new_limits: {
'reports_per_month': 'unlimited',
'api_calls_per_month': 50000
}
});
```
**Trigger:** Feature announcement email + onboarding guide
***
## Churn Signals
### Negative Engagement Events
**Track behaviors indicating churn risk:**
#### Usage Dropped
```javascript theme={null}
// Server-side (daily cron job)
const lastWeekActivity = getActivityCount(accountId, 'last_7_days');
const previousWeekActivity = getActivityCount(accountId, '8_to_14_days_ago');
if (lastWeekActivity < previousWeekActivity * 0.5) {
track('Usage Dropped Significantly', {
account_id: accountId,
last_week_activity: lastWeekActivity,
previous_week_activity: previousWeekActivity,
drop_percentage: 60,
primary_users_inactive: ['user_12345', 'user_67890'],
plan_type: 'professional',
mrr: 400
});
}
```
**Trigger:** Outreach email from customer success
***
#### Cancellation Flow Started
```javascript theme={null}
track('Cancellation Flow Started', {
account_id: 'acct_123',
initiated_by: 'user_12345',
user_role: 'admin',
plan_type: 'professional',
mrr: 400,
account_age_days: 180,
last_active_days_ago: 14,
retention_offer_shown: true,
retention_offer_type: '20_percent_discount'
});
```
**Trigger:** Save workflow (discount offer, feature unlock, account review call)
***
#### Cancellation Reason Selected
```javascript theme={null}
track('Cancellation Reason Selected', {
account_id: 'acct_123',
reason: 'too_expensive', // vs 'missing_features', 'switching_to_competitor', 'no_longer_needed'
reason_details: 'Budget cuts this quarter',
competitor_name: null, // Or 'Competitor X' if switching
requested_export: true,
requested_refund: false,
feedback_provided: true
});
```
**Analytics:** Top churn reasons by plan type, MRR cohort
***
#### Account Downgraded
```javascript theme={null}
track('Plan Downgraded', {
account_id: 'acct_123',
from_plan: 'professional',
to_plan: 'starter',
downgrade_reason: 'budget_constraints',
seat_count_before: 10,
seat_count_after: 5,
mrr_before: 400,
mrr_after: 150,
mrr_change: -250,
downgraded_by: 'user_12345',
features_lost: ['custom_reports', 'api_access']
});
```
**Trigger:** Win-back campaign (show ROI of lost features)
***
## Analytics Queries
> These SQL snippets are **illustrative** analytics patterns. Use Zixflow dashboards and APIs for real reporting — not direct database access.
### Activation Rate by Signup Source
```sql theme={null}
SELECT
properties->>'signup_source' AS source,
COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END) AS signups,
COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) AS activated,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) /
COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END), 2) AS activation_rate
FROM events
WHERE workspace_id = 'ws_123'
AND timestamp >= DATE_TRUNC('month', NOW())
GROUP BY source
ORDER BY activation_rate DESC;
```
***
### Feature Adoption by Plan Type
```sql theme={null}
SELECT
u.traits->>'plan_type' AS plan,
COUNT(DISTINCT e.user_id) AS total_users,
COUNT(DISTINCT CASE WHEN e.event_name = 'Report Generated' THEN e.user_id END) AS used_reports,
COUNT(DISTINCT CASE WHEN e.event_name = 'API Call Made' THEN e.user_id END) AS used_api,
COUNT(DISTINCT CASE WHEN e.event_name = 'Dashboard Created' THEN e.user_id END) AS used_dashboards,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN e.event_name = 'Report Generated' THEN e.user_id END) / COUNT(DISTINCT e.user_id), 2) AS reports_adoption_pct
FROM profiles u
LEFT JOIN events e ON u.user_id = e.user_id
WHERE u.workspace_id = 'ws_123'
AND e.timestamp >= NOW() - INTERVAL 30 DAY
GROUP BY plan
ORDER BY plan;
```
***
### Revenue Expansion by Cohort
```sql theme={null}
WITH cohorts AS (
SELECT
user_id,
DATE_TRUNC('month', traits->>'created_at'::TIMESTAMP) AS cohort_month
FROM profiles
WHERE workspace_id = 'ws_123'
),
upgrades AS (
SELECT
user_id,
(properties->>'mrr_change')::DECIMAL AS mrr_change,
timestamp
FROM events
WHERE event_name = 'Plan Upgraded'
AND workspace_id = 'ws_123'
)
SELECT
c.cohort_month,
COUNT(DISTINCT c.user_id) AS cohort_size,
SUM(COALESCE(u.mrr_change, 0)) AS total_expansion_mrr,
AVG(COALESCE(u.mrr_change, 0)) AS avg_expansion_per_user
FROM cohorts c
LEFT JOIN upgrades u ON c.user_id = u.user_id
GROUP BY c.cohort_month
ORDER BY c.cohort_month DESC;
```
***
## Implementation Checklist
### Phase 1: Signup & Activation (Week 1)
* [ ] Signup Completed event
* [ ] Define activation milestones (5-7 steps)
* [ ] Track each milestone event
* [ ] User Activated event
* [ ] Onboarding email sequence
* [ ] Profile attributes: activated, activation\_date, activation\_cohort
### Phase 2: Feature Usage (Week 2)
* [ ] Top 5 feature usage events
* [ ] Premium Feature Gated events
* [ ] Collaboration events (invited, joined, mentioned)
* [ ] Usage-based quota tracking
* [ ] Quota warning/exceeded triggers
### Phase 3: Expansion & Retention (Week 3)
* [ ] Plan Upgraded/Downgraded events
* [ ] Seat Added event
* [ ] Usage Dropped detection (cron job)
* [ ] Cancellation Flow Started
* [ ] At-risk account segment + outreach campaign
### Phase 4: Analytics & Optimization (Week 4)
* [ ] Activation rate dashboard (by source, plan)
* [ ] Feature adoption analysis
* [ ] Cohort retention + expansion
* [ ] Churn reason analysis
* [ ] Expansion-ready account identification
***
**Next:** Learn about communication channels and messaging → [Communication Channels](/documentation/events/channels-overview)
# Understanding Events
Source: https://docs.zixflow.com/documentation/events/understanding-events
What events are, event types, naming conventions, and best practices.
Events are the foundation of your data — they record every action users take in your application. This guide explains what events are, the different types you can track, and best practices for naming and structuring them.
***
## What is an Event?
An **event** is a timestamped record of something that happened in your product. It's like a digital breadcrumb trail that captures every meaningful action your users take.
Think of events as the **verbs** of your data model. While user attributes describe **who** someone is (their name, email, plan type), events describe **what** they do (clicked, purchased, logged in, shared).
### The Simple Definition
If you can describe it as "User did X at time Y," it's an event.
**Examples:**
* "Sarah added Red Sneakers to cart at 2:30 PM" → Event: `Added to Cart`
* "John upgraded to Pro plan at 10:15 AM" → Event: `Upgraded Plan`
* "Anonymous visitor viewed pricing page at 3:45 PM" → Event: `Viewed Pricing Page`
### Why Events Matter
Events are powerful because they let you:
1. **Understand user behavior**: See exactly what users do in your product
2. **Measure what matters**: Count how many people signed up, purchased, or churned
3. **Build conversion funnels**: Track the path from first visit to purchase
4. **Trigger automated workflows**: Send email when user abandons cart
5. **Personalize experiences**: "You recently viewed these items"
6. **Calculate business metrics**: Revenue, engagement, retention rates
### What Every Event Captures
Each event in Zixflow automatically records:
* **Who** did it → `userId` (if known) or `anonymousId` (if not)
* **What** they did → Event name like "Purchased" or "Signed Up"
* **When** it happened → Timestamp, precise to the millisecond
* **Where** it happened → Platform (web/iOS/Android), location, device type
* **How** they did it → Context like browser, screen size, app version
* **Custom details** → Properties you add (product name, price, category)
### Event Structure
Here's what a typical event looks like:
```json theme={null}
{
"event_name": "Added to Cart",
"user_id": "sarah@example.com",
"timestamp": "2026-05-04T14:32:15Z",
"properties": {
"product_name": "Red Sneakers",
"product_id": "prod_789",
"price": 89.99,
"currency": "USD",
"quantity": 1
},
"context": {
"platform": "web",
"browser": "Chrome",
"screen_size": "1920x1080",
"country": "United States"
}
}
```
**Key parts:**
* **event\_name**: The action that happened
* **properties**: Custom details about this specific action
* **context**: Automatically captured information about the user's environment
* **timestamp**: Exactly when it occurred
**What makes events powerful:** Once stored in Zixflow, you can search across millions of events instantly to find patterns, build segments, or trigger campaigns.
***
## Event Types
Zixflow supports four types of events. Each serves a different purpose in tracking user behavior:
```mermaid theme={null}
flowchart LR
track[Track] --> business[Custom business actions]
screen[Screen] --> mobileNav[Mobile navigation]
page[Page] --> webNav[Web navigation]
identify[Identify] --> identity[Link anonymous to known user]
```
### 1. **Track Events** (Custom Actions)
**What:** The events you define based on what matters to your business. This is your primary event type for tracking user behavior.
**When to use:**
* User lifecycle milestones: `Signed Up`, `Completed Onboarding`, `Upgraded Plan`
* Feature usage: `Created Project`, `Ran Report`, `Exported Data`
* Revenue events: `Subscribed`, `Payment Received`, `Refunded`
* Engagement: `Invited Teammate`, `Shared Dashboard`, `Left Comment`
**Examples:**
```javascript theme={null}
// JavaScript
Zixflow.track('Purchased', {
order_id: 'ord_12345',
revenue: 89.99,
currency: 'USD',
items: ['Red Sneakers', 'White Socks']
});
```
```dart theme={null}
// Flutter/Dart
Zixflow.track(
name: 'Purchased',
properties: {
'order_id': 'ord_12345',
'revenue': 89.99,
'currency': 'USD',
'items': ['Red Sneakers', 'White Socks']
}
);
```
**What you can do with track events:**
* Build segments: "Users who purchased in the last 30 days"
* Trigger campaigns: "Send thank-you email after Purchase event"
* Run analytics: "What % of signups complete a purchase?"
* Measure conversion: Track funnel from signup → activation → purchase
***
### 2. **Screen Events** (Mobile Navigation)
**What:** When a user views a screen in your mobile app (iOS, Android, React Native, Flutter).
**When to use:** Mobile applications where you want to track navigation flow.
**Examples:**
* `Home Screen`
* `Product Details`
* `Shopping Cart`
* `Checkout`
* `Profile Settings`
**Auto-tracking:** Our SDK can automatically track screen views when configured. You can also manually track specific screens.
**SDK call:**
```dart theme={null}
// Flutter - Manual screen tracking
Zixflow.instance.screen(
title: 'Product Details',
properties: {
'product_id': 'prod_789',
'category': 'Shoes',
},
);
```
**Use cases:**
* Navigation and screen-flow analysis
* Mobile funnel steps (e.g. Product Details → Cart → Checkout)
* Drop-off detection between screens
***
### 3. **Page Events** (Web Navigation)
**What:** When a user views a page on your website or web application.
**When to use:** Web applications, marketing sites, content platforms.
**Examples:**
* `/` (Homepage)
* `/pricing` (Pricing page)
* `/blog/getting-started` (Blog post)
* `/product/red-sneakers` (Product page)
* `/checkout` (Checkout page)
**Auto-tracking:** Can be configured to automatically track page views with our JavaScript SDK.
**SDK call:**
```javascript theme={null}
// JavaScript - Manual page tracking
Zixflow.page({
name: 'Product Details',
properties: {
path: '/product/red-sneakers',
url: 'https://store.example.com/product/red-sneakers',
referrer: 'https://google.com/search',
title: 'Red Sneakers - Example Store'
}
});
```
**Common use cases:**
* Track marketing campaign effectiveness
* Understand content engagement
* Build page-view funnels
* Identify high-exit pages
***
### 4. **Identify Events** (User Identification)
**What:** Special event that links anonymous browsing history to a known user profile. Triggers identity resolution and profile creation/update.
**When to use:**
* Immediately after signup (new user)
* On login (returning user)
* When you learn identifying info (email capture via newsletter signup)
**What happens on `identify()`:**
1. Creates or updates the user's profile with the information you provide
2. Records when this identification happened (for audit purposes)
3. **Identity merge**: If you include both `userId` and `anonymousId`, Zixflow automatically:
* Finds all anonymous activity from this visitor
* Links it to the identified user profile
* Updates the user's complete history
* Prevents duplicate merges (processes once)
**SDK call:**
```javascript theme={null}
// JavaScript - After signup/login
Zixflow.identify({
userId: 'sarah@example.com',
traits: {
name: 'Sarah Johnson',
email: 'sarah@example.com',
plan: 'pro',
created_at: '2026-05-04T10:00:00Z'
}
});
```
```dart theme={null}
// Flutter - After signup/login
Zixflow.instance.identify(
userId: 'sarah@example.com',
traits: {
'name': 'Sarah Johnson',
'email': 'sarah@example.com',
'plan': 'pro',
'created_at': '2026-05-04T10:00:00Z',
},
);
```
**What happens behind the scenes:**
1. Creates or updates user profile with provided information
2. Records when identification occurred
3. If both `userId` + `anonymousId` provided:
* Triggers identity merge workflow (see [Users & Identity](/documentation/events/users-and-identity))
* All anonymous events linked to identified user
* System prevents duplicate merges automatically
4. Subsequent events from this device/session use `user_id` instead of `anonymous_id`
***
## Event Properties Explained
Properties add context to events. They answer: *"What details matter about this action?"*
### Property Structure
Properties are key-value pairs attached to events:
```javascript theme={null}
Zixflow.track({
name: 'Purchased',
properties: {
// Order info
order_id: 'ord_12345',
revenue: 89.99,
currency: 'USD',
// Product info
product_name: 'Red Sneakers',
product_id: 'prod_789',
category: 'Shoes',
// Context
payment_method: 'credit_card',
shipping_method: 'express',
coupon_code: 'SAVE10'
}
});
```
### Common Property Patterns
**Product properties:**
* `product_id`: Unique identifier
* `product_name`: Human-readable name
* `sku`: Stock keeping unit
* `category`: Product category
* `brand`: Manufacturer/brand
* `price`: Unit price
* `currency`: USD, EUR, etc.
**Transaction properties:**
* `order_id`: Unique order identifier
* `revenue`: Total amount
* `tax`: Tax amount
* `shipping`: Shipping cost
* `discount`: Discount applied
* `payment_method`: How they paid
**User action context:**
* `source`: Where did they come from? (homepage, search, email)
* `referrer`: Full referrer URL
* `search_term`: What they searched for
* `filter_applied`: Active filters
* `sort_order`: How they sorted results
### Keep It Simple
**Rule of thumb:** Only track properties you'll actually use for:
* Personalization ("You viewed Red Sneakers")
* Analysis ("Average order value by payment method")
* Triggered campaigns ("Coupon code was used")
**Don't over-track:**
* ❌ 50 properties per event (too much)
* ❌ Duplicate data (if it's a user attribute, don't put it in every event)
* ❌ Sensitive PII (passwords, credit card numbers, SSNs)
***
## System vs Custom Events
### System Events (Auto-Generated)
These events are created automatically by the system — you don't need to call `track()` for them.
#### Device Lifecycle Events
Sent automatically by the SDK. They feed device health monitoring and platform analytics, and are separate from your business events:
| Event Name | When it fires |
| --------------------------- | ---------------------------------------------------------------------- |
| `Application Installed` | First launch after install |
| `Application Opened` | App moves to foreground |
| `Application Backgrounded` | App moves to background |
| `Application Foregrounded` | App returns from background (distinct from cold open) |
| `Application Crashed` | SDK detects an unhandled crash on next launch |
| `Device Created or Updated` | SDK registers or updates device info with Zixflow |
| `Device Registered` | Device token successfully registered for push |
| `Device Updated` | Device attributes changed (new OS version, app update, token rotation) |
These events use a shorter retention window (about 30 days) than your custom track events.
#### User Lifecycle Events
These are server-side lifecycle events that change the state of a user profile. They carry no trait data — only the user ID and the action:
| Lifecycle Event | Effect on profile |
| ------------------- | --------------------------------------------------------------------------- |
| `User Deleted` | Sets `is_deleted = true` + cascades deactivation to all linked devices |
| `User Suppressed` | Sets `is_suppressed = true` — blocks all outbound messages on every channel |
| `User Unsuppressed` | Clears the suppressed flag — message delivery resumes |
**User Deleted** and **User Suppressed/Unsuppressed** are triggered server-side (via the Track API v1 or your backend). You cannot trigger them from the client SDK.
#### Device Deletion
| Lifecycle Event | Effect |
| ---------------- | ------------------------------------------------------------------------------- |
| `Device Deleted` | Soft delete: sets `active = false`, `push_enabled = false` on the device record |
This stops push notifications being sent to that device without permanently removing the record.
### Custom Events (You Define)
Everything else! You decide what matters for your business:
* User actions: Clicked, Viewed, Purchased, Shared
* Product usage: Feature used, Setting changed, Export created
* Business events: Trial started, Plan upgraded, Referral sent
* Content engagement: Article read, Video watched, Comment posted
***
## When NOT to Use Events
Events are for actions. Attributes are for facts. Don't confuse the two!
### ❌ Wrong: Tracking State Changes as Events
**Bad example:**
```javascript theme={null}
// DON'T DO THIS
Zixflow.track({ name: 'Became Premium User' });
Zixflow.track({ name: 'Email Changed to sarah@example.com' });
Zixflow.track({ name: 'Name Changed to Sarah Johnson' });
```
**Why it's wrong:** These are **attribute updates**, not actions the user took.
### ✅ Right: Update Attributes + Track Action Event
**Good example:**
```javascript theme={null}
// Update the attribute (WHO they are)
Zixflow.setProfileAttributes({
plan: 'premium',
email: 'sarah@example.com',
name: 'Sarah Johnson'
});
// Track the action (WHAT they did)
Zixflow.track({
name: 'Upgraded Plan',
properties: {
from_plan: 'free',
to_plan: 'premium'
}
});
```
**Why it's right:** The attribute stores the current state. The event records that the upgrade happened (for analytics and triggers).
### Rule of Thumb
**Use attributes for:**
* Current state: email, name, plan, status
* Computed values: total\_purchases, last\_login, lifetime\_value
* Demographics: age, location, company
**Use events for:**
* Actions: clicked, viewed, purchased, upgraded
* One-time occurrences: signed\_up, cancelled, shared
* Timestamps: when things happened
***
## Privacy Considerations
### What NOT to Track
**Never** put these in event names or properties:
* ❌ Passwords or credentials
* ❌ Credit card numbers
* ❌ Social security numbers
* ❌ Medical information
* ❌ Full date of birth (age range is okay)
### What's Safe to Track
✅ User actions and behavior (clicked, viewed, purchased)\
✅ Product data (names, prices, categories)\
✅ Transaction IDs (order\_id, session\_id)\
✅ Aggregated data (count, average, total)\
✅ Email addresses (if user consented)\
✅ Device information (platform, OS version)
### GDPR/CCPA Compliance
Users have the right to:
* **Access their data**: Export all events via API
* **Delete their data**: Remove all events and profile via `DELETE /api/v1/customers/:id`
* **Opt out**: Stop tracking via unsubscribe/opt-out flags
***
**Next:** Learn how anonymous visitors become identified users → [Users & Identity](/documentation/events/users-and-identity)
# User Communication Preferences
Source: https://docs.zixflow.com/documentation/events/user-preferences
Managing user consent and communication preferences for compliance.
Give users control over what communications they receive and how you use their data. This guide covers capturing, storing, and respecting preferences in Zixflow.
***
## What Are User Preferences?
Preferences are settings that control:
* **Which channels** you can use (email yes, SMS no)
* **What types of messages** they want (product updates yes, promotions no)
* **How frequently** they hear from you
* **When** you can reach them (quiet hours)
```mermaid theme={null}
flowchart TD
send[Outbound message] --> suppressed{User suppressed?}
suppressed -->|Yes| block[Do not send]
suppressed -->|No| channel{Channel opted out?}
channel -->|Yes| block
channel -->|No| category{Category allowed?}
category -->|No| block
category -->|Yes| deliver[Send on channel]
```
**Ask once, respect forever.** Honor opt-outs immediately, store them permanently, and apply them across campaigns.
***
## Why Preferences Matter
**For users:** Control fatigue, choose channels, set frequency limits.
**For you:** Higher engagement, legal compliance (GDPR, CAN-SPAM, CCPA), better deliverability.
***
## Types of Preferences
### 1. Channel Preferences (What to Reach Them On)
**Definition:** Which communication channels the user accepts messages through. Each channel has its own opt-out field and timestamp stored on the user profile.
**Channel opt-out fields:**
| Channel | Field | Default state |
| -------- | ------------------ | -------------------------------- |
| Email | `email_opt_out` | `UNKNOWN` (pending confirmation) |
| SMS | `sms_opt_out` | `NOT_OPTED_OUT` |
| WhatsApp | `whatsapp_opt_out` | `NOT_OPTED_OUT` |
| RCS | `rcs_opt_out` | `NOT_OPTED_OUT` |
| Push | `push_opt_out` | `NOT_OPTED_OUT` |
Each field uses one of three values:
* `1` — `NOT_OPTED_OUT`: user is reachable (default for SMS/WhatsApp/RCS/Push)
* `2` — `OPTED_OUT`: user explicitly opted out — all sends suppressed
* `3` — `UNKNOWN`: intent not yet established (default for Email)
Each channel also stores an `_opt_out_at` timestamp:
```dart theme={null}
// User opts out of SMS
Zixflow.instance.setProfileAttributes({
'sms_opt_out': 2,
'sms_opt_out_at': DateTime.now().toIso8601String(),
});
// User opts out of push
Zixflow.instance.setProfileAttributes({
'push_opt_out': 2,
'push_opt_out_at': DateTime.now().toIso8601String(),
});
// User confirms email opt-in
Zixflow.instance.setProfileAttributes({
'email_opt_out': 1, // NOT_OPTED_OUT
});
```
**Important:** Email defaults to `UNKNOWN` rather than `NOT_OPTED_OUT` because email requires explicit confirmation before you can assume consent (GDPR). All other channels default to `NOT_OPTED_OUT` since they require an explicit action (push permission grant, phone number submission) to receive messages at all.
***
### 2. Category Preferences (What Type of Messages)
**Definition:** What topics or types of content the user wants to receive. Category preferences are custom attributes — you define the categories that make sense for your product and store them as profile attributes.
**Example:**
```dart theme={null}
Zixflow.instance.setProfileAttributes({
'subscribed_to_promotions': false, // No sales/discounts
'subscribed_to_product_updates': true, // Product news yes
'subscribed_to_newsletter': true, // Weekly newsletter yes
'subscribed_to_transactional': true, // Order updates (always on)
});
```
**Why categories matter:**
* User might want product updates but not promotional emails
* Transactional messages (order confirmations, password resets) typically can't be opted out
* GDPR requires granular control (not just "all or nothing")
***
### 3. Frequency Preferences (How Often to Message)
**Definition:** Controls how often users receive non-critical messages. Like categories, frequency preferences are custom attributes you define and check when building campaigns.
**Example:**
```dart theme={null}
Zixflow.instance.setProfileAttributes({
'email_frequency': 'daily_digest', // immediate | daily_digest | weekly_digest | never
'push_frequency': 'immediate',
'max_emails_per_week': 3,
});
```
***
### 4. User Suppression (Block All Channels)
**Suppression is a server-side lifecycle event** that blocks all outbound messages for a user across every channel, regardless of their individual opt-out settings.
| Lifecycle Event | Effect |
| ------------------- | ------------------------------------------------------------------------ |
| `User Suppressed` | Sets `is_suppressed = true` on profile — no messages sent on any channel |
| `User Unsuppressed` | Clears the flag — message delivery resumes |
**When to use suppression:**
* User requests complete silence (GDPR "right to be forgotten" equivalent)
* Account fraud/abuse detected
* Account is past-due / in bad standing
Suppression is distinct from channel opt-out: opt-out is per-channel and set by the user, suppression is workspace-controlled and blocks everything.
***
## Capturing Preferences
### Method 1: In-App Preference Center
**Best for:** Letting users manage their own preferences (GDPR requirement)
**Example UI:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Notification Preferences
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Email Preferences:
☑ Product updates & new features
☑ Weekly newsletter
☐ Promotions & discounts
☑ Order & account notifications
Push Notifications:
☑ New messages
☑ Friend activity
☐ Daily summary
[Save Preferences]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**Implementation:**
```javascript theme={null}
// User clicks "Save Preferences"
function savePreferences() {
const prefs = {
subscribed_to_product_updates: document.getElementById('product_updates').checked,
subscribed_to_newsletter: document.getElementById('newsletter').checked,
subscribed_to_promotions: document.getElementById('promotions').checked,
subscribed_to_transactional: true, // Always on
push_new_messages: document.getElementById('push_messages').checked,
push_friend_activity: document.getElementById('push_activity').checked,
push_daily_summary: document.getElementById('push_summary').checked
};
Zixflow.setProfileAttributes(prefs);
// Also track that they updated preferences
Zixflow.track('Updated Preferences', { changes: prefs });
}
```
***
### Method 2: Email Unsubscribe Links
**Best for:** One-click opt-out from email campaigns (CAN-SPAM requirement)
**Example flow:**
1. User receives promotional email
2. Clicks "Unsubscribe" link at bottom
3. Lands on preference page: "You've unsubscribed from promotional emails"
4. Option to manage granular preferences or unsubscribe from all
**Implementation:**
```javascript theme={null}
// Unsubscribe page loads
const urlParams = new URLSearchParams(window.location.search);
const userId = urlParams.get('user_id');
const category = urlParams.get('category'); // e.g., "promotions"
// Immediately unsubscribe
Zixflow.identify({
userId: userId,
traits: {
[`subscribed_to_${category}`]: false,
unsubscribed_at: new Date().toISOString()
}
});
Zixflow.track('Unsubscribed', {
category: category,
source: 'email_link'
});
// Show confirmation
document.getElementById('message').innerText =
`You've been unsubscribed from ${category} emails.`;
```
***
### Method 3: Onboarding Preference Collection
**Best for:** Setting preferences early (before first campaign)
**Example onboarding screen:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
How would you like to hear from us?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
☑ Email me about new features (weekly)
☑ Push notifications for messages (real-time)
☐ SMS me about special offers
[Continue]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**Why this works:**
* Sets expectations early ("we'll email you weekly")
* Reduces future unsubscribes (they opted in knowingly)
* Better engagement (only contact people who agreed)
***
## Respecting Preferences in Campaigns
### Transactional vs. Marketing Messages
**Transactional messages** (can't be opted out):
* Order confirmations
* Password resets
* Account security alerts
* Billing notifications
**Why:** User needs these to use your service (GDPR carve-out)
**Marketing messages** (must respect opt-out):
* Newsletters
* Promotional emails
* Feature announcements
* Re-engagement campaigns
**Implementation:**
```javascript theme={null}
// Transactional: Always send (ignore preferences)
sendEmail({
to: user.email,
template: 'order_confirmation',
bypass_preferences: true, // Send even if opted out of marketing
data: { order_id: 'ord_123' }
});
// Marketing: Check preferences first
if (user.subscribed_to_promotions && user.preferences_email) {
sendEmail({
to: user.email,
template: 'weekly_deals',
respect_preferences: true,
data: { deals: [...] }
});
}
```
***
## Default Preferences (What If User Hasn't Set Any?)
### Recommended Defaults
**Conservative approach** (better for compliance):
```javascript theme={null}
// Default user to opted OUT of marketing, IN for transactional
const defaultPreferences = {
subscribed_to_promotions: false, // Opt-in required
subscribed_to_newsletter: false, // Opt-in required
subscribed_to_product_updates: false, // Opt-in required
subscribed_to_transactional: true, // Always on
preferences_email: true, // Email enabled (but only transactional)
preferences_push: false, // Opt-in required
preferences_sms: false // Opt-in required
};
```
**Aggressive approach** (better for engagement, riskier legally):
```javascript theme={null}
// Default user to opted IN to everything
const defaultPreferences = {
subscribed_to_promotions: true, // Opt-out to stop
subscribed_to_newsletter: true, // Opt-out to stop
subscribed_to_product_updates: true,
subscribed_to_transactional: true,
preferences_email: true,
preferences_push: true, // Requires device token
preferences_sms: false // SMS usually requires explicit opt-in
};
```
**When to use each:**
* **Conservative**: EU users (GDPR), healthcare, financial services
* **Aggressive**: Existing customers, B2B, low-regulation industries
***
## Preference Syncing Across Devices
**Challenge:** User opts out of push notifications on phone, but still gets them on tablet.
**Solution:** Preferences are stored at the **user profile level**, not device level.
**How it works:**
```javascript theme={null}
// User opts out on iPhone
Zixflow.setProfileAttributes({
preferences_push: false // Applies to ALL devices
});
// Next campaign:
// → iPhone: No push (opted out)
// → iPad: No push (opted out)
// → Web: No push (opted out)
```
**Device-specific preferences** (if needed):
```javascript theme={null}
// Opt out on THIS device only
Zixflow.setDeviceAttributes({
push_enabled: false // Only this device
});
```
***
## Compliance & Legal Requirements
### GDPR (Europe)
**Requirements:**
✅ Clear consent before marketing communications\
✅ Easy way to withdraw consent (unsubscribe link)\
✅ Granular control (not just "all or nothing")\
✅ Explicit opt-in for sensitive categories (health, finance)
**Implementation:**
* Default users to opted OUT
* Require checkbox during signup: "I agree to receive marketing emails"
* Provide preference center with granular controls
***
### CAN-SPAM (United States)
**Requirements:**
✅ Unsubscribe link in every marketing email\
✅ Honor opt-out within 10 business days\
✅ Clear "From" name (not deceptive)\
✅ Accurate subject line
**Implementation:**
* Include unsubscribe link in email footer
* Immediately set `subscribed_to_promotions = false` on click
* Exclude opted-out users from all marketing campaigns
***
### CCPA (California)
**Requirements:**
✅ Allow users to opt out of data "sale" (sharing for advertising)\
✅ Provide "Do Not Sell My Personal Information" link\
✅ Honor opt-out immediately
**Implementation:**
```javascript theme={null}
// User clicks "Do Not Sell"
Zixflow.setProfileAttributes({
ccpa_opt_out: true, // Don't share data with ad platforms
opted_out_at: new Date().toISOString()
});
```
***
**Next:** Server-side tracking and lifecycle APIs → [Event API Reference](/documentation/events/event-api-reference)
# Users & Identity
Source: https://docs.zixflow.com/documentation/events/users-and-identity
How anonymous visitors become identified users and how identity merging works.
Understanding how Zixflow tracks users is fundamental to getting value from your event data. This guide explains anonymous visitors, identified users, and how they connect into one customer journey.
***
## The Two Types of Users
Every person who interacts with your product exists in one of two states: **anonymous** or **identified**.
```mermaid theme={null}
flowchart LR
anon[Anonymous visitor] -->|identify with userId| id[Identified user]
anon -->|browse / track| eventsA[Events under anonymousId]
id -->|track| eventsI[Events under userId]
eventsA -.->|merge on identify| eventsI
```
***
## Anonymous Users
An anonymous user is someone using your product **before** you know who they are. They haven't signed up, logged in, or given you an email.
When someone first visits your site or opens your app, the SDK generates an `anonymousId` (UUID) and stores it:
* **Web:** First-party cookie (or localStorage fallback)
* **Mobile:** Keychain (iOS) or SharedPreferences (Android)
* **Server-side:** Your session layer (you generate and pass it)
The ID stays stable on the same device/browser until cookies/storage are cleared — or until the user identifies.
### What you can do
* Track behavior and funnels
* Personalize in-session experiences
* Attribute first-touch campaigns
* Send push if a device token is registered before signup
### What you cannot do
* Email or SMS them (no contact info yet)
* Unify activity across devices until they identify on each device
***
## Identified Users
An identified user is someone you **know**. Call `identify()` when they sign up, log in, capture email, use OAuth, or complete guest checkout:
```javascript theme={null}
Zixflow.identify({
userId: "user_12345", // Your stable internal ID
traits: {
email: "sarah@example.com",
name: "Sarah Johnson",
plan: "free"
}
});
```
```dart theme={null}
Zixflow.instance.identify(
userId: 'user_12345',
traits: {
'email': 'sarah@example.com',
'name': 'Sarah Johnson',
'plan': 'free',
},
);
```
Once identified you can message across channels, track across devices, enrich from CRM/billing, and reconstruct the full journey.
***
## Identity Merging
Identity merging connects pre-signup activity to the known profile when you call `identify()` with both `userId` and `anonymousId` (SDKs usually send the current anonymous ID automatically).
```mermaid theme={null}
sequenceDiagram
participant User
participant App
participant Zixflow
User->>App: Browses anonymously
App->>Zixflow: track events with anonymousId
User->>App: Signs up / logs in
App->>Zixflow: identify userId + anonymousId
Zixflow->>Zixflow: Merge anonymous history into profile
Zixflow->>Zixflow: Link pre-signup devices
App->>Zixflow: Future events use userId
```
**What Zixflow does:**
1. Finds events (and devices) for that `anonymousId`
2. Upgrades the anonymous profile in place — `first_seen_at` stays as the first visit, not signup time
3. Links history under the identified `userId`
4. Prevents duplicate merges
**Why it matters:** Multi-touch attribution, true funnel conversion rates (including anonymous drop-off), and onboarding personalization from pre-signup intent.
***
## Choosing a User ID
| Approach | Pros | Cons |
| -------------------------------------- | -------------- | --------------------------- |
| **Database primary key** (recommended) | Stable, unique | Less readable in reports |
| **Email** | Readable | Can change; privacy in logs |
| **Username** | Readable | Often changeable |
**Do not use:** session IDs, device IDs, or temporary IDs — they break cross-session or cross-device continuity.
Use an ID that **never changes**, is **unique**, and **exists in your system**.
***
## User Profiles
After identification, Zixflow maintains a profile with:
* Core identity: `user_id`, email, name, phone, original `anonymous_id`
* Custom attributes from `identify()` traits and `setProfileAttributes()`
* System metadata: first seen, last updated, last seen
* Linked events and devices
For attribute modeling, see [Attributes](/documentation/events/attributes).
***
## Multiple Devices Per User
```mermaid theme={null}
flowchart TB
profile[User profile userId]
profile --> phone[iPhone]
profile --> tablet[iPad]
profile --> web[Chrome browser]
```
Each login on a new device calls `identify()` with the same `userId` and a different device token. You can push to all devices or target a platform, and see cross-device journeys in one timeline.
***
## Common Scenarios
### First-time visitor
SDK creates `anonymousId` → events tracked anonymously.
### Returning visitor (not logged in)
Same cookie/storage → same `anonymousId` → history continues.
### Signup / login
`identify(userId, anonymousId)` → merge → future events are identified.
### Logout
Call `clearIdentify()` so a new anonymous session starts cleanly:
```javascript theme={null}
Zixflow.clearIdentify();
```
### Switch devices
Identify on each device with the same `userId` → histories merge into one profile.
***
## User Lifecycle States
Server-controlled states (not from the client SDK):
| Event | Effect |
| ------------------- | ------------------------------------------------ |
| `User Deleted` | Soft-deletes profile; deactivates linked devices |
| `User Suppressed` | Blocks **all** outbound channels |
| `User Unsuppressed` | Resumes normal delivery |
Suppression is workspace-controlled and blocks everything. Channel opt-outs are per-channel and user-controlled — see [User Preferences](/documentation/events/user-preferences).
Use the [Event API Reference](/documentation/events/event-api-reference) for server-side lifecycle calls.
***
## Device Registration Before Signup
```mermaid theme={null}
sequenceDiagram
participant App
participant Zixflow
App->>Zixflow: Register device token with anonymousId
Note over Zixflow: Push works immediately
App->>Zixflow: identify userId + anonymousId
Note over Zixflow: Device migrates to user profile
```
1. Device registers under `anonymousId` — push can send immediately
2. User signs up — app calls `identify()`
3. Devices migrate to the identified profile — no re-registration needed
Details: [Devices & Push Notifications](/documentation/events/devices-and-push-notifications).
***
**Next:** Learn how to register devices for push → [Devices & Push Notifications](/documentation/events/devices-and-push-notifications)
# Advanced Configuration
Source: https://docs.zixflow.com/documentation/sdk/android/advanced-configuration
Advanced Configuration — Zixflow Android SDK integration guide.
### Configuration Options
```kotlin theme={null}
import com.zixflow.sdk.ZixflowConfigBuilder
import com.zixflow.sdk.core.util.ZixflowLogLevel
import com.zixflow.sdk.data.model.Region
import com.zixflow.datapipelines.config.ScreenView
val config = ZixflowConfigBuilder(
applicationContext = this,
apiKey = "YOUR_API_KEY"
)
// Logging
.logLevel(ZixflowLogLevel.DEBUG) // NONE, ERROR, INFO, DEBUG
// Workspace region (default: Region.US)
.region(Region.US)
// Auto-tracking
.autoTrackDeviceAttributes(true) // default: true
.autoTrackActivityScreens(false) // default: false
.trackApplicationLifecycleEvents(true) // default: true
// How screen events are used (default: ScreenView.All)
.screenViewUse(ScreenView.All)
// Optional host overrides (override region-based defaults)
// .apiHost("api-events.zixflow.com/v1")
// .cdnHost("cdn.example.com")
// Event batching
.flushAt(20) // Send after 20 events (default: 20)
.flushInterval(30) // Or every 30 seconds (default: 30)
.build()
```
### Defaults
| Option | Default |
| --------------------------------- | ----------------------- |
| `logLevel` | `ZixflowLogLevel.ERROR` |
| `region` | `Region.US` |
| `autoTrackDeviceAttributes` | `true` |
| `autoTrackActivityScreens` | `false` |
| `trackApplicationLifecycleEvents` | `true` |
| `screenViewUse` | `ScreenView.All` |
| `flushAt` | `20` |
| `flushInterval` | `30` (seconds) |
### Region
Use `.region(Region.US)` or `.region(Region.EU)` so CDP requests route to the correct regional endpoint. If you set `apiHost` or `cdnHost` manually, those values override region-based defaults.
### Screen view use
| Value | Behavior |
| ---------------- | ------------------------------------- |
| `ScreenView.All` | Screen events are sent to the backend |
### Log Levels
Control SDK logging verbosity:
```kotlin theme={null}
import com.zixflow.sdk.core.util.ZixflowLogLevel
.logLevel(ZixflowLogLevel.NONE) // No logs
.logLevel(ZixflowLogLevel.ERROR) // Errors only (default)
.logLevel(ZixflowLogLevel.INFO) // Errors + informational
.logLevel(ZixflowLogLevel.DEBUG) // All messages (development)
```
### Auto-collected device attributes
When `autoTrackDeviceAttributes(true)` is enabled (the default), the SDK collects:
* `device_os`
* `device_model`
* `device_manufacturer`
* `app_version`
* `zixflow_sdk_version`
* `device_locale`
* `push_enabled`
You can still set custom device attributes with `Zixflow.instance().setDeviceAttributes(...)`.
***
# API Reference
Source: https://docs.zixflow.com/documentation/sdk/android/api-reference
SDK method reference for the Zixflow Android integration.
### Zixflow Core
```kotlin theme={null}
// Initialize SDK
Zixflow.initialize(config: ZixflowConfig)
// Get instance
Zixflow.instance(): Zixflow
// Identify user
Zixflow.instance().identify(userId: String, traits: Map = emptyMap())
// Track event
Zixflow.instance().track(name: String, properties: Map = emptyMap())
// Track screen
Zixflow.instance().screen(title: String, properties: Map = emptyMap())
// Set profile attributes
Zixflow.instance().setProfileAttributes(attributes: Map)
// Set device attributes
Zixflow.instance().setDeviceAttributes(attributes: Map)
// Clear user identification (also deletes device token and resets anonymousId)
Zixflow.instance().clearIdentify()
// Device token
Zixflow.instance().registerDeviceToken(deviceToken: String)
Zixflow.instance().deleteDeviceToken()
Zixflow.instance().registeredDeviceToken: String?
// Identity state
Zixflow.instance().anonymousId: String
Zixflow.instance().userId: String?
Zixflow.instance().isUserIdentified: Boolean
// Push metrics
Zixflow.instance().trackMetric(event: TrackMetric)
```
### TrackMetric
```kotlin theme={null}
import com.zixflow.sdk.events.Metric
import com.zixflow.sdk.events.TrackMetric
TrackMetric.Push(
metric: Metric, // Delivered, Opened, Converted, Clicked
deliveryId: String,
deviceToken: String
)
```
### ZixflowConfigBuilder
```kotlin theme={null}
ZixflowConfigBuilder(applicationContext: Application, apiKey: String)
.logLevel(ZixflowLogLevel) // NONE, ERROR, INFO, DEBUG
.region(Region) // US, EU
.apiHost(String)
.cdnHost(String)
.autoTrackDeviceAttributes(Boolean)
.autoTrackActivityScreens(Boolean)
.trackApplicationLifecycleEvents(Boolean)
.screenViewUse(ScreenView) // All
.flushAt(Int)
.flushInterval(Int)
.addZixflowModule(ZixflowModule)
.build(): ZixflowConfig
```
### Modules
```kotlin theme={null}
// Push notifications (FCM)
ModuleMessagingPushFCM(moduleConfig: MessagingPushModuleConfig = default)
MessagingPushModuleConfig.Builder()
.setAutoTrackPushEvents(Boolean)
.setPushClickBehavior(PushClickBehavior)
.setNotificationCallback(ZixflowPushNotificationCallback)
.build()
// Location tracking
ModuleLocation(moduleConfig: LocationModuleConfig = default)
ModuleLocation.instance().locationServices
LocationModuleConfig.Builder()
.setLocationTrackingMode(LocationTrackingMode) // OFF, MANUAL, ON_APP_START
.build()
LocationServices.setLastKnownLocation(latitude: Double, longitude: Double)
LocationServices.setLastKnownLocation(location: Location)
LocationServices.requestLocationUpdate()
```
***
# Core Features
Source: https://docs.zixflow.com/documentation/sdk/android/core-features
User identification, event tracking, and core SDK features for Android.
### User Identification
Identify users to track their activity:
```kotlin theme={null}
// Identify user with ID only
Zixflow.instance().identify(userId = "user@example.com")
// Identify user with additional attributes
Zixflow.instance().identify(
userId = "user@example.com",
traits = mapOf(
"first_name" to "John",
"last_name" to "Doe",
"email" to "user@example.com",
"plan" to "premium"
)
)
```
**Java:**
```java theme={null}
// Identify user with ID only
Zixflow.instance().identify("user@example.com");
// Identify user with attributes
Map traits = new HashMap<>();
traits.put("first_name", "John");
traits.put("last_name", "Doe");
traits.put("email", "user@example.com");
Zixflow.instance().identify("user@example.com", traits);
```
Events tracked before `identify()` are attributed to an anonymous profile. After you identify a user, that anonymous activity is associated with the identified person.
```kotlin theme={null}
val anonymousId = Zixflow.instance().anonymousId
val isIdentified = Zixflow.instance().isUserIdentified
```
### Event Tracking
Track custom events:
```kotlin theme={null}
// Simple event
Zixflow.instance().track(name = "Button Clicked")
// Event with properties
Zixflow.instance().track(
name = "Product Purchased",
properties = mapOf(
"product_id" to "123",
"product_name" to "Widget",
"price" to 29.99,
"currency" to "USD"
)
)
```
**Java:**
```java theme={null}
// Simple event
Zixflow.instance().track("Button Clicked");
// Event with properties
Map properties = new HashMap<>();
properties.put("product_id", "123");
properties.put("product_name", "Widget");
properties.put("price", 29.99);
Zixflow.instance().track("Product Purchased", properties);
```
### Screen Tracking
Track screen views:
```kotlin theme={null}
// Manual screen tracking
Zixflow.instance().screen(
title = "Product Detail",
properties = mapOf(
"product_id" to "123",
"category" to "Electronics"
)
)
// Enable automatic activity screen tracking (in configuration)
val config = ZixflowConfigBuilder(this, "YOUR_API_KEY")
.autoTrackActivityScreens(true)
.build()
```
**Java:**
```java theme={null}
// Manual screen tracking
Map properties = new HashMap<>();
properties.put("product_id", "123");
properties.put("category", "Electronics");
Zixflow.instance().screen("Product Detail", properties);
```
### Profile Attributes
Set user profile attributes:
```kotlin theme={null}
// Set profile attributes
Zixflow.instance().setProfileAttributes(
mapOf(
"age" to 30,
"gender" to "male",
"subscription_status" to "active"
)
)
```
**Java:**
```java theme={null}
Map attributes = new HashMap<>();
attributes.put("age", 30);
attributes.put("gender", "male");
Zixflow.instance().setProfileAttributes(attributes);
```
If no user is identified yet, profile attributes update the anonymous profile.
### Device Attributes
Set device attributes:
```kotlin theme={null}
// Set device-specific attributes
Zixflow.instance().setDeviceAttributes(
mapOf(
"app_version" to "2.1.0",
"custom_device_flag" to true
)
)
```
With `autoTrackDeviceAttributes(true)` (the default), the SDK also collects attributes such as `device_os`, `device_model`, `device_manufacturer`, `app_version`, `zixflow_sdk_version`, `device_locale`, and `push_enabled`.
### Device Token
The push module registers FCM tokens automatically. You can also manage tokens manually:
```kotlin theme={null}
// Register a token manually (rarely needed with ModuleMessagingPushFCM)
Zixflow.instance().registerDeviceToken("fcm-device-token")
// Read the currently registered token
val deviceToken = Zixflow.instance().registeredDeviceToken
if (deviceToken != null) {
println("Device token: $deviceToken")
} else {
println("Device token not yet registered")
}
// Delete the registered device token
Zixflow.instance().deleteDeviceToken()
```
### User Logout
Clear user identification when they log out:
```kotlin theme={null}
// Clear identified user
Zixflow.instance().clearIdentify()
```
**Java:**
```java theme={null}
Zixflow.instance().clearIdentify();
```
`clearIdentify()` resets analytics state, generates a new `anonymousId`, and deletes the registered device token.
***
# Example Apps
Source: https://docs.zixflow.com/documentation/sdk/android/example-apps
Example Apps — Zixflow Android SDK integration guide.
### Clone and run
Public examples live in [zixflow/sdk-examples](https://github.com/zixflow/sdk-examples):
```bash theme={null}
git clone https://github.com/zixflow/sdk-examples.git
# Open sdk-examples/android in Android Studio
# Set YOUR_API_KEY in app/.../Config.kt
```
See the [Android example README](https://github.com/zixflow/sdk-examples/tree/master/android) for FCM and location module setup.
### What a complete integration covers
| Area | What to implement | Docs |
| -------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Install & initialize | Add the Android SDK dependency and initialize | [Installation](/documentation/sdk/android/installation), [Quick Start](/documentation/sdk/android/quick-start) |
| Identify & track | Identify users, track events and screens | [Core Features](/documentation/sdk/android/core-features) |
| Push notifications | FCM setup, delivery, and action buttons | [Push Notifications](/documentation/sdk/android/push-notifications) |
| Location | Optional location module | [Location Tracking](/documentation/sdk/android/location-tracking) |
| API surface | Full method reference | [API Reference](/documentation/sdk/android/api-reference) |
### Suggested build order
1. Install the SDK and complete Android project setup.
2. Initialize the SDK and identify a test user.
3. Add event and screen tracking.
4. Enable push, attach action buttons via `onNotificationComposed`, and verify delivery on a physical device.
5. Optionally enable location.
# Installation
Source: https://docs.zixflow.com/documentation/sdk/android/installation
Install and configure the Zixflow Android SDK in your project.
The Zixflow Android SDK is available on **Maven Central**. You can browse all available packages and versions here:
**[Maven Central - Zixflow Android SDK](https://central.sonatype.com/search?q=com.zixflow.com.android\&namespace=com.zixflow.com.android)**
### Gradle Dependencies
Add the Zixflow SDK to your app's `build.gradle` or `build.gradle.kts` file:
#### Kotlin DSL (build.gradle.kts)
```kotlin theme={null}
dependencies {
// Core module (required)
implementation("com.zixflow.com.android:datapipelines:1.1.3")
// Optional modules
implementation("com.zixflow.com.android:messaging-push-fcm:1.1.3") // Push notifications (FCM)
implementation("com.zixflow.com.android:location:1.1.3") // Location tracking
}
```
#### Groovy DSL (build.gradle)
```groovy theme={null}
dependencies {
// Core module (required)
implementation 'com.zixflow.com.android:datapipelines:1.1.3'
// Optional modules
implementation 'com.zixflow.com.android:messaging-push-fcm:1.1.3' // Push notifications
implementation 'com.zixflow.com.android:location:1.1.3' // Location tracking
}
```
### Add Maven Central Repository
Ensure Maven Central is in your repositories (usually in `settings.gradle.kts` or root `build.gradle`):
```kotlin theme={null}
repositories {
google()
mavenCentral()
}
```
### Project SDK levels
Use a toolchain that matches current AndroidX requirements:
| Setting | Recommended |
| -------------------------- | ------------------ |
| `compileSdk` / `targetSdk` | `36` |
| Android Gradle Plugin | `8.9.1`+ |
| Gradle | `8.11.1`+ |
| `minSdk` | `21` (SDK minimum) |
If you stay on AGP `8.7` / `compileSdk` `35`, transitive dependencies may pull `androidx.core` `1.17+` and fail AAR metadata checks. Prefer upgrading AGP and `compileSdk` rather than forcing older AndroidX versions.
### Sync Gradle
After adding dependencies, sync your project:
* Click **Sync Now** in the notification bar, or
* **File** → **Sync Project with Gradle Files**
***
# Introduction
Source: https://docs.zixflow.com/documentation/sdk/android/introduction
Overview of the Zixflow Android SDK and its modules.
The Zixflow Android SDK enables you to send data from your Android app to Zixflow, allowing you to track user behavior, send targeted push notifications, and more.
### SDK Modules
The SDK is organized into independent modules (Maven group `com.zixflow.com.android`):
* **datapipelines** (required): Core tracking, user identification, and event tracking
* **messaging-push-fcm**: Push notification support via Firebase Cloud Messaging (FCM)
* **location**: Location tracking capabilities
Current SDK version documented here: **1.1.3**.
For conceptual background on events, users, and attributes, see [Getting Started with Events](/documentation/events/getting-started).
***
# Location Tracking
Source: https://docs.zixflow.com/documentation/sdk/android/location-tracking
Capture user location for geo-aware messaging with the Zixflow Android SDK.
### How it works
The Location module captures location (with user consent) from your app and attaches it to a person’s profile in Zixflow. You can use this data for geo-aware messaging and audience segmentation with more accuracy than IP-based geolocation.
When you identify a person, the SDK includes the latest location in the identify call. The SDK also sends a location update to the person’s activity timeline (`ZIXFLOW Location Update`), which you can use in campaigns and segments. To balance location updates with battery and data usage, the SDK limits location updates (at most once a day)—and only sends that update when the person has moved a meaningful distance since the last update.
The SDK does **not** request location permission on its own—your app must handle the permission flow.
***
### Add the dependency
```kotlin theme={null}
dependencies {
implementation("com.zixflow.com.android:datapipelines:1.1.3")
implementation("com.zixflow.com.android:location:1.1.3")
}
```
***
### Add permissions
Add location permissions to `AndroidManifest.xml`:
```xml theme={null}
```
### Request location permission
```kotlin theme={null}
import android.Manifest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val requestLocationPermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true -> {
// Precise location granted
}
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true -> {
// Approximate location granted
}
else -> {
// Location permission denied
}
}
}
private fun requestLocationPermission() {
requestLocationPermission.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
}
```
***
### Initialize with location config
Add `ModuleLocation` to your `ZixflowConfigBuilder`. The `trackingMode` property controls how and when the SDK captures location.
| Option | Type | Default | Description |
| -------------- | ---------------------- | -------- | ----------------------------------------------- |
| `trackingMode` | `LocationTrackingMode` | `MANUAL` | Controls how and when the SDK captures location |
#### Tracking modes
| Mode | Description |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LocationTrackingMode.MANUAL` | Your app controls when location is captured. Call `setLastKnownLocation()` or `requestLocationUpdate()`. Use this when your app already tracks location or you want full control. |
| `LocationTrackingMode.ON_APP_START` | The SDK captures a one-shot location once per app launch when the app enters the foreground. You can still call the location APIs alongside automatic capture. |
| `LocationTrackingMode.OFF` | Disables location tracking. Location calls become silent no-ops and location is not included in identify calls. |
```kotlin theme={null}
import com.zixflow.sdk.Zixflow
import com.zixflow.sdk.ZixflowConfigBuilder
import com.zixflow.location.LocationModuleConfig
import com.zixflow.location.LocationTrackingMode
import com.zixflow.location.ModuleLocation
val locationConfig = LocationModuleConfig.Builder()
.setLocationTrackingMode(LocationTrackingMode.MANUAL)
.build()
Zixflow.initialize(
ZixflowConfigBuilder(this, "YOUR_API_KEY")
.addZixflowModule(ModuleLocation(moduleConfig = locationConfig))
.build()
)
```
Using `ModuleLocation()` without a config uses `MANUAL` mode by default.
***
### Location APIs
Access location services through the location module:
```kotlin theme={null}
val locationServices = ModuleLocation.instance().locationServices
```
You can call these methods as often as you like. The SDK caches the latest coordinates for profile enrichment, but throttles outbound location updates so you do not overwhelm your workspace.
#### `setLastKnownLocation`
Pass coordinates from your app’s own location system. This does not require the SDK to manage location permissions—your app manages access independently.
```kotlin theme={null}
import android.location.Location
import com.zixflow.location.ModuleLocation
// Pass a Location object
val location: Location = // from LocationManager or FusedLocationProviderClient
ModuleLocation.instance().locationServices.setLastKnownLocation(location)
// Or pass latitude / longitude directly
ModuleLocation.instance().locationServices.setLastKnownLocation(
latitude = 37.7749,
longitude = -122.4194
)
```
| Parameter | Type | Description |
| ----------- | ---------- | --------------------------------------------------- |
| `latitude` | `Double` | Latitude in degrees. Must be between -90 and 90. |
| `longitude` | `Double` | Longitude in degrees. Must be between -180 and 180. |
| `location` | `Location` | Android `Location` object from your provider |
#### `requestLocationUpdate`
Request a one-shot location from Play Services location APIs. Use this if your app does not have its own location system. Your app must request location permission **before** calling this method—the SDK will not prompt the user.
If permission is denied or location services are disabled, the request is ignored (no crash).
```kotlin theme={null}
// After location permission is granted
ModuleLocation.instance().locationServices.requestLocationUpdate()
```
***
### Profile switch behavior
When you call `Zixflow.instance().clearIdentify()`, the SDK clears cached location data so one person’s location does not carry over to another profile. The next person you identify starts with a clean slate.
***
# Push Notification Tracking
Source: https://docs.zixflow.com/documentation/sdk/android/push-notification-tracking
Definitive guide to tracking push notification lifecycle events — delivery, open, and action clicks — with the Zixflow Android SDK.
**Last Updated:** July 2, 2026\
**Audience:** Client developers integrating Zixflow SDK (Android)\
**Purpose:** Track push notification lifecycle events — delivery, open, and action clicks — so Zixflow can measure campaign performance accurately.
See also: [Push Notifications](/documentation/sdk/android/push-notifications) and the reference implementation in [sdk-examples/android](https://github.com/zixflow/sdk-examples/tree/master/android).
***
## Why Push Tracking Matters
When Zixflow sends a push notification to a user's device, it records that the notification was **sent**. But it cannot know on its own:
* Did the notification actually **arrive** on the device?
* Did the user **open** it (tap the banner)?
* Did the user tap an **action button** ("Shop Now", "Remind Me")?
Your app reports these three moments back to Zixflow using the SDK. This powers the delivery analytics you see in campaign dashboards — open rates, click rates, and conversion funnels after a push.
***
## Two Ways to Handle a Zixflow Push — and Why Tracking Is Required in Both
Every push Zixflow sends can be displayed to the user in one of two ways. **You choose which one per campaign in the Zixflow dashboard — "Native" or "Custom" — and that choice decides the payload shape** (see [FCM Wire Format](#fcm-android-wire-format)). Native mode sends the display content in the platform `notification` block so the OS renders it; Custom mode sends everything in `data` so your app renders it.
| | **A. Native (OS/FCM-rendered)** | **B. Custom (app-built UI)** |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Dashboard selection | **Native** | **Custom** |
| Payload shape | `notification.*` / `android.notification.*` carry the content; `data` has tracking + routing only | No `notification` block — the full content is in `data.*` |
| Who builds the visible notification | Android itself | Your app code |
| When your code runs | Only on **tap** (backgrounded/killed), or in `onMessageReceived` (foreground) | The moment the push arrives, in **every** app state (high-priority data message) |
| Image / large icon / custom sound / color / sticky | Image and color tint render with zero app code. **Large icon and sticky have no working native equivalent** | All of them, fully controllable |
| Action buttons | **Never possible** on the Native path — no payload field can carry a `PendingIntent` | Up to 2 buttons, dynamic label + deeplink |
| **Delivery/Open tracking** | **Still required** — the OS rendering the banner does not tell Zixflow anything | Same requirement |
**The one thing both paths share: tracking is never automatic.** Whether the OS drew the banner or your code did, Zixflow only learns about delivery/opens/clicks when your app calls `trackMetric()` / `track()` — that call must happen regardless of which rendering path was taken.
* **Path A (Native, app backgrounded/killed):** `onMessageReceived` is **not invoked at all** when the payload has a `notification` block and the app is backgrounded — so `Delivered` cannot be tracked from app code at that moment. It's the tap afterward (via `intent.extras`) that reaches your code and lets you track `Opened`. For `Delivered` on this path, rely on Zixflow's server-side delivery receipt, or see [Tracking Delivery When the Device Is Locked](#tracking-delivery-when-the-device-is-locked).
* **Path B (Custom):** your code runs the moment the push arrives via a **high-priority data message**. Track `Delivered` immediately, then track `Opened` / `Push Notification Action Clicked` from your own tap handlers.
**Minimal "OS renders it, I only track" implementation** — the smallest possible correct integration, with zero custom notification-building code:
```kotlin theme={null}
// onMessageReceived only fires for data-only pushes while backgrounded, and always
// while foregrounded. A notification-block push while backgrounded never reaches here —
// the tap does, via intent extras in your launcher Activity (see Track Open section).
override fun onMessageReceived(message: RemoteMessage) {
val deliveryId = message.data["Zixflow-Delivery-ID"] ?: ""
val deliveryToken = message.data["Zixflow-Delivery-Token"] ?: cachedToken ?: ""
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance.trackMetric(deliveryID = deliveryId, deviceToken = deliveryToken, event = MetricEvent.DELIVERED)
}
// No showNotification() call — OS renders it while backgrounded; while foregrounded,
// true "OS/FCM-only" means no banner at all (see comparison table above).
}
```
> **Reference implementation:** `sdk-examples/android` ships a runtime **"Custom handling"** toggle that switches live between path A and path B on the same running app.
> **A note on FCM client libraries and Path A:** once *any* FCM client library is present, it typically installs its own `FirebaseMessagingService`. Some libraries' built-in fallback renderer for notification-block pushes is *not* a full reproduction of stock Play-Services rendering. Verify your library's fallback before assuming Path A gives full fidelity for free.
***
## The Delivery Lifecycle at a Glance
```
Zixflow Campaign ──sends──▶ Zixflow (via FCM/APNs) ──delivers──▶ Device OS
│
┌──────────┴──────────┐
│ │
App foreground App background
onMessage() onMessageOpenedApp()
│ │
Your app calls: Your app calls:
trackMetric(delivered) trackMetric(opened)
│
User taps action button
│
Your app calls:
track("Push Notification
Action Clicked")
```
Each of these SDK calls results in a delivery report event flowing to the Zixflow backend, updating the campaign's live metrics.
***
## The Push Payload
Zixflow injects two special fields into every push notification's **data payload**. Your app uses these to associate the tracking event with the correct campaign delivery.
| Field | Example value | Purpose |
| ------------------------ | ------------------------------ | ----------------------------------------------------- |
| `Zixflow-Delivery-ID` | `"626533406292836846"` | Unique ID for this delivery (matches campaign record) |
| `Zixflow-Delivery-Token` | `"dcFRlDhiRbehM1vg-Lx_yn:..."` | The FCM token the notification was sent to |
These appear inside `message.data`. The SDK's `trackMetric()` method requires both.
The rest of the payload depends on the **rendering mode** chosen for the campaign in the Zixflow dashboard (see [FCM Wire Format](#fcm-android-wire-format)):
* **Native (OS-rendered):** display content (title/body/image/sound/…) is sent in `notification` / `android.notification`, and `data` carries **only** the tracking + routing keys.
* **Custom (app-rendered):** there is no `notification` block — the display content is delivered inside `data` so your app builds the notification itself.
**Example `data` payload in Custom mode (app receives the full content):**
```json theme={null}
{
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"title": "Flash Sale! 70% OFF 🔥",
"body": "Limited time only — grab your deal now!",
"deeplink_url": "https://yourapp.com/sale",
"image_url": "https://cdn.yourapp.com/banner.png",
"large_icon_url": "https://cdn.yourapp.com/icon.png",
"badge": "5",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846"
}
```
**Example `data` payload in Native mode (tracking + routing only):**
```json theme={null}
{
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"deeplink_url": "https://yourapp.com/sale",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846"
}
```
> **Important:** `action_buttons` is a JSON string (not a nested object). Parse it with `JSONArray` / `JSONObject` before use.
***
## How to handle and show push notifications sent by Zixflow
### The Delivery Pipeline
```
Zixflow Campaign / Journey
│
▼
Firebase Cloud Messaging
│
▼
Device OS ──▶ Your App
```
Zixflow sends the notification directly to FCM on your behalf — your app never talks to Zixflow's backend to receive a push. Every key your app sees (`Zixflow-Delivery-ID`, `deeplink_url`, `action_buttons`, and so on) is part of that same payload. What differs is **where** Zixflow places those keys, and that placement decides whether the OS displays the notification or your app code has to.
### FCM (Android) Wire Format
**The shape of the payload depends on the rendering mode you select for the campaign in the Zixflow dashboard.** Both modes always carry the same Zixflow tracking + routing keys in `data` (`Zixflow-Delivery-ID`, `Zixflow-Delivery-Token`, `deeplink_url`, `action_buttons`, `notif_id`, `workspace_id`, `template_id`). What changes is **where the display content — title, body, image, sound, icon, etc. — lives**. The two modes deliberately do **not** duplicate that content.
#### Mode 1 — Native (OS-rendered)
Pick this when you want Android to build and show the notification for you with **zero app code**. Zixflow puts all the display content in the `notification` + `android.notification` blocks; `data` carries **only** the tracking + routing keys — no redundant `title`/`body`/`image_url`/`sound`.
```json theme={null}
{
"notification": {
"title": "Flash Sale! 70% OFF 🔥",
"body": "Limited time only — grab your deal now!",
"image": "https://cdn.yourapp.com/banner.png"
},
"android": {
"notification": {
"sound": "notification_tone",
"notification_count": 5,
"channel_id": "zixflow_push",
"icon": "ic_notification",
"color": "#FF5722"
}
},
"data": {
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"deeplink_url": "https://yourapp.com/sale",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846",
"template_id": "469935",
"workspace_id": "68be4f797a6494676161e98b"
}
}
```
#### Mode 2 — Custom (app-rendered)
Pick this when **your app builds the notification itself** from the `data` payload. Zixflow sends **no** `notification` / `android.notification` blocks at all — every field, including the display content, lives in `data`, so your app receives the complete payload in every app state (foreground, background, or killed).
```json theme={null}
{
"data": {
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"title": "Flash Sale! 70% OFF 🔥",
"body": "Limited time only — grab your deal now!",
"message": "Limited time only — grab your deal now!",
"image_url": "https://cdn.yourapp.com/banner.png",
"large_icon_url": "https://cdn.yourapp.com/icon.png",
"deeplink_url": "https://yourapp.com/sale",
"sound": "notification_tone",
"badge": "5",
"sticky": "true",
"icon": "ic_notification",
"color": "#FF5722",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846",
"template_id": "469935",
"workspace_id": "68be4f797a6494676161e98b"
}
}
```
> **This payload is sent as a high-priority FCM data message** (`android.priority: "high"`). High priority is what lets FCM wake `onMessageReceived` even when the device is locked or the app is backgrounded — which is exactly what you need to track `Delivered` reliably on this path (see [Tracking Delivery When the Device Is Locked](#tracking-delivery-when-the-device-is-locked)).
| Block | Who consumes it | When |
| ------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `notification.*` (Native mode only) | Android OS directly | App **backgrounded or killed** — the OS builds and shows the system notification; no app code runs until the user taps it. |
| `android.notification.*` (Native mode only) | Android OS directly | Android-specific system-notification settings (sound, badge count, channel, icon tint) merged on top of `notification.*`. |
| `data.*` | Your app's FCM listener | **Always** — this is what `onMessageReceived()` receives. In Custom mode this is the *complete* payload. In Native mode it carries only the tracking + routing keys. |
**Practical consequence:** in **Custom mode**, read everything from `data.*`. In **Native mode**, the OS renders the banner from `notification.*` while backgrounded/killed and your code isn't invoked; if the app is in the **foreground** when a Native-mode push arrives, read the display content from `RemoteMessage.notification` (`message.notification?.title` and so on) since it won't be in `data`. Prefer `message.notification?.x ?: data["x"]`.
> **Sound:** `data.sound` / `android.notification.sound` is a bare resource name with **no extension** (`notification_tone` → `res/raw/notification_tone.*`).
### Complete Key Reference
Every key that can appear in the payload, where it lives, and who reads it:
| Key | FCM location | Read by | Purpose |
| ------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Zixflow-Delivery-ID` | `data.Zixflow-Delivery-ID` (both modes) | **Your app** | Unique delivery record ID — required for `trackMetric()` on every lifecycle call. |
| `Zixflow-Delivery-Token` | `data.Zixflow-Delivery-Token` (both modes) | **Your app** | The FCM token this delivery was sent to — required for `trackMetric()`. |
| `title` | Native: `notification.title` · Custom: `data.title` | OS (Native) / **your app** (Custom + foreground) | Notification headline. |
| `body` / `message` | Native: `notification.body` · Custom: `data.body` + `data.message` (alias) | OS (Native) / **your app** (Custom + foreground) | Notification text. `message` is a plain alias of `body`. |
| `image_url` | Native: `notification.image` · Custom: `data.image_url` | OS (Native, `BigPictureStyle`) / **your app** (Custom) | Rich image. |
| `large_icon_url` | `data.large_icon_url` (Custom mode) | **Your app only** | Small avatar/icon next to the notification — no native FCM field. |
| `deeplink_url` | `data.deeplink_url` (both modes) | **Your app** | Where to navigate when the notification **body** is tapped. |
| `action_buttons` | `data.action_buttons` (JSON string, both modes) | **Your app** | Array of `{name, deeplink}` for up to 2 action buttons — always a JSON-encoded string. |
| `sound` | Native: `android.notification.sound` · Custom: `data.sound` | OS (Native) / **your app** (Custom) | Custom notification sound. No extension on Android. |
| `badge` | Native: `android.notification.notification_count` · Custom: `data.badge` (string) | OS (Native) / **your app** (Custom) | App icon badge count. |
| `sticky` | `data.sticky` (Custom mode; `"true"` / `"false"`) | **Your app** (Custom mode only) | Survives swipe/"Clear all" **only via the custom-handled path**. See [Sticky Notifications](#sticky-notifications). |
| `icon` | Native: `android.notification.icon` · Custom: `data.icon` | OS (Native) / **your app** (Custom) | Renders blank unless the name matches a bundled drawable, or you declare `com.google.firebase.messaging.default_notification_icon`. |
| `color` | Native: `android.notification.color` · Custom: `data.color` (hex) | OS (Native) / **your app** (Custom) | Accent tint. Works on the Native path with zero extra app code. |
| `click_action` | `data.click_action` | **Your app** (custom-handled path) | On the Native path, a value with **no matching ``** makes the tap do nothing. On the Custom path, sample apps map tokens such as `"OPEN_SALE"` ahead of `deeplink_url`. |
| `notif_id` | `data.notif_id` | Diagnostic only | Zixflow's internal send-job ID — **not** the same as `Zixflow-Delivery-ID`. Not needed for `trackMetric()`. |
| `template_id` | `data.template_id` (both modes) | **Your app** | Dashboard template ID. Use it to branch to a template-specific renderer — see [Template-Based Custom Rendering](#template-based-custom-rendering-template_id). |
| `workspace_id` | `data.workspace_id` | Diagnostic only | Which Zixflow workspace sent this. Not required by any SDK call. |
Demo-only conventions used by the sample apps (not part of the official Zixflow schema): `data.priority`, `data.analytics_label`, `data.ttl_seconds`. The real FCM fields `android.priority`, `fcm_options.analytics_label`, and `android.ttl` are delivery headers and are not readable as display content.
### Field Support Summary: Native (OS-Rendered) vs. Custom-Handled
| Field | Native (OS-rendered) support |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `image_url` / `notification.image` | Fully supported — big-picture image with zero app code. |
| `color` | Fully supported — accent tint with zero app code. |
| `icon` | Supported only if a matching drawable is bundled (or a manifest fallback is set) — otherwise a blank icon. |
| `sound` | Supported when the sound resource is bundled in the app. |
| `badge` / `notification_count` | Inconsistent — depends on the device launcher. |
| `sticky` | **Not supported** — the notification always dismisses normally. Only available via Custom (`data.sticky`). |
| `large_icon_url` | **Not supported** — always requires Custom-handled code. |
| `action_buttons` | **Not supported** — buttons always require Custom-handled code. |
| `click_action` | Supported only with an exact matching `` — without one, the tap does nothing. |
| `template_id` | Not a rendering field. The OS never reads it; your app uses it to pick a custom renderer. |
***
## Template-Based Custom Rendering (`template_id`)
When a customer creates a notification template in the Zixflow dashboard (**Push Notifications → Templates**), the template is saved with a unique **Template ID**. Every push sent from a saved template automatically includes that ID as **`data.template_id`**, in both Native and Custom payload modes. You do not add this field to the template's custom fields yourself.
**Why this matters:** without `template_id`, a custom-handled renderer can only branch on the *content* of a push (is there an `image_url`? a `sound`?). With `template_id`, your app can recognize **which template** a push came from and give it a fully bespoke layout — a dedicated notification channel, a different icon/style, extra business logic — built from every field that template is known to define.
**The pattern:** keep a small registry mapping known `template_id` values to a dedicated render function, and fall back to the generic field-driven renderer ([Complete Field Mapping Reference](#complete-field-mapping-reference-custom-handled-ui)) for any template not in the registry (including when `template_id` is absent).
```kotlin theme={null}
// CustomFirebaseMessagingService.kt
private const val EXAMPLE_TEMPLATE_ID = "469935" // swap in your own template's ID
private fun showNotification(title: String, body: String, data: Map, deliveryId: String, deliveryToken: String) {
when (data["template_id"]) {
EXAMPLE_TEMPLATE_ID -> {
showTemplateExampleNotification(title, body, data, deliveryId, deliveryToken)
return
}
// else falls through to the generic renderer below
}
// ...generic field-driven notification building continues here
}
private fun showTemplateExampleNotification(title: String, body: String, data: Map, deliveryId: String, deliveryToken: String) {
val imageBitmap = downloadBitmap(data["image_url"])
val largeIconBitmap = downloadBitmap(data["large_icon_url"])
val sticky = data["sticky"]?.toBooleanStrictOrNull() ?: false
val notificationId = System.currentTimeMillis().toInt()
val builder = NotificationCompat.Builder(this, "zixflow_template_example")
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setOngoing(sticky)
if (largeIconBitmap != null) builder.setLargeIcon(largeIconBitmap)
builder.setStyle(
if (imageBitmap != null) NotificationCompat.BigPictureStyle().bigPicture(imageBitmap)
else NotificationCompat.BigTextStyle().bigText(body)
)
data["badge"]?.toIntOrNull()?.let { builder.setNumber(it) }
if (!data["sound"].isNullOrEmpty()) builder.setSound(android.net.Uri.parse("android.resource://$packageName/raw/${data["sound"]}"))
builder.setContentIntent(buildContentPendingIntent(deliveryId, deliveryToken, data["deeplink_url"], data["click_action"]))
PushActionButtons.attachFromRawData(deliveryId, deliveryToken, data["action_buttons"], notificationId, builder, this)
NotificationManagerCompat.from(this).notify(notificationId, builder.build())
}
```
***
## Complete Field Mapping Reference (Custom-Handled UI)
One consolidated builder covering **every** official field from the [Complete Key Reference](#complete-key-reference) — title, body, image, large icon, sound, badge, sticky, and action buttons. This is the Path-B ("custom-handled") renderer. Combine with the tracking calls from [The Three Tracking Events](#the-three-tracking-events). Matches `sdk-examples/android`.
```kotlin theme={null}
private fun showZixflowNotification(data: Map) {
val title = data["title"] ?: "Notification"
val body = data["body"] ?: data["message"] ?: ""
val imageBitmap = downloadBitmap(data["image_url"])
val largeIconBitmap = downloadBitmap(data["large_icon_url"])
val badgeCount = data["badge"]?.toIntOrNull()
val soundName = data["sound"]
val sticky = data["sticky"]
val notificationId = System.currentTimeMillis().toInt()
// Custom data["priority"] (NOT the real android.priority header, which the app never
// observes) picks the channel — "normal" routes to a lower-importance channel with no
// heads-up banner, purely to make the priority difference observable in a demo.
val priority = data["priority"]
val channelId = if (priority == "normal") "zixflow_normal" else "zixflow_default"
val builder = NotificationCompat.Builder(this, channelId)
.setContentTitle(title)
.setContentText(body)
// A bundled drawable (or a default_notification_icon manifest meta-data fallback) is
// REQUIRED — an unresolvable icon name renders as a blank white square with no error.
.setSmallIcon(R.drawable.ic_notification)
.setColor(getColor(R.color.notification_accent))
.setAutoCancel(true)
// "until_click"/"true" = ongoing (survives swipe + Clear all); anything else = normal.
.setOngoing(sticky == "until_click" || sticky == "true" || sticky == "ongoing")
.setPriority(if (priority == "normal") NotificationCompat.PRIORITY_DEFAULT else NotificationCompat.PRIORITY_HIGH)
if (largeIconBitmap != null) builder.setLargeIcon(largeIconBitmap)
if (imageBitmap != null) {
builder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(imageBitmap).setBigContentTitle(title).setSummaryText(body))
}
if (badgeCount != null) builder.setNumber(badgeCount)
if (!soundName.isNullOrEmpty() && soundName != "default" && soundName != "none") {
// Bare resource name, no extension — must exist in res/raw/.
builder.setSound(Uri.parse("android.resource://$packageName/raw/$soundName"))
}
val clickAction = data["click_action"]
builder.setContentIntent(buildContentPendingIntent(data["Zixflow-Delivery-ID"] ?: "", data["Zixflow-Delivery-Token"] ?: "", data["deeplink_url"], clickAction))
PushActionButtons.attachFromRawData(
deliveryId = data["Zixflow-Delivery-ID"] ?: "",
deliveryToken = data["Zixflow-Delivery-Token"] ?: "",
actionButtonsJson = data["action_buttons"],
notificationId = notificationId,
builder = builder,
context = this,
)
val notification = builder.build()
if (sticky == "until_swipe" || sticky == "no_clear") {
notification.flags = notification.flags or Notification.FLAG_NO_CLEAR
}
NotificationManagerCompat.from(this).notify(notificationId, notification)
data["ttl_seconds"]?.toIntOrNull()?.takeIf { it > 0 }?.let { ttlSeconds ->
Handler(Looper.getMainLooper()).postDelayed({
NotificationManagerCompat.from(this).cancel(notificationId)
}, ttlSeconds * 1000L)
}
data["analytics_label"]?.let { Log.i("CustomFCMService", "analytics_label: $it") }
}
```
> Demo-only keys used above (`data.priority`, `data.analytics_label`, `data.ttl_seconds`) are sample-app conventions, not part of the official Zixflow schema. Every other field traces back to the [Complete Key Reference](#complete-key-reference).
***
## The Three Tracking Events
Each interaction maps to a specific SDK call. The event name, parameters, and platform code are listed for each below.
> **Event naming — read this first:** `trackMetric()` used to always send a single generic internal event name, `Report Delivery Event`, for every metric type (delivered/opened/clicked/converted), with the actual status only distinguishable via an internal `metric` property. The SDK now sends the **metric name itself as the event name** — `Delivered`, `Opened`, `Clicked` — so each lifecycle stage is directly filterable/reportable by name in analytics and Journeys, with no code change required on your side (you still call `trackMetric(event: MetricEvent.DELIVERED)` exactly as before; only the resulting event name on the backend changed). If you have older dashboards/segments filtering on the literal string `"Report Delivery Event"`, update them to filter on `"Delivered"` / `"Opened"` / `"Clicked"` instead. Both old and new names are still recognized by the backend, so nothing breaks during the transition — but new events will use the short-form names going forward.
***
### 1. Delivery Confirmed
**Event name (sent to Zixflow):** `Delivered`\
**When to fire:** The moment the push data payload arrives on the device — inside your `onMessageReceived` handler.\
**SDK method:** `Zixflow.instance.trackMetric(deliveryID:, deviceToken:, event: MetricEvent.DELIVERED)`\
**What happens:** Zixflow updates the campaign delivery record to `delivered`. No profile event is stored.
**Parameters:**
| Parameter | Required | Description |
| ------------- | -------- | --------------------------------------------------------------------------------------- |
| `deliveryID` | ✅ | Value of `Zixflow-Delivery-ID` from the push payload |
| `deviceToken` | ✅ | Value of `Zixflow-Delivery-Token` from the push payload (or your cached FCM/APNs token) |
| `event` | ✅ | `MetricEvent.DELIVERED` |
```kotlin theme={null}
// In FirebaseMessagingService.onMessageReceived()
override fun onMessageReceived(message: RemoteMessage) {
val deliveryId = message.data["Zixflow-Delivery-ID"] ?: ""
val deliveryToken = message.data["Zixflow-Delivery-Token"] ?: cachedToken ?: ""
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance.trackMetric(
deliveryID = deliveryId,
deviceToken = deliveryToken,
event = MetricEvent.DELIVERED
)
}
}
```
When using the default SDK FCM service, delivery is tracked automatically for recognized pushes. When using a custom service (example dual-path), track after the SDK helper returns `false`.
#### Tracking Delivery When the Device Is Locked
A common gap: **`Delivered` isn't recorded when the push arrives while the phone is locked or the app is backgrounded.** This is expected default OS behaviour, and how you fix it depends on the rendering mode.
**Why it happens**
* **Native mode:** a `notification`-block push is rendered by the OS directly. Your `onMessageReceived` handler is **not** invoked while the app is backgrounded/locked, so there is no client-side moment to call `trackMetric(delivered)`. Your code only runs when the user taps (→ `Opened`).
* **Custom mode with normal priority:** a data-only push sent at normal priority can be **deferred by Doze / App Standby** while the screen is off, so `onMessageReceived` may fire late or not until the device wakes.
**The fix**
| Mode | How to reliably track `Delivered` while locked |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Custom mode** | Zixflow sends Custom-mode pushes as **high-priority data messages** (`android.priority: "high"`). High priority is what makes FCM wake `onMessageReceived` even in Doze / on the lock screen — track `Delivered` there. This is the recommended path when guaranteed delivery tracking matters. |
| **Native mode** | The OS renders it and no app code runs on arrival — client-side `Delivered` on the lock screen isn't possible. Use Zixflow's **server-side delivery receipt** as the source of truth for `Delivered`, and track `Opened` on tap. If you need client-side `Delivered`, switch the campaign to Custom mode. |
***
### 2. Notification Opened
**Event name (sent to Zixflow):** `Opened`\
**When to fire:** When the user taps the notification banner. Fire for **both** body taps and action button taps.\
**SDK method:** `Zixflow.instance.trackMetric(deliveryID:, deviceToken:, event: MetricEvent.opened)`\
**What happens:** Zixflow updates the campaign delivery record to `opened`. No profile event is stored.
**Parameters:**
| Parameter | Required | Description |
| ------------- | -------- | ------------------------------------------------------- |
| `deliveryID` | ✅ | Value of `Zixflow-Delivery-ID` from the push payload |
| `deviceToken` | ✅ | Value of `Zixflow-Delivery-Token` from the push payload |
| `event` | ✅ | `MetricEvent.OPENED` |
```kotlin theme={null}
// In your launcher Activity.onCreate() — handle the tap intent
intent.extras?.let { extras ->
val deliveryId = extras.getString("Zixflow-Delivery-ID", "")
val deliveryToken = extras.getString("Zixflow-Delivery-Token") ?: cachedToken ?: ""
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance.trackMetric(
deliveryID = deliveryId,
deviceToken = deliveryToken,
event = MetricEvent.OPENED
)
}
}
```
Default module: body opens are automatic. Custom display path — put delivery extras on the content `PendingIntent` and handle in the Activity.
***
### 3. Action Button Clicked
**Event name (sent to Zixflow):** `Push Notification Action Clicked`\
**When to fire:** When the user taps a named action button ("Shop Now", "Try It Free", etc.). Always fire `trackMetric(opened)` first, then fire this event.\
**SDK method:** `Zixflow.instance.track(name: "Push Notification Action Clicked", properties: {...})`\
**What happens:** Zixflow records a `clicked` delivery report and captures which button was tapped for campaign analytics.
> This is a named `track()` call — not `trackMetric()`. There is no `MetricEvent` enum for clicks.
**Properties:**
| Property | Type | Required | Description |
| ------------------------ | ------- | ----------- | ------------------------------------------------------- |
| `Zixflow-Delivery-ID` | string | ✅ | Links this event to the campaign delivery record |
| `action_index` | integer | ✅ | 0-based index of the button tapped (`0` = first button) |
| `action_name` | string | ✅ | Human-readable button label (e.g. `"Shop Now"`) |
| `Zixflow-Delivery-Token` | string | Recommended | Token push was sent to |
| `action_deeplink` | string | Recommended | URL the button navigates to |
| `title` | string | Optional | Notification title |
| `action_id` | string | Optional | OS-level action identifier (e.g. `"ACTION_0"`) |
| `source` | string | Optional | `"local_notification"` or `"web_push"` |
```kotlin theme={null}
// In your NotificationActionReceiver
val deliveryId = intent.getStringExtra("Zixflow-Delivery-ID") ?: ""
val deliveryToken = intent.getStringExtra("Zixflow-Delivery-Token") ?: cachedToken ?: ""
val actionIndex = intent.getIntExtra("action_index", -1)
val actionName = intent.getStringExtra("action_name") ?: "Action ${actionIndex + 1}"
val actionDeep = intent.getStringExtra("action_deeplink") ?: ""
// Always fire opened first
Zixflow.instance.trackMetric(deliveryID = deliveryId, deviceToken = deliveryToken, event = MetricEvent.OPENED)
// Then fire action clicked
Zixflow.instance.track(
name = "Push Notification Action Clicked",
properties = mapOf(
"Zixflow-Delivery-ID" to deliveryId,
"Zixflow-Delivery-Token" to deliveryToken,
"action_index" to actionIndex,
"action_name" to actionName,
"action_deeplink" to actionDeep
)
)
```
**Never automatic.** Pass the same `notification_id` into each action `PendingIntent` that you use with `notify()`. Reference: [`NotificationActionReceiver.kt`](https://github.com/zixflow/sdk-examples/blob/master/android/app/src/main/java/com/zixflow/demo/NotificationActionReceiver.kt).
***
## Platform-Specific Integration
### Token Registration
```kotlin theme={null}
import com.zixflow.Zixflow
// In your FirebaseMessagingService.onNewToken()
override fun onNewToken(token: String) {
super.onNewToken(token)
Zixflow.instance.registerDeviceToken(token)
}
// On app start (in case token already exists)
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
Zixflow.instance.registerDeviceToken(token)
}
```
### Track Delivery (Foreground — via onMessageReceived)
```kotlin theme={null}
override fun onMessageReceived(message: RemoteMessage) {
val data = message.data
val deliveryId = data["Zixflow-Delivery-ID"] ?: ""
val deliveryToken = data["Zixflow-Delivery-Token"] ?: currentToken ?: ""
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance.trackMetric(
deliveryID = deliveryId,
deviceToken = deliveryToken,
event = MetricEvent.DELIVERED
)
} else {
Zixflow.instance.track(
name = "Push Notification Delivered",
properties = mapOf(
"notification_id" to (data["notif_id"] ?: ""),
"title" to (data["title"] ?: ""),
"body" to (data["body"] ?: "")
)
)
}
showLocalNotification(message)
}
```
### Track Open (Notification Tap)
```kotlin theme={null}
// In your launcher Activity.onCreate() or wherever you handle the intent
intent.extras?.let { extras ->
val deliveryId = extras.getString("Zixflow-Delivery-ID", "")
val deliveryToken = extras.getString("Zixflow-Delivery-Token", "") ?: currentToken ?: ""
val deeplink = extras.getString("deeplink_url", "")
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance.trackMetric(
deliveryID = deliveryId,
deviceToken = deliveryToken,
event = MetricEvent.OPENED
)
}
if (deeplink.isNotEmpty()) navigateTo(deeplink)
}
```
### Track Action Button Click
```kotlin theme={null}
// In your NotificationActionReceiver
val deliveryId = intent.getStringExtra("Zixflow-Delivery-ID") ?: ""
val actionIndex = intent.getIntExtra("action_index", -1)
val actionName = intent.getStringExtra("action_name") ?: "Action ${actionIndex + 1}"
val actionDeep = intent.getStringExtra("action_deeplink") ?: ""
Zixflow.instance.trackMetric( // also fire opened
deliveryID = deliveryId, deviceToken = token, event = MetricEvent.OPENED
)
Zixflow.instance.track(
name = "Push Notification Action Clicked",
properties = mapOf(
"Zixflow-Delivery-ID" to deliveryId,
"Zixflow-Delivery-Token" to token,
"notification_id" to deliveryId,
"action_index" to actionIndex,
"action_name" to actionName,
"action_deeplink" to actionDeep,
"source" to "local_notification"
)
)
```
***
## Action Buttons Format
The `action_buttons` field in the push payload is a **JSON-encoded string** containing an array of button objects. You must parse it before use. Action buttons never render on the Native (OS-rendered) path — attach them only when you build the notification yourself.
**Payload value (raw string):**
```
"[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"
```
**Parsed structure:**
```json theme={null}
[
{ "name": "Shop Now", "deeplink": "https://yourapp.com/sale" },
{ "name": "Remind Me", "deeplink": "" }
]
```
**Rules:**
* Maximum **2 buttons** per notification
* `deeplink` may be an empty string — handle gracefully, don't navigate to a blank URL
* Button index is **0-based**
### Parsing `action_buttons`
```kotlin theme={null}
data class ActionButton(val name: String, val deeplink: String)
fun parseActionButtons(buttonsJson: String?): List {
if (buttonsJson.isNullOrEmpty()) return emptyList()
return try {
val jsonArray = JSONArray(buttonsJson)
val count = minOf(jsonArray.length(), 2)
List(count) { index ->
val button = jsonArray.getJSONObject(index)
ActionButton(
name = button.getString("name"),
deeplink = button.optString("deeplink", "")
)
}
} catch (_: Exception) {
emptyList()
}
}
```
### Building the Buttons on the Notification (Foreground Path)
```kotlin theme={null}
// CustomFirebaseMessagingService.kt pattern — data-payload path (foreground / manual display)
val buttonsJson = JSONArray(data["action_buttons"] ?: "[]")
for (i in 0 until minOf(buttonsJson.length(), 2)) {
val btn = buttonsJson.getJSONObject(i)
val actionIntent = Intent(this, NotificationActionReceiver::class.java).apply {
putExtra("Zixflow-Delivery-ID", deliveryId)
putExtra("Zixflow-Delivery-Token", deliveryToken)
putExtra("action_index", i)
putExtra("action_name", btn.optString("name"))
putExtra("action_deeplink", btn.optString("deeplink"))
// Needed so the receiver can dismiss this exact notification on press —
// see "Sticky Notifications" below for why this matters even for non-sticky pushes.
putExtra("notification_id", notificationId)
}
val pendingIntent = PendingIntent.getBroadcast(
this, i, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
builder.addAction(
NotificationCompat.Action.Builder(0, btn.optString("name"), pendingIntent).build()
)
}
```
`NotificationActionReceiver` is the `BroadcastReceiver` that fires the `trackMetric(opened)` + `track("Push Notification Action Clicked")` calls shown earlier — it reads back the extras you attached above.
### Handling the Tap (End-to-End)
| Platform | Callback | Identifier format | Resolve to index |
| ---------------- | -------------------------------------- | -------------------------------------------------------- | ------------------------------------ |
| Android (Kotlin) | `NotificationActionReceiver.onReceive` | Read back the `action_index` extra you attached yourself | Already an `Int` — no parsing needed |
**Important on Android (Kotlin):** pressing an action button does **not** automatically dismiss the notification the way tapping the notification body does — see [Sticky Notifications](#sticky-notifications) below.
***
## New Notification Layouts
Richer Android layouts (`style`, `progress`, `timer`, …) are **not** Zixflow-defined fields. If you want them, add those keys yourself in Custom (or Native custom data) and render them in your app. The tables below document the **sample-app convention**. These are **Android-only**.
### `style` — notification layout
| `style` value | Layout | Notes |
| --------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| absent | Basic text (auto-expands to `BigText` if the body is long) | Default. |
| `"big_picture"` | Large expandable image | Uses `image_url`; this is the implicit default whenever `image_url` is present. |
| `"big_text"` | Forced multi-line expandable text | For long copy with no image. |
| `"media"` | Media-player style | Dark themed; `action_buttons` render as transport-control icons in the collapsed view. Combine with `progress` / `max_progress` to show a playback bar. |
### `progress` — determinate / playback bar
Shown as a horizontal bar (used with `style: "media"` for playback position, but works on any style).
| Field | Type | Meaning |
| ------------------------ | -------------------- | --------------------------------------------------------------- |
| `progress` | int (string) | Current value (e.g. `"35"`). |
| `max_progress` | int (string) | Max value (e.g. `"100"`). Defaults to `100`. |
| `progress_indeterminate` | `"true"` / `"false"` | Animated indeterminate bar (ignores `progress`/`max_progress`). |
### `timer` — live countdown chronometer
Shows a self-updating countdown (e.g. `Expires in ⏰ 00:00:00`).
| Field | Type | Meaning |
| ------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
| `timer` | `"true"` | Enables the chronometer. Required to activate the fields below. |
| `timer_end_ms` | int (string) | Absolute epoch-millis to count **down** to. |
| `timer_duration_ms` | int (string) | Alternative to `timer_end_ms` — counts down `now + duration`. Used if `timer_end_ms` is absent. |
Under the hood this maps to Android's `setUsesChronometer(true)` + `setChronometerCountDown(true)` + `setWhen(endTimeMillis)` + `setShowWhen(true)`.
### Example payloads
```json theme={null}
{
"style": "media",
"title": "Keep Watching",
"body": "Watch 5 eps FREE of this super hit!",
"large_icon_url": "https://cdn.yourapp.com/poster.png",
"progress": "35",
"max_progress": "100",
"action_buttons": "[{\"name\":\"Play\",\"deeplink\":\"https://yourapp.com/watch\"},{\"name\":\"Later\",\"deeplink\":\"\"}]"
}
```
```json theme={null}
{
"title": "Check-in Reminder",
"body": "Continue to Claim Free Coins",
"timer": "true",
"timer_duration_ms": "3600000",
"sticky": "until_swipe",
"action_buttons": "[{\"name\":\"Get Now\",\"deeplink\":\"https://yourapp.com/claim\"}]"
}
```
***
## Sticky Notifications
`data.sticky` is an official Custom-mode field. Apply Android flags in **your** notification builder — Native FCM rendering does **not** honor sticky (`android.notification.sticky` has no effect).
**Sample-app convention:** `sticky` (string in `data.*`). Accepts **three** values:
| `sticky` value | Behaviour | Android flags |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| absent / `"false"` | Normal — swipe **or** "Clear all" both dismiss it. | none |
| `"until_click"` (aliases `"true"`, `"ongoing"`) | **Sticky until clicked** — survives **both** an individual swipe **and** "Clear all". Only removed when the user taps it (body tap). | `ongoing = true` |
| `"until_swipe"` (alias `"no_clear"`) | **Sticky until swiped** — an individual swipe still dismisses it, but the **"Clear all" button will not** remove it. | `FLAG_NO_CLEAR` (without `ongoing`) |
Both sticky modes are **Android-only** — there is no iOS/APNs equivalent.
> **How the two modes differ at the flag level:**
>
> * **`until_click` → `setOngoing(true)`** sets both `FLAG_ONGOING_EVENT` and `FLAG_NO_CLEAR`. This removes the swipe-to-dismiss affordance entirely and excludes the notification from "Clear all". The only way the user can get rid of it is to tap it (which, with `setAutoCancel(true)`, dismisses it).
> * **`until_swipe` → `FLAG_NO_CLEAR` only** (set via `additionalFlags`, *not* `setOngoing`). The notification can still be swiped away individually, but the bulk "Clear all" button skips it.
> **Regardless of which mode, two things always hold — and a third piece of manual cleanup for buttons:**
>
> * `setAutoCancel(true)` must **always** be on — this is what makes tapping the notification body remove it. Do **not** tie this to `sticky`.
> * `ongoing`/`FLAG_NO_CLEAR` (driven by the `sticky` value) is what actually blocks **swipe** and/or **"Clear all"** per the table above.
> * **Action buttons need an explicit cancel call in your action handler.** Android does not auto-dismiss a notification when an action button is pressed. **Exception:** for `until_click` (ongoing) notifications, do **not** auto-cancel on action-button press either — only a body tap should remove an ongoing notification.
| Mechanism | Controls | Value |
| -------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| `autoCancel` (`setAutoCancel`) | Dismissal on **body tap** | Always `true` |
| `ongoing` (`setOngoing`) | Dismissal by **swipe** and **"Clear all"** | `true` only when `sticky == "until_click"` (or aliases `"true"` / `"ongoing"`) |
| `FLAG_NO_CLEAR` | Dismissal by **"Clear all"** only (swipe still works) | set only when `sticky == "until_swipe"` (or alias `"no_clear"`) |
| Explicit cancel call in your action-button handler | Dismissal on **action button press** | Called for normal + `until_swipe`; **not** called for `until_click` (ongoing) |
### Why Two Places Read `sticky`
Because of how push delivery differs by app state, the flag needs to be honored in **two different code paths** — and in practice, only one of them actually works:
| App state | Who displays the notification | Where `sticky` is read |
| ------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Background / killed | FCM auto-displays from the `notification.*` + `android.notification.*` payload blocks — no app code runs | `android.notification.sticky` exists in FCM's schema, but **does not actually prevent dismissal** on this auto-rendered path. Treat sticky as **unsupported** on this path. |
| Foreground / Custom | Your app code builds the notification manually from the `data.*` payload | `data.sticky` — your app must read this string and apply `ongoing` / `FLAG_NO_CLEAR` itself. **This is the only path where sticky reliably works.** |
If you need sticky behavior at all, it only works when your app builds the notification itself — i.e. the custom-handled path. See [Complete Field Mapping Reference](#complete-field-mapping-reference-custom-handled-ui) for the full builder.
### Android (Kotlin) — Native SDK / Manual Notification Builder
```kotlin theme={null}
// Read the flag from the data payload (defaults to false if absent/invalid)
val sticky = data["sticky"]?.toBooleanStrictOrNull() ?: false
// Same ID passed to `notificationManager.notify()` — needed so the action-button
// receiver can explicitly cancel this exact notification (see below).
val notificationId = System.currentTimeMillis().toInt()
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true) // always true — body tap always dismisses
.setOngoing(sticky) // true = survives swipe + "Clear all"
// ...other builder calls (icon, image, sound, etc.)
// Action buttons must carry the notification ID so the receiver can cancel it:
PushActionButtons.attachFromRawData(
deliveryId = deliveryId,
deliveryToken = deliveryToken,
actionButtonsJson = data["action_buttons"],
notificationId = notificationId, // ← threaded through to the receiver
builder = notificationBuilder,
context = context
)
notificationManager.notify(notificationId, notificationBuilder.build())
```
```kotlin theme={null}
// NotificationActionReceiver.kt — runs when an action button is pressed
val notificationId = intent.getIntExtra(PushActionButtons.EXTRA_NOTIFICATION_ID, -1)
if (notificationId != -1) {
// Required regardless of `sticky` — action buttons never auto-dismiss on Android.
NotificationManagerCompat.from(context).cancel(notificationId)
}
```
If you add a `sticky` key in custom `data` and the standard Zixflow Android SDK displays the notification, it applies `ongoing` / related flags from that value. Native OS-rendered pushes still ignore sticky.
***
## Deep Link Handling
Every Zixflow push may carry a `deeplink_url` at the top level of `data` and per-button deeplinks in `action_buttons`. Your app is responsible for routing these to the right in-app screen, falling back to an external browser/app when the link isn't one of your own screens.
**Priority order for navigation on tap:**
1. If an action button was tapped → use `action_buttons[index].deeplink`
2. If body was tapped → use `deeplink_url`
3. If neither is set → open app to default screen
> **`click_action` is a separate, custom-handled-only concept** — not part of the deeplink system above. Sample apps additionally read `data.click_action` on a body tap and, if it matches a known token (`"OPEN_SALE"`, `"OPEN_DASHBOARD"`), resolve it to an in-app screen **taking priority over `deeplink_url`**. On the Native path, FCM `click_action` needs a matching `` — a mismatch causes the tap to do nothing at all. See the [Complete Key Reference](#complete-key-reference).
**If you're using the standard Zixflow Android SDK** (`ZixflowPushNotificationHandler.kt`), deep link resolution is handled **automatically** — you don't need to write any routing code. This lives in `PushMessageProcessorImpl.handleNotificationDeepLink()` and runs in this order:
1. **Host-app override** — if you registered a `notificationCallback` in `moduleConfig` and its `onNotificationClicked(context, payload)` returns non-null, the SDK stops there and your callback owns navigation entirely.
2. **Try your own app first** — builds `Intent(ACTION_VIEW, uri).setPackage(context.packageName)` and checks `resolveActivity()`.
3. **Fall back externally** — if no Activity in your app claims the URI, `queryIntentActivities()` looks for any other installed app.
4. **Default launcher fallback** — if nothing matches at all, your app's normal launcher Activity opens.
To make your own screens resolvable, add matching intent filters:
```xml theme={null}
```
Or, to fully own routing yourself, implement `NotificationCallback.onNotificationClicked()`:
```kotlin theme={null}
Zixflow.instance.configure(
moduleConfig = ZixflowModuleConfig(
// ...
notificationCallback = object : NotificationCallback {
override fun onNotificationClicked(
context: Context,
payload: ZixflowParsedPushPayload
): Boolean {
val handled = DeeplinkRouter.open(context, payload.deepLink)
return handled // non-null return short-circuits the SDK's own resolution
}
}
)
)
```
**If you're not using the SDK's built-in display path** (e.g. a custom `FirebaseMessagingService` fallback), you resolve the URI yourself:
```kotlin theme={null}
object DeeplinkRouter {
fun open(context: Context, deeplink: String?): Boolean {
val uri = deeplink?.takeIf { it.isNotBlank() }?.let(Uri::parse) ?: return false
if (uri.scheme == "zixflowdemo") {
val intent = when (uri.host) {
"sale" -> Intent(context, SaleActivity::class.java)
"dashboard" -> Intent(context, DashboardActivity::class.java)
else -> null
}
if (intent != null) {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
return true
}
}
// Not one of our screens — open externally
return try {
context.startActivity(Intent(Intent.ACTION_VIEW, uri).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
true
} catch (_: ActivityNotFoundException) {
false
}
}
}
```
***
## Decision Flowchart
```
Notification received?
├── YES → trackMetric(delivered) [always, as soon as data payload arrives]
│
User interacted?
├── Tapped notification body
│ └── trackMetric(opened) [SDK call with deliveryID + deviceToken]
│ └── Navigate to deeplink_url
│
└── Tapped action button
├── trackMetric(opened) [still track opened]
└── track("Push Notification Action Clicked") [with action_index + action_name]
└── Navigate to button's deeplink
```
***
## Fallback Behaviour (No Zixflow-Delivery-ID)
If the push notification was sent by a non-Zixflow source, `Zixflow-Delivery-ID` and `Zixflow-Delivery-Token` will be absent from the payload.
In this case, skip `trackMetric()`. Push notification event names (`Delivered`, `Opened`, `Push Notification Action Clicked`) are reserved for Zixflow's delivery pipeline **only when their properties actually carry a Zixflow delivery identifier** — the backend checks for `Zixflow-Delivery-ID` (or an internal alias) in the event's properties before treating it as a delivery report, specifically so a name collision with your own custom event of the same name does not silently swallow it. If you ever do send a custom event named exactly `Delivered`, `Opened`, or `Clicked` for your own purposes but it has no delivery identifier in its properties, it is stored as a normal profile event like any other — it is **not** treated as a push metric.
When a Zixflow-originated push tracking call **is** correctly matched, its event is **not** stored as a user profile event — the delivery report itself is sufficient.
If you want to track a non-Zixflow push for your own analytics, use a **custom event name** instead.
***
## Testing
Enable `ZixflowLogLevel.DEBUG`. In the dashboard → **Messaging** → **Push Notifications**, check **Delivered**, **Opened**, and **Clicked**.
# Push Notifications
Source: https://docs.zixflow.com/documentation/sdk/android/push-notifications
Push Notifications — Zixflow Android SDK integration guide.
Complete guide to implementing push notifications with Zixflow using Firebase Cloud Messaging (FCM).
> **Track your campaigns:** With the default FCM module, the SDK auto-tracks **delivered** and body **opened**. **Action-button clicks are never automatic**. Campaigns can be **Native** (OS-rendered from `notification.*`) or **Custom** (app-rendered from `data.*`). Zixflow always sends `Zixflow-Delivery-ID` and `Zixflow-Delivery-Token`, plus routing keys (`deeplink_url`, `action_buttons`, `template_id`). Payloads: [Push Notification Tracking](/documentation/sdk/android/push-notification-tracking#fcm-android-wire-format).
### Prerequisites
Before implementing push notifications:
1. **Configure FCM in Zixflow dashboard** - Add your FCM Server Key in the Zixflow dashboard first
2. **Firebase Project Setup** - Create Firebase project and add your Android app
3. **Install the push messaging module** - Add `messaging-push-fcm` dependency
**Important:** FCM setup is required before implementing push notifications. This integration serves as the foundation for all push messaging capabilities.
***
### Step 1: Firebase Project Setup
#### 1. Create Firebase Project
1. Go to [https://console.firebase.google.com](https://console.firebase.google.com)
2. Click **Add project** or select existing project
3. Follow the setup wizard
#### 2. Add Android App to Firebase
1. In Firebase console, click **Add app** → Select **Android**
2. Enter your app's package name (e.g., `com.yourcompany.app`)
3. (Optional) Add SHA-1 certificate for debugging
4. Download `google-services.json`
#### 3. Add google-services.json to Project
1. Place `google-services.json` in your `app/` directory
2. Verify the file is at: `app/google-services.json`
***
### Step 2: Add Dependencies
#### 1. Add Google Services Plugin
**Project-level `build.gradle.kts`:**
```kotlin theme={null}
plugins {
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
}
```
**Project-level `build.gradle` (Groovy):**
```groovy theme={null}
plugins {
id 'com.android.application' version '8.9.1' apply false
id 'org.jetbrains.kotlin.android' version '2.0.21' apply false
id 'com.google.gms.google-services' version '4.4.2' apply false
}
```
#### 2. Apply Plugin in App Module
**App-level `build.gradle` or `build.gradle.kts`:**
```kotlin theme={null}
// build.gradle.kts (Kotlin DSL)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services") // Add this line
}
```
```groovy theme={null}
// build.gradle (Groovy DSL)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services' // Add this line
}
```
#### 3. Add Zixflow and Firebase Dependencies
**Kotlin DSL:**
```kotlin theme={null}
dependencies {
// Zixflow core and push module
implementation("com.zixflow.com.android:datapipelines:1.1.3")
implementation("com.zixflow.com.android:messaging-push-fcm:1.1.3")
// Firebase (if not already included)
implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
implementation("com.google.firebase:firebase-messaging")
}
```
**Groovy DSL:**
```groovy theme={null}
dependencies {
// Zixflow core and push module
implementation 'com.zixflow.com.android:datapipelines:1.1.3'
implementation 'com.zixflow.com.android:messaging-push-fcm:1.1.3'
// Firebase
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.google.firebase:firebase-messaging'
}
```
***
### Step 3: Initialize Zixflow with Push Support
#### Basic Initialization (Kotlin)
```kotlin theme={null}
import android.app.Application
import com.zixflow.sdk.Zixflow
import com.zixflow.sdk.ZixflowConfigBuilder
import com.zixflow.sdk.core.util.ZixflowLogLevel
import com.zixflow.messagingpush.ModuleMessagingPushFCM
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Zixflow SDK
val config = ZixflowConfigBuilder(
applicationContext = this,
apiKey = "YOUR_API_KEY"
)
.autoTrackDeviceAttributes(true)
.autoTrackActivityScreens(true)
.trackApplicationLifecycleEvents(true)
.logLevel(ZixflowLogLevel.DEBUG)
// Add push notifications module
.addZixflowModule(ModuleMessagingPushFCM())
.build()
Zixflow.initialize(config)
}
}
```
#### Basic Initialization (Java)
```java theme={null}
import android.app.Application;
import com.zixflow.sdk.Zixflow;
import com.zixflow.sdk.ZixflowConfig;
import com.zixflow.sdk.ZixflowConfigBuilder;
import com.zixflow.sdk.core.util.ZixflowLogLevel;
import com.zixflow.messagingpush.ModuleMessagingPushFCM;
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize Zixflow SDK
ZixflowConfig config = new ZixflowConfigBuilder(this, "YOUR_API_KEY")
.autoTrackDeviceAttributes(true)
.autoTrackActivityScreens(true)
.trackApplicationLifecycleEvents(true)
.logLevel(ZixflowLogLevel.DEBUG)
.addZixflowModule(new ModuleMessagingPushFCM())
.build();
Zixflow.initialize(config);
}
}
```
**Important:** The SDK automatically:
* Registers a `FirebaseMessagingService` in your manifest
* Fetches and registers FCM device tokens
* Handles push notification display and tracking
***
### Step 4: Register Application in AndroidManifest.xml
Ensure your custom `Application` class is registered:
```xml theme={null}
```
***
### Step 5: Request Push Notification Permission (Android 13+)
Android 13 (API 33) and higher requires runtime permission for notifications.
#### Request Permission in Activity
```kotlin theme={null}
import android.Manifest
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import android.content.pm.PackageManager
class MainActivity : AppCompatActivity() {
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// Permission granted - push notifications enabled
println("Push notification permission granted")
} else {
// Permission denied - explain value to user
println("Push notification permission denied")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Request permission for Android 13+
requestNotificationPermission()
}
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED -> {
// Permission already granted
println("Notification permission already granted")
}
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> {
// Show explanation to user why you need this permission
showPermissionRationale()
}
else -> {
// Request permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}
private fun showPermissionRationale() {
// Show dialog explaining why push notifications are valuable
// Then request permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
```
***
### Step 6: Identify Users
**Critical:** Device tokens must be associated with a user before push notifications can be delivered.
```kotlin theme={null}
// Identify user when they log in
Zixflow.instance().identify(
userId = "user@example.com",
traits = mapOf(
"email" to "user@example.com",
"first_name" to "John",
"last_name" to "Doe",
"plan" to "premium"
)
)
```
**Important Notes:**
* When users start the app, they automatically generate a device token
* This token links to their Zixflow profile when you call `identify()`
* This ensures notifications are delivered to the correct user
***
### Configuration Options
Configure push notification behavior with `MessagingPushModuleConfig`:
```kotlin theme={null}
import com.zixflow.messagingpush.MessagingPushModuleConfig
import com.zixflow.messagingpush.ModuleMessagingPushFCM
import com.zixflow.messagingpush.config.PushClickBehavior
val pushConfig = MessagingPushModuleConfig.Builder()
.setAutoTrackPushEvents(true) // Auto-track delivered/opened
.setPushClickBehavior( // Control app navigation
PushClickBehavior.ACTIVITY_PREVENT_RESTART
)
.build()
val config = ZixflowConfigBuilder(this, "YOUR_API_KEY")
.addZixflowModule(ModuleMessagingPushFCM(moduleConfig = pushConfig))
.build()
```
#### Configuration Options
| Option | Type | Default | Description |
| ---------------------- | ------------------------------- | -------------------------- | ---------------------------------------------------- |
| `autoTrackPushEvents` | Boolean | `true` | Automatically track `delivered` and `opened` metrics |
| `pushClickBehavior` | PushClickBehavior | `ACTIVITY_PREVENT_RESTART` | Define behavior when notification is clicked |
| `notificationCallback` | ZixflowPushNotificationCallback | `null` | Override default push behavior |
#### Push Click Behaviors
Three strategies control app navigation when notifications are tapped:
**`ACTIVITY_PREVENT_RESTART`** (Default - Recommended)
* Reuses existing activities when possible
* Ideal for apps with sensitive screens (checkout, payment)
* Prevents interrupting user workflows
**`ACTIVITY_NO_FLAGS`**
* Creates new activity instance on every notification tap
* Standard Android behavior
**`RESET_TASK_STACK`**
* Clears entire app state
* Prevents back navigation to previous screens
* Use when you want fresh app start
```kotlin theme={null}
import com.zixflow.messagingpush.config.PushClickBehavior
val pushConfig = MessagingPushModuleConfig.Builder()
.setPushClickBehavior(PushClickBehavior.ACTIVITY_PREVENT_RESTART)
.build()
```
***
### Advanced Features
#### Customize Notification Appearance
Override notification appearance using `ZixflowPushNotificationCallback`:
```kotlin theme={null}
import com.zixflow.messagingpush.data.communication.ZixflowPushNotificationCallback
import com.zixflow.messagingpush.data.model.ZixflowParsedPushPayload
import androidx.core.app.NotificationCompat
val pushConfig = MessagingPushModuleConfig.Builder()
.setNotificationCallback(object : ZixflowPushNotificationCallback {
override fun onNotificationComposed(
payload: ZixflowParsedPushPayload,
builder: NotificationCompat.Builder
) {
// Customize notification appearance in place
builder
.setColor(getColor(R.color.notification_accent))
.setSound(Uri.parse("android.resource://${packageName}/${R.raw.notification_sound}"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
}
})
.build()
```
#### Attach action buttons
The SDK does **not** auto-attach action buttons. Parse `action_buttons` (a JSON-encoded string of `{name, deeplink}` objects, max 2) from `payload.extras` inside `onNotificationComposed` and call `builder.addAction` with `PendingIntent`s that your app handles. Action buttons only render on the **Custom** (app-built) path.
```kotlin theme={null}
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.zixflow.messagingpush.config.MessagingPushModuleConfig
import com.zixflow.messagingpush.data.communication.ZixflowPushNotificationCallback
import com.zixflow.messagingpush.data.model.ZixflowParsedPushPayload
import org.json.JSONArray
val pushConfig = MessagingPushModuleConfig.Builder()
.setNotificationCallback(object : ZixflowPushNotificationCallback {
override fun onNotificationComposed(
payload: ZixflowParsedPushPayload,
builder: NotificationCompat.Builder
) {
val context = /* your Application or Activity context */
attachActionButtons(context, payload, builder)
}
})
.build()
fun attachActionButtons(
context: Context,
payload: ZixflowParsedPushPayload,
builder: NotificationCompat.Builder
) {
val raw = payload.extras.getString("action_buttons") ?: return
val buttons = try {
JSONArray(raw)
} catch (_: Exception) {
return
}
val count = minOf(buttons.length(), 2) // max 2 buttons
for (index in 0 until count) {
val button = buttons.getJSONObject(index)
val name = button.optString("name", "Action ${index + 1}")
val deeplink = button.optString("deeplink", "")
val intent = Intent(context, NotificationActionReceiver::class.java).apply {
action = "com.yourcompany.app.PUSH_NOTIFICATION_ACTION"
putExtra("Zixflow-Delivery-ID", payload.zixflowDeliveryId)
putExtra("Zixflow-Delivery-Token", payload.zixflowDeliveryToken)
putExtra("action_index", index)
putExtra("action_name", name)
putExtra("action_deeplink", deeplink)
// Same ID used in NotificationManagerCompat.notify() — required to cancel on action press
putExtra("notification_id", notificationId)
}
val flags = PendingIntent.FLAG_UPDATE_CURRENT or
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
val pendingIntent = PendingIntent.getBroadcast(
context,
index,
intent,
flags
)
builder.addAction(
NotificationCompat.Action.Builder(0, name, pendingIntent).build()
)
}
}
```
Register a `BroadcastReceiver` (for example `NotificationActionReceiver`) with matching intent action. In the receiver: track **Opened**, then `Push Notification Action Clicked`, then `NotificationManagerCompat.cancel(notificationId)` (action presses do not auto-dismiss). See [Push Notification Tracking](/documentation/sdk/android/push-notification-tracking#3-action-button-clicked) and [`NotificationActionReceiver.kt`](https://github.com/zixflow/sdk-examples/blob/master/android/app/src/main/java/com/zixflow/demo/NotificationActionReceiver.kt).
#### Configure Default Icon and Color
Add metadata to `AndroidManifest.xml`:
```xml theme={null}
```
#### Rich Push Notifications
Rich push (images, videos, GIFs) is **automatically supported** by the SDK. No additional configuration needed.
Send rich media from Zixflow dashboard:
1. Create push notification campaign
2. Add image URL or video URL
3. SDK automatically downloads and displays media
***
### Custom FirebaseMessagingService (dual-path)
Android routes FCM to **one** `FirebaseMessagingService`. If you declare your own, remove the SDK service from the merged manifest, then try the SDK helper first and fall back to manual display + metrics when it returns `false`.
Reference: [`CustomFirebaseMessagingService.kt`](https://github.com/zixflow/sdk-examples/blob/master/android/app/src/main/java/com/zixflow/demo/CustomFirebaseMessagingService.kt).
```xml theme={null}
```
```kotlin theme={null}
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.zixflow.messagingpush.ZixflowFirebaseMessagingService
import com.zixflow.sdk.Zixflow
import com.zixflow.sdk.events.Metric
import com.zixflow.sdk.events.TrackMetric
class CustomFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
val sdkHandled = try {
ZixflowFirebaseMessagingService.onMessageReceived(
applicationContext,
message,
true // handleNotificationTrigger — display + track when recognized
)
} catch (_: Exception) {
false
}
if (sdkHandled) return
// SDK did not handle — track Delivered and display yourself
val data = message.data
val deliveryId = data["Zixflow-Delivery-ID"]
?: data["ZIXFLOW-Delivery-ID"]
?: ""
val deliveryToken = data["Zixflow-Delivery-Token"]
?: data["ZIXFLOW-Delivery-Token"]
?: Zixflow.instance().registeredDeviceToken
?: ""
if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
Zixflow.instance().trackMetric(
TrackMetric.Push(
metric = Metric.Delivered,
deliveryId = deliveryId,
deviceToken = deliveryToken
)
)
}
// show notification, attach action buttons, put delivery extras on content Intent
handlePushManually(message, deliveryId, deliveryToken)
}
override fun onNewToken(token: String) {
super.onNewToken(token)
try {
ZixflowFirebaseMessagingService.onNewToken(applicationContext, token)
} catch (_: Exception) { }
Zixflow.instance().registerDeviceToken(token)
}
}
```
Accept both `Zixflow-` and `ZIXFLOW-` delivery key casings. For body opens on the manual path, put delivery ID/token on the content `PendingIntent` extras and call `trackMetric(Opened)` in your Activity — see the example `MainActivity.handlePushOpenIntent`.
***
### Tracking Push Metrics
| Event | Default FCM module | Custom dual-path (example) |
| ------------------ | --------------------------------------------- | ---------------------------------------------------------------------- |
| **Delivered** | Automatic when SDK handles the message | Call `trackMetric(Delivered)` when `onMessageReceived` returns `false` |
| **Opened** (body) | Automatic | Track from Activity extras on content-intent launch |
| **Action clicked** | **Never automatic** — receiver + `track(...)` | Same |
> Full details: [Push Notification Tracking](/documentation/sdk/android/push-notification-tracking).
***
### Deep Links and Custom Actions
Zixflow sends `deeplink_url` on the body and per-button `deeplink` values inside `action_buttons`. FCM `android.notification.click_action` (Native path) needs a matching `` or the tap does nothing.
The [Android example](https://github.com/zixflow/sdk-examples/tree/master/android):
1. Puts `deeplink_url` on the content `PendingIntent` (body) and per-button `deeplink` on action intents
2. Tracks Opened / Action Clicked (see [Push Notification Tracking](/documentation/sdk/android/push-notification-tracking))
3. Opens the URL via [`DeeplinkRouter`](https://github.com/zixflow/sdk-examples/blob/master/android/app/src/main/java/com/zixflow/demo/DeeplinkRouter.kt) — custom scheme hosts → Activities; otherwise `ACTION_VIEW`
```kotlin theme={null}
// After body Opened tracking in MainActivity:
DeeplinkRouter.open(this, intent.getStringExtra("deeplink"))
// After action tracking in NotificationActionReceiver:
if (actionDeeplink.isNotEmpty()) {
DeeplinkRouter.open(context, actionDeeplink)
}
```
You can still add HTTPS / app-link intent filters for cold starts from the browser:
```xml theme={null}
```
Full open / action / deeplink contract: [Push Notification Tracking](/documentation/sdk/android/push-notification-tracking).
***
### Sticky Notifications
`data.sticky` is an official Custom-mode field. Native OS rendering does **not** honor sticky.
**Values:**
| `sticky` value | Behaviour | Android flags |
| ----------------------------------------------- | ------------------------------------------------------------- | ---------------- |
| absent / `"false"` | Normal — swipe **or** "Clear all" both dismiss it. | none |
| `"until_click"` (aliases `"true"`, `"ongoing"`) | Survives swipe **and** "Clear all". Only removed on body tap. | `ongoing = true` |
| `"until_swipe"` (alias `"no_clear"`) | Swipe still dismisses it; **"Clear all" will not**. | `FLAG_NO_CLEAR` |
Sticky is **Android-only** and only works on the **Custom** (app-rendered) path. Native/OS-rendered pushes ignore it.
> **This requires two separate Android notification flags, not one — and a third piece of manual cleanup for action buttons.**
>
> * `setAutoCancel(true)` must **always** be on, regardless of `sticky`
> * `setOngoing(sticky == "until_click" || sticky == "true" || sticky == "ongoing")` is what blocks **swipe** and **"Clear all"**
> * `"until_swipe"` / `"no_clear"` applies `FLAG_NO_CLEAR` without `ongoing`
> * **Action buttons need an explicit cancel call in your action handler** (except for `until_click`, where only a body tap should dismiss)
```kotlin theme={null}
// Read the flag from the data payload (defaults to false if absent/invalid)
val sticky = data["sticky"]?.toBooleanStrictOrNull() ?: false
// Same ID passed to `notificationManager.notify()` — needed so the action-button
// receiver can explicitly cancel this exact notification (see below).
val notificationId = System.currentTimeMillis().toInt()
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true) // always true — body tap always dismisses
.setOngoing(sticky) // true = survives swipe + "Clear all"
// ...other builder calls (icon, image, sound, etc.)
// Action buttons must carry the notification ID so the receiver can cancel it:
PushActionButtons.attachFromRawData(
deliveryId = deliveryId,
deliveryToken = deliveryToken,
actionButtonsJson = data["action_buttons"],
notificationId = notificationId, // ← threaded through to the receiver
builder = notificationBuilder,
context = context
)
notificationManager.notify(notificationId, notificationBuilder.build())
```
```kotlin theme={null}
// NotificationActionReceiver.kt — runs when an action button is pressed
val notificationId = intent.getIntExtra(PushActionButtons.EXTRA_NOTIFICATION_ID, -1)
if (notificationId != -1) {
// Required regardless of `sticky` — action buttons never auto-dismiss on Android.
NotificationManagerCompat.from(context).cancel(notificationId)
}
```
If `data.sticky` is present and the standard Zixflow Android SDK displays the notification, it applies `ongoing` / related flags from that value. Native OS-rendered pushes still ignore sticky.
Full details: [Push Notification Tracking — Sticky Notifications](/documentation/sdk/android/push-notification-tracking#sticky-notifications).
***
### Template-based custom rendering (`template_id`)
Every push sent from a dashboard template includes `data.template_id`. Map known IDs to a dedicated renderer and fall back to the generic field-driven builder for unknown IDs. Full pattern: [Template-Based Custom Rendering](/documentation/sdk/android/push-notification-tracking#template-based-custom-rendering-template_id).
***
### Testing Push Notifications
#### 1. Verify Device Token Registration
Check if device token is registered:
```kotlin theme={null}
val deviceToken = Zixflow.instance().registeredDeviceToken
if (deviceToken != null) {
println("Device token registered: $deviceToken")
} else {
println("Device token not yet registered")
}
```
#### 2. Enable Debug Logging
Use debug logging to troubleshoot:
```kotlin theme={null}
val config = ZixflowConfigBuilder(this, "YOUR_API_KEY")
.logLevel(ZixflowLogLevel.DEBUG) // Enable verbose logging
.build()
```
View logs in Android Studio Logcat (filter by "Zixflow").
#### 3. Test on Physical Device or Emulator
**Physical Device:** Always works with valid `google-services.json`
**Emulator:** Requires:
* Google Play Services installed
* Signed in with Google account
* Internet connectivity
#### 4. Send Test Push from Zixflow
1. Log into Zixflow dashboard
2. Navigate to **Messaging** → **Push Notifications**
3. Create test push notification
4. Send to specific user (using `userId` from `identify()`)
***
### Troubleshooting
#### google-services.json Not Found
**Error:** `File google-services.json is missing`
**Solution:**
1. Verify `google-services.json` is in `app/` directory
2. Sync Gradle: **File** → **Sync Project with Gradle Files**
3. Clean and rebuild: **Build** → **Clean Project** → **Rebuild Project**
#### Push Notifications Not Received
**Symptoms:** Notifications sent but not delivered
**Solutions:**
1. Verify FCM Server Key configured in Zixflow dashboard
2. Ensure user is identified: `Zixflow.instance().identify(userId = ...)`
3. Check POST\_NOTIFICATIONS permission granted (Android 13+)
4. Verify device token registered: `Zixflow.instance().registeredDeviceToken`
5. Test on physical device or emulator with Google Play Services
6. Check Firebase console for delivery status
#### Device Token Not Registering
**Symptoms:** `registeredDeviceToken` returns `null`
**Solutions:**
1. Verify `google-services.json` is correct and in `app/` directory
2. Ensure `google-services` plugin is applied
3. Check Google Play Services available on device
4. Verify app package name matches Firebase configuration
5. Check logs for FCM token generation errors
#### Notification Not Displayed
**Symptoms:** Push delivered but not shown
**Solutions:**
1. Check notification channels (Android 8.0+)
2. Verify notification permission granted
3. Ensure app not in "Do Not Disturb" mode
4. Check notification settings for your app
5. Verify custom notification callback isn't blocking display
#### Rich Media Not Loading
**Symptoms:** Images/videos not appearing in notifications
**Solutions:**
1. Verify media URL is accessible from device
2. Check internet connectivity
3. Ensure media format is supported (JPEG, PNG, GIF for images)
4. Check file size (large files may timeout)
***
### Best Practices
1. **Always identify users** before sending push notifications
2. **Request permission thoughtfully** - explain value before requesting
3. **Use ACTIVITY\_PREVENT\_RESTART** for better user experience
4. **Test on multiple devices** and Android versions
5. **Enable debug logging** during development
6. **Monitor metrics** in Zixflow dashboard
7. **Handle permission denial gracefully**
8. **Test deep links thoroughly**
9. **Customize notification appearance** to match app branding
10. **Keep google-services.json secure** - don't commit to version control
***
# Quick Start
Source: https://docs.zixflow.com/documentation/sdk/android/quick-start
Initialize the SDK and send your first event with Android.
### Kotlin Apps
For Kotlin applications using Jetpack Compose or traditional Views:
#### 1. Create Application Class
Create or update your `Application` class:
```kotlin theme={null}
import android.app.Application
import com.zixflow.sdk.Zixflow
import com.zixflow.sdk.ZixflowConfigBuilder
import com.zixflow.sdk.core.util.ZixflowLogLevel
import com.zixflow.messagingpush.ModuleMessagingPushFCM
import com.zixflow.location.ModuleLocation
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Zixflow SDK
val config = ZixflowConfigBuilder(
applicationContext = this,
apiKey = "YOUR_API_KEY"
)
.autoTrackDeviceAttributes(true)
.autoTrackActivityScreens(true)
.trackApplicationLifecycleEvents(true)
.logLevel(ZixflowLogLevel.DEBUG)
// Add push notifications module (optional)
.addZixflowModule(ModuleMessagingPushFCM())
// Add location tracking module (optional)
.addZixflowModule(ModuleLocation())
.build()
Zixflow.initialize(config)
}
}
```
#### 2. Register Application in AndroidManifest.xml
```xml theme={null}
```
#### 3. Replace Configuration Values
* **YOUR\_API\_KEY**: Your Zixflow API key (from Zixflow dashboard)
***
### Java Apps
For Java-based applications:
#### 1. Create Application Class
```java theme={null}
import android.app.Application;
import com.zixflow.sdk.Zixflow;
import com.zixflow.sdk.ZixflowConfig;
import com.zixflow.sdk.ZixflowConfigBuilder;
import com.zixflow.sdk.core.util.ZixflowLogLevel;
import com.zixflow.messagingpush.ModuleMessagingPushFCM;
import com.zixflow.location.ModuleLocation;
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Configure and initialize Zixflow SDK
ZixflowConfig config = new ZixflowConfigBuilder(
this, // applicationContext
"YOUR_API_KEY"
)
.autoTrackDeviceAttributes(true)
.autoTrackActivityScreens(true)
.trackApplicationLifecycleEvents(true)
.logLevel(ZixflowLogLevel.DEBUG)
.addZixflowModule(new ModuleMessagingPushFCM())
.addZixflowModule(new ModuleLocation())
.build();
Zixflow.initialize(config);
}
}
```
#### 2. Register in AndroidManifest.xml
```xml theme={null}
```
***
# Requirements
Source: https://docs.zixflow.com/documentation/sdk/android/requirements
System and tooling requirements for the Zixflow Android SDK.
* **Android**: API 21 (Android 5.0 Lollipop) or higher (`minSdk`)
* **compileSdk / targetSdk**: **36** recommended (required if your dependency graph resolves `androidx.core` 1.17+)
* **Kotlin**: 1.9.0 or higher (recommended)
* **Java**: Java 17 or higher
* **Gradle**: 8.11.1 or higher (required for AGP 8.9)
* **Android Gradle Plugin**: **8.9.1** or higher (required for `compileSdk` 36)
The public [Android example](https://github.com/zixflow/sdk-examples/tree/master/android) uses AGP `8.9.1`, Gradle `8.11.1`, and `compileSdk` / `targetSdk` `36`.
### Feature-specific requirements
| Module | Additional requirements |
| -------------------- | -------------------------------------------------------------------- |
| `messaging-push-fcm` | Firebase project, `google-services` plugin, Firebase Cloud Messaging |
| `location` | Google Play Services Location; runtime location permissions |
***
# Advanced Configuration
Source: https://docs.zixflow.com/documentation/sdk/flutter/advanced-configuration
Advanced Configuration — Zixflow Flutter SDK integration guide.
### Full Configuration Options
```dart theme={null}
final config = ZixflowConfig(
// Required
apiKey: 'YOUR_API_KEY',
// Logging level
logLevel: LogLevel.debug, // none, error, info, debug
// Auto-tracking options
autoTrackDeviceAttributes: true,
trackApplicationLifecycleEvents: true,
// Screen tracking
screenViewUse: ScreenView.all, // all
// Event batching
flushAt: 20, // Send events after 20 tracked
flushInterval: 30, // Or every 30 seconds
// Custom endpoints (advanced)
apiHost: 'custom-api.example.com',
cdnHost: 'custom-cdn.example.com',
// Push notifications (Android click behavior)
pushConfig: PushConfig(
android: PushConfigAndroid(
pushClickBehavior: PushClickBehaviorAndroid.activityPreventRestart,
),
),
// Location tracking
locationConfig: LocationConfig(
trackingMode: LocationTrackingMode.manual,
),
);
await Zixflow.initialize(config: config);
```
### Log Levels
Control SDK logging verbosity:
```dart theme={null}
logLevel: LogLevel.none, // Disable logging
logLevel: LogLevel.error, // Errors only
logLevel: LogLevel.info, // Errors + informational
logLevel: LogLevel.debug, // All messages (development)
```
### Screen Tracking Options
```dart theme={null}
screenViewUse: ScreenView.all, // Send screen events for analytics
```
### Push Click Behavior (Android)
`PushConfig` / `PushClickBehaviorAndroid` apply when the **native Android SDK** displays or handles notification clicks. They do **not** control taps on notifications you show with `flutter_local_notifications` (the Dart path in [Push Notifications](/documentation/sdk/flutter/push-notifications)).
```dart theme={null}
pushConfig: PushConfig(
android: PushConfigAndroid(
pushClickBehavior: PushClickBehaviorAndroid.resetTaskStack,
// or: activityPreventRestart (default), activityNoFlags
),
),
```
### Location Tracking Modes
```dart theme={null}
locationConfig: LocationConfig(
trackingMode: LocationTrackingMode.manual, // App controls capture (default)
// trackingMode: LocationTrackingMode.onAppStart,
// trackingMode: LocationTrackingMode.off,
),
```
See [Location Tracking](/documentation/sdk/flutter/location-tracking) for platform enablement and APIs.
***
# API Reference
Source: https://docs.zixflow.com/documentation/sdk/flutter/api-reference
SDK method reference for the Zixflow Flutter integration.
Import the main package for core APIs, config, and enums:
```dart theme={null}
import 'package:zixflow/zixflow.dart';
```
### Core SDK
#### `Zixflow.initialize({required ZixflowConfig config}): Future`
Initialize the SDK with configuration. Call once before using other APIs.
```dart theme={null}
await Zixflow.initialize(
config: ZixflowConfig(apiKey: 'YOUR_API_KEY'),
);
```
#### `Zixflow.instance`
Singleton instance. Throws `StateError` if the SDK has not been initialized.
#### `Zixflow.instance.identify({required String userId, Map traits}): void`
Identify a user. Provide a unique `userId` and optional traits.
```dart theme={null}
Zixflow.instance.identify(
userId: 'user-123',
traits: {'email': 'user@example.com'},
);
```
#### `Zixflow.instance.clearIdentify(): void`
Clear the current user's identity.
```dart theme={null}
Zixflow.instance.clearIdentify();
```
#### `Zixflow.instance.track({required String name, Map properties}): void`
Track a custom event.
```dart theme={null}
Zixflow.instance.track(
name: 'button_clicked',
properties: {'button_name': 'signup'},
);
```
#### `Zixflow.instance.screen({required String title, Map properties}): void`
Track a screen view.
```dart theme={null}
Zixflow.instance.screen(title: 'HomeScreen');
```
#### `Zixflow.instance.setProfileAttributes({required Map attributes}): void`
Set attributes on the user profile.
```dart theme={null}
Zixflow.instance.setProfileAttributes(
attributes: {'plan': 'premium'},
);
```
#### `Zixflow.instance.setDeviceAttributes({required Map attributes}): void`
Set attributes on the current device.
```dart theme={null}
Zixflow.instance.setDeviceAttributes(
attributes: {'app_version': '2.0.0'},
);
```
#### `Zixflow.instance.registerDeviceToken({required String deviceToken}): void`
Register a device token for push notifications.
```dart theme={null}
Zixflow.instance.registerDeviceToken(deviceToken: token);
```
#### `Zixflow.instance.deleteDeviceToken(): void`
Remove the current device token.
```dart theme={null}
Zixflow.instance.deleteDeviceToken();
```
#### `Zixflow.instance.trackMetric({required String deliveryID, required String deviceToken, required MetricEvent event}): void`
Track push lifecycle metrics. With the Dart FCM + local-notifications integration, call this for **opened** (and optionally **delivered**). See [Push Notification Tracking](/documentation/sdk/flutter/push-notification-tracking).
```dart theme={null}
Zixflow.instance.trackMetric(
deliveryID: '626533406292836846',
deviceToken: token,
event: MetricEvent.opened, // or MetricEvent.delivered / MetricEvent.converted
);
```
`MetricEvent.converted` is for conversion attribution after an open; it is not used by the standard push open/action flow. Action button presses use `track(name: 'Push Notification Action Clicked', ...)`, not `trackMetric`.
### Push Messaging
Native Android helpers for forwarding FCM data messages into the SDK (display and/or delivered tracking). The Dart path in [sdk-examples/flutter](https://github.com/zixflow/sdk-examples/tree/master/flutter) uses `trackMetric` / `track` directly instead of these APIs.
#### `Zixflow.pushMessaging.getRegisteredDeviceToken(): Future`
```dart theme={null}
final token = await Zixflow.pushMessaging.getRegisteredDeviceToken();
```
#### `Zixflow.pushMessaging.onMessageReceived(Map message, {bool handleNotificationTrigger = true}): Future`
Process an FCM data message on Android (displays and/or tracks). On iOS this resolves to `true` without additional work. Use when another FCM listener receives the message and you want the native SDK to handle display/tracking.
```dart theme={null}
await Zixflow.pushMessaging.onMessageReceived(message);
```
#### `Zixflow.pushMessaging.onBackgroundMessageReceived(Map message): Future`
Background helper around `onMessageReceived` (Android).
```dart theme={null}
await Zixflow.pushMessaging.onBackgroundMessageReceived(message);
```
### Location
#### `Zixflow.location.setLastKnownLocation({required double latitude, required double longitude}): void`
```dart theme={null}
Zixflow.location.setLastKnownLocation(
latitude: 37.7749,
longitude: -122.4194,
);
```
#### `Zixflow.location.requestLocationUpdate(): void`
```dart theme={null}
Zixflow.location.requestLocationUpdate();
```
### Types
#### `ZixflowConfig`
```dart theme={null}
ZixflowConfig({
required String apiKey,
LogLevel? logLevel,
bool? autoTrackDeviceAttributes,
bool? trackApplicationLifecycleEvents,
ScreenView? screenViewUse,
String? apiHost,
String? cdnHost,
int? flushAt,
int? flushInterval,
PushConfig? pushConfig,
LocationConfig? locationConfig,
});
```
#### `LogLevel`
```dart theme={null}
enum LogLevel { none, error, info, debug }
```
#### `ScreenView`
```dart theme={null}
enum ScreenView { all }
```
#### `LocationTrackingMode`
```dart theme={null}
enum LocationTrackingMode { off, manual, onAppStart }
```
#### `MetricEvent`
```dart theme={null}
enum MetricEvent { delivered, opened, converted }
```
#### `PushClickBehaviorAndroid`
```dart theme={null}
enum PushClickBehaviorAndroid {
resetTaskStack,
activityPreventRestart,
activityNoFlags,
}
```
#### `PushConfig` / `PushConfigAndroid`
```dart theme={null}
PushConfig(
android: PushConfigAndroid(
pushClickBehavior: PushClickBehaviorAndroid.activityPreventRestart,
),
)
```
#### `LocationConfig`
```dart theme={null}
LocationConfig(
trackingMode: LocationTrackingMode.manual,
)
```
***
# Core Features
Source: https://docs.zixflow.com/documentation/sdk/flutter/core-features
User identification, event tracking, and core SDK features for Flutter.
### User Identification
Identify users to track their activity:
```dart theme={null}
// Identify user with ID only
Zixflow.instance.identify(userId: 'user@example.com');
// Identify user with additional attributes
Zixflow.instance.identify(
userId: 'user@example.com',
traits: {
'first_name': 'John',
'last_name': 'Doe',
'email': 'user@example.com',
'plan': 'premium',
'created_at': DateTime.now().toIso8601String(),
},
);
```
### Event Tracking
Track custom events:
```dart theme={null}
// Simple event
Zixflow.instance.track(name: 'button_clicked');
// Event with properties
Zixflow.instance.track(
name: 'purchase_completed',
properties: {
'product_id': '123',
'product_name': 'Widget',
'price': 29.99,
'currency': 'USD',
},
);
```
### Screen Tracking
Track screen views:
```dart theme={null}
// Manual screen tracking
Zixflow.instance.screen(
title: 'Product Detail',
properties: {
'product_id': '123',
'category': 'Electronics',
},
);
```
#### Automatic Screen Tracking with GoRouter
Enable automatic screen tracking by setting `screenViewUse` in configuration:
```dart theme={null}
final config = ZixflowConfig(
apiKey: 'YOUR_API_KEY',
screenViewUse: ScreenView.all, // Track all screens automatically
);
```
### Profile Attributes
Set user profile attributes:
```dart theme={null}
// Set profile attributes
Zixflow.instance.setProfileAttributes(
attributes: {
'age': 30,
'gender': 'male',
'subscription_status': 'active',
'preferences': {'notifications': true},
},
);
```
### Device Attributes
Set device-specific attributes:
```dart theme={null}
// Set device attributes
Zixflow.instance.setDeviceAttributes(
attributes: {
'app_theme': 'dark',
'notifications_enabled': true,
'preferred_language': 'en',
},
);
```
### User Logout
Clear user identification when they log out:
```dart theme={null}
// Clear identified user
Zixflow.instance.clearIdentify();
```
### Register Device Token
Register device token for push notifications:
```dart theme={null}
Zixflow.instance.registerDeviceToken(deviceToken: 'YOUR_DEVICE_TOKEN');
```
***
# Example Apps
Source: https://docs.zixflow.com/documentation/sdk/flutter/example-apps
Example Apps — Zixflow Flutter SDK integration guide.
### Clone and run
Public examples live in [zixflow/sdk-examples](https://github.com/zixflow/sdk-examples):
```bash theme={null}
git clone https://github.com/zixflow/sdk-examples.git
cd sdk-examples/flutter
flutter pub get
flutter run --dart-define=ZIXFLOW_API_KEY=your_api_key
```
See the [Flutter example README](https://github.com/zixflow/sdk-examples/tree/master/flutter) for Firebase, push, and location notes. Push + action buttons + open/action tracking live in [`lib/push_handlers.dart`](https://github.com/zixflow/sdk-examples/blob/master/flutter/lib/push_handlers.dart).
### What a complete integration covers
| Area | What to implement | Docs |
| -------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Install & initialize | Add `zixflow` from pub.dev, call `Zixflow.initialize` | [Installation](/documentation/sdk/flutter/installation), [Quick Start](/documentation/sdk/flutter/quick-start) |
| Identify & track | `identify`, `track`, `screen`, profile/device attributes | [Core Features](/documentation/sdk/flutter/core-features) |
| Push notifications | FCM + `flutter_local_notifications`, token registration, action buttons, NSE | [Push Notifications](/documentation/sdk/flutter/push-notifications) |
| Push tracking | `trackMetric(opened)`, action clicked; delivered via NSE or manual | [Push Notification Tracking](/documentation/sdk/flutter/push-notification-tracking) |
| Location | Enable module, permissions, `setLastKnownLocation` / `requestLocationUpdate` | [Location Tracking](/documentation/sdk/flutter/location-tracking) |
| Platform setup | iOS/Android Firebase, permissions, optional SPM | [Platform-Specific Setup](/documentation/sdk/flutter/platform-specific-setup) |
| API surface | Full method and type reference | [API Reference](/documentation/sdk/flutter/api-reference) |
### Suggested build order
1. Complete [Platform-Specific Setup](/documentation/sdk/flutter/platform-specific-setup).
2. Initialize the SDK and identify a test user ([Quick Start](/documentation/sdk/flutter/quick-start)).
3. Add event and screen tracking ([Core Features](/documentation/sdk/flutter/core-features)).
4. Enable push (and rich push / action buttons if needed), then verify delivery on a physical device.
5. Optionally enable location.
# Installation
Source: https://docs.zixflow.com/documentation/sdk/flutter/installation
Install and configure the Zixflow Flutter SDK in your project.
The Zixflow Flutter SDK is available on **pub.dev**. You can browse the package and view all available versions here:
**[pub.dev - Zixflow Flutter SDK](https://pub.dev/packages/zixflow)**
### Add Dependency
Add the Zixflow SDK to your `pubspec.yaml`:
```yaml theme={null}
dependencies:
zixflow: ^1.1.5
```
### Install Package
Run the following command:
```bash theme={null}
flutter pub get
```
### Platform-Specific Setup
After adding the dependency, follow platform-specific setup for iOS and Android (see [Platform-Specific Setup](/documentation/sdk/flutter/platform-specific-setup) section).
***
# Introduction
Source: https://docs.zixflow.com/documentation/sdk/flutter/introduction
Overview of the Zixflow Flutter SDK and its modules.
The Zixflow Flutter SDK enables you to send data from your Flutter app to Zixflow, allowing you to track user behavior, send targeted push notifications, and more. The SDK works across iOS and Android platforms.
### SDK Modules
The SDK includes integrated modules:
* **Data Pipelines** (core): User identification, event tracking, and analytics
* **Push Messaging**: Push notifications via Firebase Cloud Messaging (FCM)
* **[Location Tracking](/documentation/sdk/flutter/location-tracking)**: Location-based messaging capabilities
Current SDK version documented here: **1.1.5**.
***
For conceptual background on events, users, and attributes, see [Getting Started with Events](/documentation/events/getting-started).
# Location Tracking
Source: https://docs.zixflow.com/documentation/sdk/flutter/location-tracking
Capture user location for geo-aware messaging with the Zixflow Flutter SDK.
### How it works
The Location module captures location (with user consent) from your app and attaches it to a person’s profile in Zixflow. You can use this data for geo-aware messaging and audience segmentation with more accuracy than IP-based geolocation.
When you identify a person, the SDK includes the latest location in the identify call. The SDK also sends a location update to the person’s activity timeline, which you can use in campaigns and segments. To balance location updates with battery and data usage, the SDK limits location updates (at most once a day)—and only sends that update when the person has moved a meaningful distance since the last update.
The SDK does **not** request location permission on its own—your app must handle the permission flow.
***
### Enable the location module
Location is an optional native module. Enable it on both platforms before calling location APIs.
#### Android
Set the following flag in `android/gradle.properties`:
```properties theme={null}
zixflow_location_enabled=true
```
#### iOS (CocoaPods)
Add the `location` subspec to your `ios/Podfile` (inside the `Runner` target), then run `pod install`:
```ruby theme={null}
pod 'zixflow/location', :path => '.symlinks/plugins/zixflow/ios'
```
#### iOS (Swift Package Manager)
With SPM enabled, set `zixflow_location_enabled=true` in `android/gradle.properties` (the same flag used for Android). The plugin Package.swift reads this flag and includes the location module. Alternatively, set `ZIXFLOW_LOCATION=true` when building:
```bash theme={null}
ZIXFLOW_LOCATION=true flutter build ios
```
If the module is not enabled, location calls no-op and a one-time development warning is logged.
***
### Initialize with location config
Add a `locationConfig` to your `ZixflowConfig`. The `trackingMode` property controls how and when the SDK captures location.
| Option | Type | Default | Description |
| -------------- | ---------------------- | -------- | ----------------------------------------------- |
| `trackingMode` | `LocationTrackingMode` | `manual` | Controls how and when the SDK captures location |
#### Tracking modes
| Mode | Description |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LocationTrackingMode.manual` | Your app controls when location is captured. Call `setLastKnownLocation()` or `requestLocationUpdate()`. Use this when your app already tracks location or you want full control. |
| `LocationTrackingMode.onAppStart` | The SDK captures a one-shot location once per app launch when the app enters the foreground. You can still call the location APIs alongside automatic capture. |
| `LocationTrackingMode.off` | Disables location tracking. Location calls become silent and location is not included in identify calls. |
```dart theme={null}
import 'package:zixflow/zixflow.dart';
final config = ZixflowConfig(
apiKey: 'YOUR_API_KEY',
locationConfig: LocationConfig(
trackingMode: LocationTrackingMode.manual,
),
);
await Zixflow.initialize(config: config);
```
***
### Request location permissions
Add the required platform keys, then request permission at runtime before calling SDK location APIs.
**iOS — `Info.plist`:**
```xml theme={null}
NSLocationWhenInUseUsageDescription
We use your location to personalize your experience.
```
**Android — `AndroidManifest.xml`:**
```xml theme={null}
```
Request permission with a library such as `permission_handler`:
```yaml theme={null}
dependencies:
permission_handler: ^11.3.1
```
```dart theme={null}
import 'package:permission_handler/permission_handler.dart';
Future requestLocationPermission() async {
final status = await Permission.location.request();
if (status.isGranted) {
return true;
} else if (status.isPermanentlyDenied) {
openAppSettings();
}
return false;
}
```
***
### Location APIs
You can call these methods as often as you like. The SDK caches the latest coordinates for profile enrichment, but throttles outbound location updates so you do not overwhelm your workspace.
#### `setLastKnownLocation`
Pass coordinates from your app’s own location system. This does not require the SDK to manage location permissions—your app manages access independently.
```dart theme={null}
import 'package:zixflow/zixflow.dart';
// Pass coordinates from your app's location provider
Zixflow.location.setLastKnownLocation(
latitude: 37.7749,
longitude: -122.4194,
);
```
| Parameter | Type | Description |
| ----------- | -------- | --------------------------------------------------- |
| `latitude` | `double` | Latitude in degrees. Must be between -90 and 90. |
| `longitude` | `double` | Longitude in degrees. Must be between -180 and 180. |
#### `requestLocationUpdate`
Request a one-shot location from the native platform’s location services. Use this if your app does not have its own location system. Your app must request location permission **before** calling this method—the SDK will not prompt the user.
If permission is denied or location services are disabled, the request is ignored (no crash). If a request is already in progress, additional calls are ignored until it completes.
```dart theme={null}
import 'package:zixflow/zixflow.dart';
// After location permission is granted
Zixflow.location.requestLocationUpdate();
```
***
### Profile switch behavior
When you call `Zixflow.instance.clearIdentify()`, the SDK clears cached location data so one person’s location does not carry over to another profile. The next person you identify starts with a clean slate.
Location can persist across app restarts. When your app relaunches, the SDK may restore the cached location so the next `identify()` call includes it automatically.
***
# Platform-Specific Setup
Source: https://docs.zixflow.com/documentation/sdk/flutter/platform-specific-setup
Platform-Specific Setup — Zixflow Flutter SDK integration guide.
### iOS Setup
#### 1. Add GoogleService-Info.plist
1. Download `GoogleService-Info.plist` from Firebase Console
2. Add to `ios/Runner/` directory in Xcode
3. Ensure it's added to target
#### 2. Update Info.plist
Add required permissions to `ios/Runner/Info.plist`:
```xml theme={null}
NSLocationWhenInUseUsageDescription
We use your location to personalize your experience.
UIBackgroundModes
fetch
remote-notification
```
Location keys are only required if you use [Location Tracking](/documentation/sdk/flutter/location-tracking).
#### 3. CocoaPods (Default)
Run pod install:
```bash theme={null}
cd ios
pod install
```
To enable the optional location module with CocoaPods, add the location subspec inside the `Runner` target:
```ruby theme={null}
pod 'zixflow/location', :path => '.symlinks/plugins/zixflow/ios'
```
Then run `pod install` again.
#### 4. Swift Package Manager (Optional)
To use SPM instead of CocoaPods, update `pubspec.yaml`:
```yaml theme={null}
dependencies:
zixflow:
version: ^1.1.5
native_sdk:
ios:
enable-swift-package-manager: true
```
Then run:
```bash theme={null}
cd ios
pod install
```
With SPM, enable location by setting `zixflow_location_enabled=true` in `android/gradle.properties` (same flag as Android). See [Location Tracking](/documentation/sdk/flutter/location-tracking).
***
### Android Setup
#### 1. Add google-services.json
1. Download `google-services.json` from Firebase Console
2. Place in `android/app/` directory
#### 2. Add Google Services Plugin
Prefer the plugins DSL (as in the [Flutter example](https://github.com/zixflow/sdk-examples/tree/master/flutter)).
`android/settings.gradle`:
```gradle theme={null}
plugins {
// ...
id "com.google.gms.google-services" version "4.4.0" apply false
}
```
`android/app/build.gradle`:
```gradle theme={null}
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
id "com.google.gms.google-services"
}
android {
compileSdk 36
compileOptions {
// Required by flutter_local_notifications
coreLibraryDesugaringEnabled true
}
defaultConfig {
// Must match package_name in google-services.json
applicationId "com.yourcompany.app"
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}
```
For Push Notifications capability, `ZX_2BTN`, Notification Service Extension, and App Groups, see [Push Notifications](/documentation/sdk/flutter/push-notifications).
#### 3. Permissions
Add permissions to `android/app/src/main/AndroidManifest.xml`:
```xml theme={null}
```
#### 4. Enable Location (Optional)
To enable location tracking, add to `android/gradle.properties`:
```properties theme={null}
zixflow_location_enabled=true
```
See [Location Tracking](/documentation/sdk/flutter/location-tracking) for tracking modes and APIs.
***
# Push Notification Tracking
Source: https://docs.zixflow.com/documentation/sdk/flutter/push-notification-tracking
Definitive guide to tracking push notification lifecycle events — delivery, open, and action clicks — with the Zixflow Flutter SDK.
**Last Updated:** July 2, 2026\
**Audience:** Client developers integrating Zixflow SDK (Flutter)\
**Purpose:** Track push notification lifecycle events — delivery, open, and action clicks — so Zixflow can measure campaign performance accurately.
See also: [Push Notifications](/documentation/sdk/flutter/push-notifications) and the reference implementation [`push_handlers.dart`](https://github.com/zixflow/sdk-examples/blob/master/flutter/lib/push_handlers.dart).
***
## Why Push Tracking Matters
When Zixflow sends a push notification to a user's device, it records that the notification was **sent**. But it cannot know on its own:
* Did the notification actually **arrive** on the device?
* Did the user **open** it (tap the banner)?
* Did the user tap an **action button** ("Shop Now", "Remind Me")?
Your app reports these three moments back to Zixflow using the SDK. This powers the delivery analytics you see in campaign dashboards — open rates, click rates, and conversion funnels after a push.
***
## Two Ways to Handle a Zixflow Push — and Why Tracking Is Required in Both
Every push can be **Native** or **Custom** in the dashboard — that choice decides the payload shape (see [The Push Payload](#the-push-payload)).
* **Native:** display content lives in FCM `notification` / APNs `aps.alert`; `data` has tracking + routing keys (`Zixflow-Delivery-ID`, `Zixflow-Delivery-Token`, `deeplink_url`, `action_buttons`, `template_id`).
* **Custom:** no `notification` block — the full content is in `data.*` so your app renders with `flutter_local_notifications`.
| | **A. Native (OS-rendered)** | **B. Custom (app-built UI)** |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Dashboard | **Native** | **Custom** |
| Payload | `notification.*` / `aps.alert` carry title/body/image; `data` = tracking + routing | No native block — `data` is the complete payload |
| Who shows the banner | Android/iOS | Your app (`flutter_local_notifications`) |
| Image / color | Native image + color work with zero app code (verify FlutterFire fallback — it can drop `notification.image`) | Fully controllable |
| Large icon / sticky / action buttons | **Not supported** on the Native path | Fully controllable (sticky is Android-only) |
| Tracking | Still required | Same |
**The one thing both paths share: tracking is never automatic.**
* **Path A (Native, app backgrounded/killed):** your `onMessage`/background handler is **not invoked at all** when the payload has a `notification` block and the app is backgrounded. Track `Opened` from `onMessageOpenedApp` / `getInitialMessage`. For `Delivered` on this path, rely on Zixflow's server-side delivery receipt, or see [Tracking Delivery When the Device Is Locked](#tracking-delivery-when-the-device-is-locked).
* **Path B (Custom):** your code runs the moment the push arrives. Track `Delivered` immediately, then track `Opened` / `Push Notification Action Clicked` from your local-notification tap handlers.
**Minimal "OS renders it, I only track" implementation:**
```dart theme={null}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Foreground: OS never auto-shows anything, so if you truly want zero custom UI,
// you show nothing here — just track. (Most apps still build a notification in
// foreground; this snippet is the "OS/FCM-only" extreme for comparison.)
final deliveryId = message.data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = message.data['Zixflow-Delivery-Token'] ?? fcmToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId, deviceToken: deliveryToken, event: MetricEvent.delivered,
);
}
});
// Background/killed tap (notification-block push, OS already rendered it):
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) => _trackOpened(message.data));
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) _trackOpened(initial.data);
```
> **Reference implementation:** `sdk-examples/flutter` ships a runtime **"Custom handling"** toggle that switches live between path A and path B on the same running app.
> **A note on FlutterFire and Path A:** FlutterFire's fallback (`fcm_fallback_notification_channel`) renders title+body only and silently drops `notification.image`, even though genuine stock FCM rendering supports it. If you need images on Path A, build a minimal renderer yourself that reads `notification.title`/`body`/`android.imageUrl` rather than relying on the library's fallback.
***
## The Delivery Lifecycle at a Glance
```
Zixflow Campaign ──sends──▶ Zixflow (via FCM/APNs) ──delivers──▶ Device OS
│
┌──────────┴──────────┐
│ │
App foreground App background
onMessage() onMessageOpenedApp()
│ │
Your app calls: Your app calls:
trackMetric(delivered) trackMetric(opened)
│
User taps action button
│
Your app calls:
track("Push Notification
Action Clicked")
```
Each of these SDK calls results in a delivery report event flowing to the Zixflow backend, updating the campaign's live metrics.
***
## The Push Payload
Zixflow injects two special fields into every push **data** payload. `trackMetric()` requires both.
| Field | Example value | Purpose |
| ------------------------ | ------------------------------ | --------------------------------- |
| `Zixflow-Delivery-ID` | `"626533406292836846"` | Unique ID for this delivery |
| `Zixflow-Delivery-Token` | `"dcFRlDhiRbehM1vg-Lx_yn:..."` | The FCM/APNs token this send used |
The rest of the payload depends on the **rendering mode** chosen in the dashboard:
* **Native (OS-rendered):** display content lives in `notification` / `aps.alert`; `data` carries tracking + routing keys only.
* **Custom (app-rendered):** there is no `notification` block — the full content is in `data` (Android) / top-level keys (iOS).
**Example `data` payload in Custom mode:**
```json theme={null}
{
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"title": "Flash Sale! 70% OFF 🔥",
"body": "Limited time only — grab your deal now!",
"deeplink_url": "https://yourapp.com/sale",
"image_url": "https://cdn.yourapp.com/banner.png",
"large_icon_url": "https://cdn.yourapp.com/icon.png",
"badge": "5",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846",
"template_id": "469935",
"workspace_id": "68be4f797a6494676161e98b"
}
```
**Example `data` payload in Native mode (tracking + routing only):**
```json theme={null}
{
"Zixflow-Delivery-ID": "626533406292836846",
"Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
"deeplink_url": "https://yourapp.com/sale",
"action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]",
"notif_id": "626533406292836846",
"template_id": "469935",
"workspace_id": "68be4f797a6494676161e98b"
}
```
> **Important:** `action_buttons` is a JSON string (not a nested object). Parse it with `json.decode()` before use.
On Android, Native-mode display content lives in `notification.title` / `body` / `image`. On iOS it lives in `aps.alert`. Prefer `message.notification?.x ?? data['x']` so a single renderer covers both modes while the app is in the foreground.
### Complete Key Reference
| Key | Where to read it | Purpose |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `Zixflow-Delivery-ID` / `Zixflow-Delivery-Token` | `message.data` (both modes) | Required for `trackMetric()`. |
| `title` / `body` | Native: `message.notification` · Custom: `data.title` / `data.body` (`message` is an alias of `body`) | Notification text. |
| `image_url` | Native: `notification.image` · Custom: `data.image_url` | Big-picture image. |
| `large_icon_url` | `data.large_icon_url` (Custom) | Small avatar — Custom path only. |
| `deeplink_url` | `data.deeplink_url` (both modes) | Body-tap navigation. |
| `action_buttons` | `data.action_buttons` (JSON string, both modes) | Up to 2 `{name, deeplink}` buttons. Custom path only to *render*. |
| `sound` / `badge` / `sticky` / `icon` / `color` | Native: platform `notification` / `aps` · Custom: matching `data.*` keys | See [Field Support Summary](#field-support-summary-native-os-rendered-vs-custom-handled). Sticky is Android-only. |
| `notif_id` | `data.notif_id` | Diagnostic send-job ID — not `Zixflow-Delivery-ID`. |
| `template_id` | `data.template_id` (both modes) | Dashboard template ID — see [Template-Based Custom Rendering](#template-based-custom-rendering-template_id). |
| `workspace_id` | `data.workspace_id` | Diagnostic workspace ID. |
Demo-only sample-app conventions (not official schema): `data.priority`, `data.analytics_label`, `data.ttl_seconds`.
### Field Support Summary: Native (OS-Rendered) vs. Custom-Handled
| Field | Native (OS-rendered) support |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `image_url` / `notification.image` | Fully supported on stock FCM. FlutterFire's fallback can drop the image — build a minimal renderer if you need it. |
| `color` | Fully supported on Android. |
| `icon` / `sound` | Supported when the resource is bundled in the app. |
| `sticky` | **Not supported** on Native. Custom path only (`data.sticky`). Android-only. |
| `large_icon_url` | **Not supported** on Native. |
| `action_buttons` | **Not supported** on Native — always requires Custom handling. |
| `template_id` | Not a rendering field. Your app uses it to pick a custom renderer. |
***
## Template-Based Custom Rendering (`template_id`)
Every push sent from a dashboard template includes `data.template_id` (both modes). Map known IDs to a dedicated renderer and fall back to the generic field-driven builder.
The [Flutter sample](https://github.com/zixflow/sdk-examples/blob/master/flutter/lib/push_templates.dart) also checks `template_type == "custom"` before looking up `template_id` — that extra gate is a sample-app routing convention. The official payload field is `template_id`.
```dart theme={null}
// push_templates.dart
const String _exampleTemplateId = '469935'; // swap in your own template's ID
Future _showLocalNotification(FlutterLocalNotificationsPlugin plugin, RemoteMessage message) async {
final data = Map.from(message.data);
final title = message.notification?.title ?? data['title']?.toString() ?? 'Notification';
final body = message.notification?.body ?? data['body']?.toString() ?? '';
if (data['template_id']?.toString() == _exampleTemplateId) {
await _showTemplateExampleNotification(plugin, message, data, title, body);
return;
}
// ...generic field-driven notification building continues here
}
Future _showTemplateExampleNotification(
FlutterLocalNotificationsPlugin plugin, RemoteMessage message,
Map data, String title, String body,
) async {
final id = message.hashCode;
final imagePath = await _downloadToTempFile(data['image_url']?.toString(), 'template_img_$id.jpg');
final largeIconPath = await _downloadToTempFile(data['large_icon_url']?.toString(), 'template_icon_$id.jpg');
final soundName = data['sound']?.toString();
final sticky = data['sticky']?.toString() == 'true';
await plugin.show(
id, title, body,
NotificationDetails(
android: AndroidNotificationDetails(
'zixflow_template_example', 'Template Example',
largeIcon: largeIconPath != null ? FilePathAndroidBitmap(largeIconPath) : null,
styleInformation: imagePath != null
? BigPictureStyleInformation(FilePathAndroidBitmap(imagePath), contentTitle: title, summaryText: body)
: BigTextStyleInformation(body),
number: int.tryParse(data['badge']?.toString() ?? ''),
sound: soundName != null ? RawResourceAndroidNotificationSound(soundName) : null,
playSound: soundName != null,
ongoing: sticky, autoCancel: true,
),
iOS: DarwinNotificationDetails(
categoryIdentifier: 'ZX_2BTN',
attachments: imagePath != null ? [DarwinNotificationAttachment(imagePath)] : null,
),
),
payload: jsonEncode(data),
);
}
```
***
## Complete Field Mapping Reference (Custom-Handled UI)
One consolidated builder covering **every** official field from the [Complete Key Reference](#complete-key-reference). Combine with the tracking calls from [The Three Tracking Events](#the-three-tracking-events). Matches `sdk-examples/flutter`.
```dart theme={null}
Future showZixflowNotification(RemoteMessage message) async {
final data = message.data;
final title = message.notification?.title ?? data['title'] ?? 'Notification';
final body = message.notification?.body ?? data['body'] ?? data['message'] ?? '';
final imagePath = await _downloadToTempFile(data['image_url'], 'img.jpg');
final largeIconPath = await _downloadToTempFile(data['large_icon_url'], 'icon.jpg');
final soundName = data['sound'];
final androidSound = (soundName != null && soundName != 'default' && soundName != 'none')
? RawResourceAndroidNotificationSound(soundName)
: null;
final badgeCount = int.tryParse(data['badge'] ?? '');
final sticky = data['sticky'];
final ongoing = sticky == 'true' || sticky == 'until_click' || sticky == 'ongoing';
final noClearOnly = sticky == 'until_swipe' || sticky == 'no_clear';
final priority = data['priority'];
final channelId = priority == 'normal' ? 'zixflow_normal' : 'zixflow_default';
final channelName = priority == 'normal' ? 'Zixflow Notifications (normal priority)' : 'Zixflow Notifications';
final buttons = parseActionButtons(data['action_buttons']);
final androidActions = [
for (var i = 0; i < buttons.length && i < 2; i++)
AndroidNotificationAction('ACTION_$i', buttons[i]['name'] ?? 'Action ${i + 1}', showsUserInterface: true),
];
await FlutterLocalNotificationsPlugin().show(
message.hashCode,
title,
body,
NotificationDetails(
android: AndroidNotificationDetails(
channelId, channelName,
icon: 'ic_notification',
color: const Color(0xFFFA2438),
importance: priority == 'normal' ? Importance.defaultImportance : Importance.high,
priority: priority == 'normal' ? Priority.defaultPriority : Priority.high,
autoCancel: true, ongoing: ongoing,
additionalFlags: noClearOnly ? Int32List.fromList([0x00000020 /* FLAG_NO_CLEAR */]) : null,
sound: androidSound, playSound: androidSound != null,
number: badgeCount,
largeIcon: largeIconPath != null ? FilePathAndroidBitmap(largeIconPath) : null,
styleInformation: imagePath != null
? BigPictureStyleInformation(FilePathAndroidBitmap(imagePath), contentTitle: title, summaryText: body)
: null,
actions: androidActions,
),
iOS: DarwinNotificationDetails(
categoryIdentifier: 'ZX_2BTN',
badgeNumber: badgeCount,
sound: (soundName != null && soundName != 'default') ? '$soundName.caf' : null,
attachments: imagePath != null ? [DarwinNotificationAttachment(imagePath)] : null,
),
),
payload: jsonEncode(data),
);
}
```
***
## The Three Tracking Events
Each interaction maps to a specific SDK call. The event name, parameters, and platform code are listed for each below.
> **Event naming — read this first:** `trackMetric()` used to always send a single generic internal event name, `Report Delivery Event`, for every metric type (delivered/opened/clicked/converted), with the actual status only distinguishable via an internal `metric` property. The SDK now sends the **metric name itself as the event name** — `Delivered`, `Opened`, `Clicked` — so each lifecycle stage is directly filterable/reportable by name in analytics and Journeys, with no code change required on your side (you still call `trackMetric(event: MetricEvent.delivered)` exactly as before; only the resulting event name on the backend changed). If you have older dashboards/segments filtering on the literal string `"Report Delivery Event"`, update them to filter on `"Delivered"` / `"Opened"` / `"Clicked"` instead. Both old and new names are still recognized by the backend, so nothing breaks during the transition — but new events will use the short-form names going forward.
***
### 1. Delivery Confirmed
**Event name (sent to Zixflow):** `Delivered`\
**When to fire:** The moment the push data payload arrives on the device — inside your `onMessage` / `onMessageReceived` / `willPresent` handler.\
**SDK method:** `Zixflow.instance.trackMetric(deliveryID:, deviceToken:, event: MetricEvent.delivered)`\
**What happens:** Zixflow updates the campaign delivery record to `delivered`. No profile event is stored.
**Parameters:**
| Parameter | Required | Description |
| ------------- | -------- | --------------------------------------------------------------------------------------- |
| `deliveryID` | ✅ | Value of `Zixflow-Delivery-ID` from the push payload |
| `deviceToken` | ✅ | Value of `Zixflow-Delivery-Token` from the push payload (or your cached FCM/APNs token) |
| `event` | ✅ | `MetricEvent.delivered` |
```dart theme={null}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final deliveryId = message.data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = message.data['Zixflow-Delivery-Token'] ?? fcmToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.delivered,
);
}
});
```
#### Tracking Delivery When the Device Is Locked
A common gap: **`Delivered` isn't recorded when the push arrives while the phone is locked or the app is backgrounded.** This is expected default OS behaviour.
| Platform | How to reliably track `Delivered` while locked |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Android (Custom mode)** | Zixflow sends Custom-mode pushes as **high-priority data messages** (`android.priority: "high"`). Track `Delivered` in `FirebaseMessaging.onMessage` / the background handler. |
| **Android (Native mode)** | The OS renders it and no app code runs on arrival. Use Zixflow's **server-side delivery receipt**, and track `Opened` on tap. Switch the campaign to Custom mode if you need client-side `Delivered`. |
| **iOS (either mode)** | Add a **Notification Service Extension** with `mutable-content: 1` (already set in Custom mode). Track `Delivered` from the NSE — see [iOS Push Notification Tracking](/documentation/sdk/ios/push-notification-tracking#tracking-delivery-when-the-device-is-locked). |
***
### 2. Notification Opened
**Event name (sent to Zixflow):** `Opened`\
**When to fire:** When the user taps the notification banner. Fire for **both** body taps and action button taps.\
**SDK method:** `Zixflow.instance.trackMetric(deliveryID:, deviceToken:, event: MetricEvent.opened)`\
**What happens:** Zixflow updates the campaign delivery record to `opened`. No profile event is stored.
**Parameters:**
| Parameter | Required | Description |
| ------------- | -------- | ------------------------------------------------------- |
| `deliveryID` | ✅ | Value of `Zixflow-Delivery-ID` from the push payload |
| `deviceToken` | ✅ | Value of `Zixflow-Delivery-Token` from the push payload |
| `event` | ✅ | `MetricEvent.opened` |
```dart theme={null}
// Background tap — app was backgrounded
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final deliveryId = message.data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = message.data['Zixflow-Delivery-Token'] ?? fcmToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.opened,
);
}
});
// Terminated tap — app was closed when user tapped
bool _initialHandled = false;
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null && !_initialHandled) {
_initialHandled = true;
final deliveryId = initial.data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = initial.data['Zixflow-Delivery-Token'] ?? fcmToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.opened,
);
}
}
```
***
### 3. Action Button Clicked
**Event name (sent to Zixflow):** `Push Notification Action Clicked`\
**When to fire:** When the user taps a named action button ("Shop Now", "Try It Free", etc.). Always fire `trackMetric(opened)` first, then fire this event.\
**SDK method:** `Zixflow.instance.track(name: "Push Notification Action Clicked", properties: {...})`\
**What happens:** Zixflow records a `clicked` delivery report and captures which button was tapped for campaign analytics.
> This is a named `track()` call — not `trackMetric()`. There is no `MetricEvent` enum for clicks.
**Properties:**
| Property | Type | Required | Description |
| ------------------------ | ------- | ----------- | ------------------------------------------------------- |
| `Zixflow-Delivery-ID` | string | ✅ | Links this event to the campaign delivery record |
| `action_index` | integer | ✅ | 0-based index of the button tapped (`0` = first button) |
| `action_name` | string | ✅ | Human-readable button label (e.g. `"Shop Now"`) |
| `Zixflow-Delivery-Token` | string | Recommended | Token push was sent to |
| `action_deeplink` | string | Recommended | URL the button navigates to |
| `title` | string | Optional | Notification title |
| `action_id` | string | Optional | OS-level action identifier (e.g. `"ACTION_0"`) |
| `source` | string | Optional | `"local_notification"` or `"web_push"` |
```dart theme={null}
// In FlutterLocalNotificationsPlugin.initialize()
onDidReceiveNotificationResponse: (NotificationResponse response) {
final payload = jsonDecode(response.payload ?? '{}') as Map;
// Always fire opened first
final deliveryId = payload['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = payload['Zixflow-Delivery-Token'] ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId, deviceToken: deliveryToken, event: MetricEvent.opened,
);
}
// Then fire action clicked (only if a button was tapped, not body tap)
if (response.actionId != null) {
final actionIndex = int.tryParse(response.actionId!.replaceAll(RegExp(r'\D'), '')) ?? -1;
final buttons = _parseButtons(payload['action_buttons']);
final actionName = (actionIndex >= 0 && actionIndex < buttons.length)
? buttons[actionIndex]['name'] ?? 'Action ${actionIndex + 1}'
: 'Action ${actionIndex + 1}';
Zixflow.instance.track(
name: 'Push Notification Action Clicked',
properties: {
'Zixflow-Delivery-ID': deliveryId,
'Zixflow-Delivery-Token': deliveryToken,
'action_index': actionIndex,
'action_name': actionName,
'action_deeplink': actionIndex >= 0 ? (buttons[actionIndex]['deeplink'] ?? '') : '',
},
);
}
},
```
***
## Platform-Specific Integration
Flutter is the reference implementation. The pattern is the same for both platforms once FCM / APNs tokens are obtained.
### Step 1 — Register Device Token
```dart theme={null}
import 'package:zixflow/zixflow.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
// On app start, get the FCM token and register it
final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken != null) {
Zixflow.instance.registerDeviceToken(deviceToken: fcmToken);
}
// Keep token fresh — re-register whenever FCM rotates it
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
Zixflow.instance.registerDeviceToken(deviceToken: newToken);
});
```
### Step 2 — Track Delivery (Foreground)
When the app is in the foreground, FCM delivers the message to `onMessage`. Track delivery immediately.
```dart theme={null}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final deliveryId = message.data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = message.data['Zixflow-Delivery-Token'] ?? fcmToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.delivered,
);
} else {
// Fallback: no Zixflow IDs present (non-Zixflow push source)
Zixflow.instance.track(
name: 'Push Notification Delivered',
properties: {
'notification_id': message.messageId ?? '',
'title': message.notification?.title ?? message.data['title'] ?? '',
'body': message.notification?.body ?? message.data['body'] ?? '',
},
);
}
// Show a local notification (since app is in foreground)
_showLocalNotification(message);
});
```
### Step 3 — Track Open (Background / Terminated)
```dart theme={null}
// Background: app was in background, user tapped banner
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_trackOpened(message.data, fcmToken);
_handleDeeplink(message.data['deeplink_url']);
});
// Terminated: app was closed, notification tap launched the app
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) {
_trackOpened(initial.data, fcmToken);
_handleDeeplink(initial.data['deeplink_url']);
}
void _trackOpened(Map data, String? fallbackToken) {
final deliveryId = data['Zixflow-Delivery-ID'] ?? '';
final deliveryToken = data['Zixflow-Delivery-Token'] ?? fallbackToken ?? '';
if (deliveryId.isNotEmpty && deliveryToken.isNotEmpty) {
Zixflow.instance.trackMetric(
deliveryID: deliveryId,
deviceToken: deliveryToken,
event: MetricEvent.opened,
);
} else {
Zixflow.instance.track(
name: 'Push Notification Opened',
properties: {
'notification_id': data['notif_id'] ?? '',
'deeplink': data['deeplink_url'] ?? '',
},
);
}
}
```
### Step 4 — Track Action Button Click (Local Notification Response)
For foreground-received notifications displayed as local notifications, handle taps via `flutter_local_notifications`:
```dart theme={null}
// In FlutterLocalNotificationsPlugin.initialize()
onDidReceiveNotificationResponse: (NotificationResponse response) {
final payload = jsonDecode(response.payload ?? '{}') as Map;
if (response.actionId == null) {
// Body tap → opened
_trackOpened(payload, null);
} else {
// Action button tap → opened + clicked
_trackOpened(payload, null);
final actionIndex = int.tryParse(
response.actionId!.replaceAll(RegExp(r'\D'), '')) ?? -1;
final buttons = _parseButtons(payload['action_buttons']);
final buttonName = (actionIndex >= 0 && actionIndex < buttons.length)
? buttons[actionIndex]['name'] ?? 'Action ${actionIndex + 1}'
: 'Action ${actionIndex + 1}';
Zixflow.instance.track(
name: 'Push Notification Action Clicked',
properties: {
'Zixflow-Delivery-ID': payload['Zixflow-Delivery-ID'] ?? '',
'Zixflow-Delivery-Token': payload['Zixflow-Delivery-Token'] ?? '',
'notification_id': payload['Zixflow-Delivery-ID'] ?? '',
'title': payload['title'] ?? '',
'action_id': response.actionId!,
'action_index': actionIndex,
'action_name': buttonName,
'action_deeplink': _resolveButtonDeeplink(buttons, actionIndex),
'source': 'local_notification',
},
);
}
},
List