Userflow API Reference
Userflow API endpoint:
https://api.userflow.com
With the Userflow REST API you can synchronize your user data with Userflow, and track events directly from your back-end application.
The API is based on REST. Its URLs are resource-oriented and it uses the standard HTTP verbs (GET
, POST
etc.). It receives and responds with JSON. It responds with standard HTTP response codes.
Follow us on Twitter for API announcements.
Quick links
Quick access to endpoints that you'll probably need:
Authentication
Include your API key in the Authorization header prefixed with "Bearer ":
curl https://api.userflow.com/users \
-H 'Authorization: Bearer <api-key>'
Example:
curl https://api.userflow.com/users \
-H 'Authorization: Bearer ak_2snys2ycbvetjeivbzs7m7btfm'
You use API keys to authenticate your API requests. You can view and create API keys in the Userflow web app under Settings -> API. Note that if you have multiple environments (e.g. Production and Staging) that each environment has separate API keys, and that an API key only authorize access to its environment.
API keys grant access to a whole environment in your account! Make sure to keep them safe and secret. Do not put them in your GitHub repo or in your client-side code.
All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail.
Versioning
Override API version (recommended!):
curl https://api.userflow.com/users \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
When we make backwards-incompatible changes to the API, we release new, dated versions. The current (and so far only) version is 2020-01-03
.
All requests will default to using the latest API version, unless you explicitly override the API version with the Userflow-Version
header.
We therefore strongly recommend that you specify your desired version to make sure your integration won't break in the future.
Rate limits
The API employs 2 safeguards against bursts of incoming traffic to help maximize its stability:
- A rate limiter that limits the number of requests per Userflow account per minute. Your account's limit depends on your plan. See Pricing. If you exceed this limit, you may receive error responses with HTTP status code
429 Too Many Requests
. - A concurrency limiter that limits the number of requests that are active at any given time. If the API is at high capacity, it may delay responding to some requests. If requests are queued for more than 15 seconds, you may receive error responses with HTTP status code
503 Service Unavailable
. Problems with concurrency limiting should be very rare. We strive to have enough capacity at all times to avoid (or at least minimize) queuing and never timing out.
Errors
HTTP status codes
The API responds with HTTP status codes in the 2xx range when requests are successful. If a request is invalid due to information provided (e.g. a missing parameter, or), it'll respond with 4xx codes. If Userflow experiences an internal error, it'll respond with 5xx errors.
Code | Meaning |
---|---|
200 | OK - Request was successful |
400 | Bad Request - Invalid query parameters or JSON body. Check the error response for details on how to fix. |
401 | Unauthorized - Missing or invalid API key. |
404 | Not Found - Unrecognized URL. |
405 | Method Not Allowed - The used HTTP method (e.g. GET ) is not allowed for the given URL. |
406 | Not Acceptable - You requested a format that isn't JSON. |
415 | Unsupported Media Type - You probably forgot to add Content-Type: application/json to a POST request. |
429 | Too Many Requests - You have sent too many requests, too quickly, and are being rate limited. See Rate limits. |
500 | Internal Server Error - Something went wrong on Userflow's end. Consider retrying the request, or reach out to Userflow for help. |
503 | Internal Server Error - Service Unavailable - We're temporarily offline for maintenance. Please try again later. |
Error responses
Example error response:
{
"error": {
"code": "invalid_api_key",
"message": "Invalid API key provided: ...",
"doc_url": "https://userflow.com/docs/api"
}
}
Error responses contains an error
object with the following properties:
Key | Description |
---|---|
code |
String - Machine-readable error code. |
message |
String - Human-readable explanation of what went wrong. |
doc_url |
String - URL to the Userflow API docs. |
request_id |
String - Userflow assigns all requests an ID. When you report issues to us, including this request ID will make it easier for us to see what happened. |
Pagination
All top-level API resources that support listing (such as listing users) use the same format for pagination and the same response object.
Pagination query parameters
Listing uses cursor-based pagination through 2 query parameters: limit
and starting_after
.
Key | Description |
---|---|
limit |
Number - Tells Userflow how many items to respond with. Must be between 1 and 100. Defaults to 10. |
starting_after |
String - Tells Userflow to respond with items in the list immediately after and not including the object with the given id. If you want the next page, use the id of the last item in the previous request. It's often easiest to use the list object's next_page_url field though. If starting_after is not set, Userflow will return items from the beginning of the list. |
The list object
Example list object (from
GET /users
):
{
"object": "list",
"data": [
{
"id": "1",
"object": "user",
...
},
...
{
"id": "10",
"object": "user",
...
}
],
"has_more": true,
"url": "/users",
"next_page_url": "/users?starting_after=10"
}
All list endpoints returns an object with the following keys:
Key | Description |
---|---|
object |
String - Represents the object's type. Always list . |
data |
Array - An array containing the actual response objects (e.g. users). No more than limit elements will be included. |
has_more |
Boolean - true if there are more items in the next page, false if this is the last page. |
url |
String - The URL for this list. This will include any pagination parameters, filters or order-by parameters. |
next_page_url |
String - URL to fetch the next page from. Will always be set, even if has_more is false . This is because there may not currently be more items in the list, but some clients may still want to know how to fetch the next page if more items were to arrive later. |
Ordering
Order by single field:
GET /users?order_by=attributes.name
Order by multiple fields:
GET /users?order_by[]=attributes.name&order_by[]=created_at
Use descending order:
GET /users?order_by=-created_at
Some top-level API resources that support listing (such as listing users) can be ordered by one or more fields using a sort_by
query parameter. Each resource will list which fields it can be ordered by.
Key | Description |
---|---|
order_by |
String or array of strings - A single field or a an array of fields to order by. If you include multiple fields, the list will be sorted by the first element first, then the second, and so on. Each field is sorted in ascending order, unless you prefix it with a - (hyphen), in which case it'll be sorted in descending order. |
Expanding objects
Get a user with all their memberships and groups/companies:
curl https://api.userflow.com/users/1?expand=memberships.group \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "1",
"object": "user",
"attributes": {
"name": "Maxie Kris"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"memberships": [
{
"id": "e9b32bd0-63cb-415e-9c4f-477c85b92f97",
"object": "group_membership",
"attributes": {
"role": "admin"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"group": {
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"object": "group",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus"
},
"created_at": "2022-10-17T12:34:56.000+00:00"
},
"group_id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"user": null,
"user_id": "1"
}
]
}
You can ask for objects or lists to be expanded in responses from the API using the expand
query parameter. For example, an event object has a user_id
, which simply contains the string ID of the user the event is related to. By adding ?expand=user
to the request URL, the API will respond with an extra user
key which contains the full user object.
Expansion also works with has-many relationships, such as a user's list of memberships. By default, the memberships
key will be null
(not expanded). If you add ?expand=memberships
, the API will respond with an array of all the user's memberships.
You can expand recursively by specifying nested fields separated with a dot (.
). Example to get memberships and their groups: ?expand=memberships.group
. You can expand to a maximum of 4 levels deep.
You can use the expand
parameter on any endpoint which returns expandable fields, including list, create, and update endpoints.
You can expand multiple objects at once by adding multiple paths in the expand
array. Example: ?expand[]=user&expand[]=group
.
Attributes
Example user object with attributes:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"object": "user",
"attributes": {
"email": "annabelle@example.com",
"email_verified": true,
"name": "Annabelle Terry",
"project_count": 17,
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
}
// ...
}
Some objects, such as users, groups and events, can hold custom attributes in the free-form attributes
dictionaries.
Attribute naming
Attribute names (the keys in attributes
dictionaries) can only consist of a-z, A-Z, 0-9, underscores, dashes and spaces.
We recommend using snake_case
everywhere though.
Attributes' human-friendly display names (e.g. "Signed Up" for signed_up_at
) can also be configured in the Userflow UI.
Attribute data types
We support the following attribute data types:
Name | Description |
---|---|
string |
Represents a string. |
number |
Represents a number (supports both integers and floating point numbers). |
boolean |
Represents either true or false . |
datetime |
Represents a point in time, always stored as ISO 8601 in UTC. |
list |
Represents a list of strings. |
Attribute values and operations
Explicitly specify data type:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"phone": {"set": 12345678, "data_type": "string"}
}
}
Set attribute, unless it has already been set:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"coupon_code": {"set_once": "xyz123"}
}
}
Increment attribute:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"widget_count": {"add": 1},
"total_revenue": {"add": 1234.56}
}
}
Decrement attribute:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"days_left": {"subtract": 1}
}
}
Append a single value to a list:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"foods": {"append": "apple"}
}
}
Append a multiple values to a list:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"foods": {"append": ["apple", "banana"]}
}
}
Prepend values to a list:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"foods": {"prepend": ["apple", "banana"]}
}
}
Remove values from a list:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"foods": {"remove": ["apple", "banana"]}
}
}
Unset attribute:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"remove_me": null
}
}
When updating objects with attributes, the values in the attributes
dictionary can be literal values (string, boolean, number), which just sets the attributes to the given values.
If an attribute value is null
, the attribute will be unset/removed from the user.
You can also specify an operation object instead of a literal value. This operation object can contain the following keys (note that exactly one of set
, set_once
, add
, subtract
, append
, prepend
or remove
must be set):
Key | Description |
---|---|
set |
String/number/boolean - Sets the attribute to this value. This is the same as using literal values, except this way you can explicitly set the data_type , too. |
set_once |
String/number/boolean - Sets the attribute to this value, but only if the user doesn't already have a value for this attribute. |
add |
Number - Adds this value to the attribute's current value. If the user does not have the attribute yet, the given number will be added to 0 . Only works on number attributes. |
subtract |
Number - Same as add , but subtracts the given number instead. To subtract, you either supply a negative number to add , or a positive number to subtract . |
append |
String/list of strings - Adds one or more values to the end of a list type attribute. If the user's attribute value was not set before, it will be initialized to an empty list before the values are appended. Any values already present in the user's attribute value will be ignored (no duplicates are inserted). |
prepend |
String/list of strings - Like append , but inserts the values at the beginning of the list. |
remove |
String/list of strings - Removes one or more values from a list type attribute. If the user's attribute value was not set before, it will be set to an empty list. |
data_type |
String - Explicitly tells Userflow what data type to use. See Attribute data types for possible values. You usually don't have to set this, as Userflow will infer the right data type depending on the values you supply. |
Condition filtering
Using the
condition
parameter when listing users:
curl https://api.userflow.com/users?condition=%7B%22type%22%3A%22attribute%22%2C%22attribute_name%22%3A%22plan%22%2C%22operator%22%3A%22eq%22%2C%22value%22%3A%22Pro%22%7D \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Simple condition filtering on a user attribute:
{
"type": "attribute",
"attribute_name": "experience_level",
"operator": "eq",
"value": "Expert"
}
Simple condition filtering on a group attribute:
{
"type": "attribute",
"attribute_name": "group/plan",
"operator": "eq",
"value": "Pro"
}
Advanced condition matching users who are both on the Pro plan and has created at least 10 widgets:
{
"type": "clause",
"operator": "and",
"conditions": [
{
"type": "attribute",
"attribute_name": "plan",
"operator": "eq",
"value": "Pro"
},
{
"type": "attribute",
"attribute_name": "widget_count",
"operator": "gte",
"value": 10
}
]
}
When listing users and listing groups, you can provide a ?condition
query parameter to filter the list based on attributes.
The condition
parameter must be a JSON-formatted string.
You can perform AND
and OR
logic using multiple conditions combined in a conditions
list.
When referring to attribute names in "type": "attribute"
conditions, the attribute_name
defaults to refer to a user attribute, e.g. experience_level
. If you want to refer to a group attribute, such as plan
, then you must prefix it like group/plan
. If you want to refer to a membership attribute, such as role
, then you must prefix it like group_membership/role
.
Note that condition filtering is intended to find a small number of matching users. If the condition matches more than 10,000 records, the request will be rejected.
We support the following types of conditions.
and - AND clause
{
"type": "clause",
"operator": "and",
"conditions": [
{
// ...condition
},
{
// ...condition
}
]
}
Used to combine multiple child conditions with AND
.
True if all of the child conditions
are true.
between - Is between
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "between",
"value": 10,
"value2": 20
}
True if the given number attribute is between value
and value2
(both inclusive).
contains - Contains
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "contains",
"value": "my value"
}
True if the given string attribute contains the string value
.
empty - Is empty
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "empty"
}
True if the given attribute is either not set, null, an empty string or an empty list.
ends_with - Ends with
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "ends_with",
"value": "my value"
}
True if the given string attribute ends with value
.
eq - Equals / is
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "eq",
"value": "my value"
}
True if the given attribute equals value
.
excludes_all - Does not include all of
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "excludes_all",
"values": ["apple", "banana"]
}
True if the given list attribute does not include all of the given values
(an array of strings).
excludes_any - Does not include at least one of
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "excludes_any",
"value": "my value"
}
True if the given list attribute does not include at least one of the given values
(an array of strings).
false - Is false
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "false"
}
True if the given boolean attribute is false
.
gt - Is greater than
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "gt",
"value": 123
}
True if the given number attribute is greater than value
.
gte - Is greater than or equal to
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "gte",
"value": 123
}
True if the given number attribute is greater than value
.
includes_all - Includes all of
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "includes_all",
"values": ["apple", "banana"]
}
True if the given list attribute includes all of the given values
(an array of strings).
includes_any - Includes at least one of
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "includes_any",
"values": ["apple", "banana"]
}
True if the given list attribute includes at least one of the given values
(an array of strings).
lt - Is less than
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "lt",
"value": 123
}
True if the given number attribute is less than value
.
lte - Is less than or equal to
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "lte",
"value": 123
}
True if the given number attribute is less than or equal to value
.
ne - Is not / does not equal
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "ne",
"value": "my value"
}
True if the given attribute does not equal value
.
not_contains - Does not contain
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "not_contains",
"value": "my value"
}
True if the given string attribute does not contain value
.
not_empty - Has any value / is not empty
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "not_empty"
}
True if the given attribute is set and is neither an empty string, nor null nor an empty list.
or - OR clause
{
"type": "clause",
"operator": "or",
"conditions": [
{
// ...condition
},
{
// ...condition
}
]
}
Used to combine multiple child conditions with OR
.
True if at least one of the child conditions
is true.
starts_with - Starts with
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "starts_with",
"value": "my value"
}
True if the given string attribute starts with value
.
true - Is true
{
"type": "attribute",
"attribute_name": "my_attribute",
"operator": "true"
}
True if the given boolean attribute is true
.
Users
Users are the people using your application. Userflow keeps track of your users, so we can determine which flows to show them, and remember which flows they've already seen.
Your application can either register users with Userflow and update their attributes on the client-side using Userflow.js' userflow.identify()
method, or directly from your back-end using this API.
The user object
Example user object:
{
"id": "34907ae0-24e0-4261-ac31-3c7299a354c0",
"object": "user",
"attributes": {
"email": "annabelle@example.com",
"email_verified": true,
"name": "Annabelle Terry",
"project_count": 17,
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"groups": null,
"memberships": null
}
Key | Description |
---|---|
id |
String - Unique identifier for the user. Should match the ID the user has in your database. |
object |
String - Represents the object's type. Always user . |
attributes |
Object - A map with all the user's attributes. You can add any attributes you want here. |
created_at |
String - ISO 8601 date time representing when the user was created with Userflow. Note that this is not the time the user signed up in your app - use an attribute such as signed_up_at in the attributes object for that. |
groups |
Array of group objects - A list of all the groups the user is a member of. This field is only useful if you're not interested in membership attributes, in which case you should use memberships . Defaults to null , but can be expanded using ?expand=groups . |
memberships |
Array of group membership objects - A list of all the user's group memberships. Memberships can hold attributes that describe the user's role for just this group (e.g. their access level). Each membership object will point to an actual group object. Defaults to null , but can be expanded using ?expand=memberships , or ?expand=memberships.group if you want to expand both the user's memberships and each group. |
Create or update a user
POST /users
curl https://api.userflow.com/users \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"name": "Evelyn Reichert",
"email": "evelyn@example.com",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
}
}'
Response:
{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"object": "user",
"attributes": {
"email": "evelyn@example.com",
"name": "Evelyn Reichert",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00"
}
Creating and updating a user is the same operation. If a user with the same ID doesn't already exist in Userflow, it will be created. If it already exists, the given attributes will be merged into the existing user's attributes.
Request body
Key | Description |
---|---|
id * |
String - Unique identifier for the user. Should match the ID the user has in your database. |
attributes |
Object - A map with attributes to update for the user. You can add any attributes you want here. Existing attributes not included in the request will not be touched. Attribute values can either be supplied as literal values (string, boolean, number) or as operations (set, set_once, add, subtract). See Attributes. |
groups ** |
Array of group objects - A list of groups/companies to update and ensure the user is a member of. This field is simpler to use than memberships , if you don't use membership attributes. |
memberships ** |
Array of group membership objects - A list of group/company memberships to create/update for the user. Memberships can hold attributes that describe the user's role for just this group (e.g. their access level). Each membership object must include an embedded group object with the group's id as a minimum. |
prune_memberships |
Boolean - By default, the API will only update the memberships/groups that's included in the request. Existing memberships that are not included will not be removed, unless you set prune_memberships to true . Only set prune_memberships to true , if the groups or memberships list is set and contains all the groups the user belongs to. When a membership is deleted, the group itself is left intact. |
* Required
** Only one of groups
and memberships
can be set.
Response body
Returns the created/updated user object.
Create or update a user with groups
Create/update user including groups/companies (not using memberships)
curl https://api.userflow.com/users \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"name": "Evelyn Reichert",
},
"groups": [
{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
}
}
]
}'
You can create/update a user and associate it with one or more groups/companies in one go by embedding a list of groups.
If you want to assign attributes to the user's membership of the group (e.g. their access level), use the memberships
key instead (see next section).
Create or update a user with memberships
Create/update user including group/company memberships
curl https://api.userflow.com/users \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
"attributes": {
"name": "Evelyn Reichert",
},
"memberships": [
{
"attributes": {
"role": "admin"
},
"group": {
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
}
}
}
]
}'
You can create/update a user, associate it with one or more groups/companies and assign attributes to the user's membership of each group in one go by embedding a list of memberships.
Memberships can hold attributes that describe the user's role for just this group (e.g. their access level).
Get a user
GET /users/:user_id
curl https://api.userflow.com/users/50c3d110-80f0-47c8-8364-9adfcdb2c9fa \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "50c3d110-80f0-47c8-8364-9adfcdb2c9fa",
"object": "user",
"attributes": {
"email": "nathanial@example.com",
"name": "Nathaniel Harvey",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"groups": null,
"memberships": null
}
Retrieves a user including all their attributes.
URL arguments
Key | Description |
---|---|
user_id * |
String - Unique identifier for the user. Should match the ID the user has in your database. |
* Required
Response body
Returns the user object, if found. Responds with 404 Not Found
if not.
Delete a user
DELETE /users/:user_id
curl https://api.userflow.com/users/4c20f0fb-a835-4f3c-b726-8c34bf538dcc \
-XDELETE \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "4c20f0fb-a835-4f3c-b726-8c34bf538dcc",
"object": "user",
"deleted": true
}
Permanently deletes a user including all their attributes, memberships, events and flow history. It cannot be undone. Groups that the user was a member of will be left intact.
URL arguments
Key | Description |
---|---|
user_id * |
String - Unique identifier for the user. Should match the ID the user has in your database. |
* Required
Response body
Returns an object with a deleted
key on success (or if the user already didn't exist - this call is idempotent).
List users
GET /users
curl https://api.userflow.com/users \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "1",
"object": "user",
"attributes": {
"email": "maxie@example.com",
"name": "Maxie Kris",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"groups": null,
"memberships": null
},
...
{
"id": "10",
"object": "user",
...
}
],
"has_more": true,
"url": "/users",
"next_page_url": "/users?starting_after=10"
}
Filter by email:
curl https://api.userflow.com/users?email=alice@example.com \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Returns a list of your users.
Query parameters
Key | Description |
---|---|
condition |
String - Only include users that match the given condition. Can be used to filter any attribute. See Condition filtering. |
email |
String - Only include users whose email attribute equals this value. |
group_id |
String - Only include users who are members of this group. |
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that users can be ordered by: created_at (note that this is when the user was created with Userflow), attributes.signed_up_at , attributes.last_seen_at , attributes.name . Defaults to created_at . |
segment_id |
String - Only include users in this segment. Find a segment's ID in the Userflow UI via the three-dot menu at the top right of the segment page. |
starting_after |
String - See Pagination. |
Response body
Returns a list object with a data
property that contains an array of user objects.
Groups/companies
Groups are used to group multiple users together. In your business, groups may correspond to e.g. companies, teams or departments.
Like users, groups can have attributes. Events can also be associated with groups.
With groups, you can orchestrate the flows a user sees:
- ...based on behavior of other users in a group. Example: Show a flow if no one in a group has created a "widget" yet.
- ...based on multiple groups that the user is a member of. Example: Show a flow if the group the user is currently working on matches some condition.
Note that the Groups feature is only available in certain plans. Check your Billing page or reach out to us to ask.
The group object
Example group object:
{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"object": "group",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00"
}
Key | Description |
---|---|
id |
String - Unique identifier for the group/company. Should match the ID the group/company has in your database. |
object |
String - Represents the object's type. Always group . |
attributes |
Object - A map with all the group's attributes. You can add any attributes you want here. |
created_at |
String - ISO 8601 date time representing when the group was created with Userflow. Note that this is not the time the group was signed up in your app - use an attribute such as signed_up_at in the attributes object for that. |
memberships |
Array of group membership objects - A list of all the group's memberships. Memberships can hold attributes that describe each user's role for just this group (e.g. their access level). Each membership object will point to an actual user object. Defaults to null , but can be expanded using ?expand=memberships , or ?expand=memberships.user if you want to expand both the group's memberships and each user. |
users |
Array of user objects - A list of all users that are members of this group. This field is only useful if you're not interested in membership attributes, in which case you should use memberships . Defaults to null , but can be expanded using ?expand=users . |
Create or update a group
POST /groups
curl https://api.userflow.com/groups \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
}
}'
Response:
{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"object": "group",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"memberships": null,
"users": null
}
Creating and updating a group is the same operation. If a group with the same ID doesn't already exist in Userflow, it will be created. If it already exists, the given attributes will be merged into the existing group's attributes.
Note that groups can also be created/updated by embedding groups
or memberships
when creating or updating a user.
Request body
Key | Description |
---|---|
id * |
String - Unique identifier for the group. Should match the ID the group has in your database. |
attributes |
Object - A map with attributes to update for the group. You can add any attributes you want here. Existing attributes not included in the request will not be touched. Attribute values can either be supplied as literal values (string, boolean, number) or as operations (set, set_once, add, subtract). See Attributes. |
* Required
Response body
Returns the created/updated group object.
Get a group
GET /groups/:group_id
curl https://api.userflow.com/groups/ab82c312-b3a4-4feb-870c-53dd336f955e \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"object": "group",
"attributes": {
"name": "Acme Inc.",
"billing_plan": "plus",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"memberships": null,
"users": null
}
Retrieves a group including all its attributes.
URL arguments
Key | Description |
---|---|
group_id * |
String - Unique identifier for the group. Should match the ID the group has in your database. |
* Required
Response body
Returns the group object, if found. Responds with 404 Not Found
if not.
Delete a group
DELETE /groups/:group_id
curl https://api.userflow.com/groups/ab82c312-b3a4-4feb-870c-53dd336f955e \
-XDELETE \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"object": "group",
"deleted": true
}
Permanently deletes a group including all their attributes, memberships and events. It cannot be undone. Users who were members of the group will be left intact.
URL arguments
Key | Description |
---|---|
group_id * |
String - Unique identifier for the group. Should match the ID the group has in your database. |
* Required
Response body
Returns an object with a deleted
key on success (or if the group already didn't exist - this call is idempotent).
List groups
GET /groups
curl https://api.userflow.com/groups \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "1",
"object": "group",
"attributes": {
"name": "Acme Inc.",
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"memberships": null,
"users": null
},
...
{
"id": "10",
"object": "group",
...
}
],
"has_more": true,
"url": "/groups",
"next_page_url": "/groups?starting_after=10"
}
Returns a list of groups.
Query parameters
Key | Description |
---|---|
condition |
String - Only include groups that match the given condition. Can be used to filter any attribute. See Condition filtering. |
email |
String - Only include users whose email attribute equals this value. |
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that groups can be ordered by: created_at (note that this is when the group was created with Userflow), attributes.name . Defaults to created_at . |
segment_id |
String - Only include groups in this segment. Find a segment's ID in the Userflow UI via the three-dot menu at the top right of the segment page. |
starting_after |
String - See Pagination. |
user_id |
String - Only include groups that this user is a member of. |
Response body
Returns a list object with a data
property that contains an array of group objects.
Group memberships
Group memberships represent the many-to-many association between users and groups. One user can be a member of multiple groups. One group can have multiple users as members.
Memberships can hold attributes related to the user's role in a specific group. One user may be the Owner of one group and an Admin of another group. One group can have both an Owner and an Admin. You can't put the user's role in either the user's attributes or the group's attributes. Instead, you put it in the membership's attributes.
The group membership object
Example group membership object:
{
"id": "e9b32bd0-63cb-415e-9c4f-477c85b92f97",
"object": "group_membership",
"attributes": {
"role": "admin"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"group": null,
"group_id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"user": null,
"user_id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c"
}
Key | Description |
---|---|
id |
String - Unique identifier for the membership set by Userflow. |
object |
String - Represents the object's type. Always group_membership . |
attributes |
Object - A map with all the membership's attributes. You can add any attributes you want here. |
created_at |
String - ISO 8601 date time representing when the membership was created with Userflow. Note that this is not the time the member user signed up in your app - use an attribute such as signed_up_at in the user's attributes object for that. |
group |
Group object - Defaults to null , but can be expanded using ?expand=group . |
group_id |
String - The ID of group. |
user |
User object - Defaults to null , but can be expanded using ?expand=user . |
user_id |
String - The ID of user. |
Create or update a group membership
Memberships can't be created/updated directly. Instead, create or update a user and embed a list of group membership objects in the memberships
field.
Remove a user from a group
DELETE /group_memberships?user_id=:user_id&group_id=:group_id
curl https://api.userflow.com/group_memberships?user_id=123&group_id=456 \
-XDELETE \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "e9b32bd0-63cb-415e-9c4f-477c85b92f97",
"object": "group_membership",
"deleted": true
}
Removes a single user from a single group. Leaves the user and the group themselves intact.
URL arguments
Key | Description |
---|---|
group_id * |
String - Unique identifier for the group. Should match the ID the group has in your database. |
user_id * |
String - Unique identifier for the user. Should match the ID the user has in your database. |
* Required
Response body
Returns an object with a deleted
key on success (or if the user already wasn't a member of the group - this call is idempotent).
List group memberships
Memberships can't be listed directly. Instead, retrieve a user or a group and use the ?expand
query parameter to expand the user/group's memberships
.
Events
You can track events for users and groups for analytics purposes or to segment and personalize your flows.
Events can be either associated with just a user, just a group, or both a user and a group.
The event object
Example event object:
{
"id": "4738382",
"object": "event",
"attributes": {
"plan_name": "plus",
"plan_price": 199
},
"created_at": "2022-11-29T12:34:56.000+00:00",
"group": null,
"group_id": "ab82c312-b3a4-4feb-870c-53dd336f955e",
"name": "subscription_activated",
"time": "2022-11-29T12:34:56.000+00:00",
"user": null,
"user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791"
}
Key | Description |
---|---|
id |
String - Unique identifier for the event. |
object |
String - Represents the object's type. Always event . |
attributes |
Object - A map of attributes associated with the event. You can add any attributes you want here. |
created_at |
String - ISO 8601 date time representing when the event was created with Userflow. |
group |
Group object - Defaults to null , but can be expanded using ?expand=group . |
group_id |
String - ID of the group the event is associated with (if any). Should match the ID the group has in your database. |
name |
String - Event name (the action the user performed). |
time |
String - ISO 8601 date time representing when the event actually happened. This may differ from created_at if your integration explicitly sends time , or if you have imported historic events. |
user |
User object - Defaults to null , but can be expanded using ?expand=user . |
user_id |
String - ID of the user the event is associated with (if any). Should match the ID the user has in your database. |
Track an event
POST /events
curl https://api.userflow.com/events \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791",
"name": "subscription_activated",
"attributes": {
"plan_name": "plus",
"plan_price": 199
}
}'
Response:
{
"id": "4738382",
"object": "event",
"attributes": {
"plan_name": "plus",
"plan_price": 199
},
"created_at": "2022-11-29T12:34:56.000+00:00",
"group_id": null,
"group": null,
"name": "subscription_activated",
"time": "2022-11-29T12:34:56.000+00:00",
"user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791",
"user": null
}
Stores an event associated with a user and/or a group.
Request body
Key | Description |
---|---|
attributes |
Object - A map of attributes associated with the event. You can add any attributes you want here. Attribute names (the keys) must conform to our Attribute naming rules. |
group_id * |
String - ID of the group the event is associated with (if any). Should match the ID the group has in your database. |
name ** |
String - Name of the action a user has performed. Must conform to our Event naming rules. |
time |
String - ISO 8601 date time representing when the event actually happened. If not set, it'll default to the curren time when the event is received. |
user_id * |
String - ID of the user the event is associated with (if any). Should match the ID the user has in your database. |
* One (or both) of user_id
and group_id
is required.
** Required
Response body
Returns the created event object.
Content
Content is a common term for flows, checklists and launchers.
Note that content is versioned. The actual contents of content objects are found in content versions.
The content object
Example content object:
{
"id": "54bce034-1303-4200-a09a-780a2eee355d",
"object": "content",
"created_at": "2022-10-17T12:34:56.000+00:00",
"draft_version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"draft_version": null,
"name": "Example flow",
"published_version_id": "c8eeb97b-43aa-47d9-bed2-a3d964900977",
"published_version": null,
"type": "flow"
}
Key | Description |
---|---|
id |
String - Unique identifier for the content object. |
object |
String - Represents the object's type. Always content . |
created_at |
String - ISO 8601 date time representing when the content object was first created. |
draft_version_id |
String - The ID of the version currently in the Builder. |
draft_version |
Content version object - Defaults to null , but can be expanded using ?expand=draft_version . |
name |
String - Name given in the Builder. |
published_version_id |
String - The ID of the version currently published in the environment. |
published_version |
Content version object - Defaults to null , but can be expanded using ?expand=published_version . |
type |
String - Indicates what type of content it is. One of: checklist , flow , launcher |
Get a content object
GET /content/:content_id
curl https://api.userflow.com/content/54bce034-1303-4200-a09a-780a2eee355d \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "54bce034-1303-4200-a09a-780a2eee355d",
"object": "content",
"created_at": "2022-10-17T12:34:56.000+00:00",
"draft_version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"draft_version": null,
"name": "Example flow",
"published_version_id": "c8eeb97b-43aa-47d9-bed2-a3d964900977",
"published_version": null,
"type": "flow"
}
Retrieves a single content object by its ID.
URL arguments
Key | Description |
---|---|
content_id * |
String - Unique identifier for the content object. |
* Required
Response body
Returns the content object, if found. Responds with 404 Not Found
if not.
List content
GET /content
curl https://api.userflow.com/content \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "54bce034-1303-4200-a09a-780a2eee355d",
"object": "content",
"created_at": "2022-10-17T12:34:56.000+00:00",
"draft_version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"draft_version": null,
"name": "Example flow",
"published_version_id": "c8eeb97b-43aa-47d9-bed2-a3d964900977",
"published_version": null,
"type": "flow"
},
...
{
"id": "f7ddbadb-4368-4367-b728-b69610450cbd",
"object": "content",
...
}
],
"has_more": true,
"url": "/content",
"next_page_url": "/content?starting_after=f7ddbadb-4368-4367-b728-b69610450cbd"
}
Filter by type:
curl https://api.userflow.com/content?type=checklist \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Returns a list of your content.
Query parameters
Key | Description |
---|---|
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that content objects can be ordered by: created_at , name . Defaults to created_at . |
starting_after |
String - See Pagination. |
type |
String - Only include content objects of the given type. Supported values: checklist , flow , launcher . |
Response body
Returns a list object with a data
property that contains an array of content objects.
Content versions
Content (flows, checklists and launchers) is versioned. A new version is automatically created by Userflow when new edits are made and when publishing content.
The content version object
Example content version object:
{
"id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"object": "content_version",
"created_at": "2022-10-17T12:34:56.000+00:00",
"description": "Optional example description",
"edited_at": "2022-06-29T23:45:67.000+00:00",
"number": 3,
"questions": null,
"tasks": null
}
Key | Description |
---|---|
id |
String - Unique identifier for the content object. |
object |
String - Represents the object's type. Always content_version . |
created_at |
String - ISO 8601 date time representing when the content object was first created. |
description |
String - Optional user-provided description of the version. |
edited_at |
String - ISO 8601 date time representing when the version was last edited (or published, in which case it was frozen at this moment). |
number |
Number - The version number. Version numbers are incremental for each content object. |
questions |
Array of question objects - A list of all questions in the version. Only flows can have questions. Defaults to null , but can be expanded using ?expand=questions . |
tasks |
Array of checklist task objects - A list of all tasks in the version. Only checklists can have tasks. Defaults to null , but can be expanded using ?expand=tasks . |
The question object
Example question object:
{
"id": "d3af62f1-4de3-4c9a-b20c-85b85dce0fef",
"object": "question",
"cvid": "540649a1-9443-4b59-90a2-2262e58744f8",
"name": "Example Question",
"type": "text"
}
Key | Description |
---|---|
id |
String - Unique identifier for the question. Important: The ID of questions change between versions. When a new version is created, all its components are copied from the old version, but with new id values. You can use cvid for an ID that's stable across versions. |
object |
String - Represents the object's type. Always question . |
cvid |
String - Short for Cross-Version ID. An ID that stays the same for the logically same question across versions. Use this if you ever have to refer to a question - not id , since it'll change between versions. |
name |
String - Name given to the question in the Builder. |
type |
String - Type of question. Supported values: multiline_text , multiple_choice , nps , stars , text . |
The checklist task object
Example checklist task object:
{
"id": "1a8152f8-41cf-4e10-9e74-71cafd6dc8b2",
"object": "checklist_task",
"cvid": "ad157908-b182-4585-b0ea-21fdcb2da684",
"name": "Example Task"
}
Key | Description |
---|---|
id |
String - Unique identifier for the task. Important: The ID of tasks change between versions. When a new version is created, all its components are copied from the old version, but with new id values. You can use cvid for an ID that's stable across versions. |
object |
String - Represents the object's type. Always checklist_task . |
cvid |
String - Short for Cross-Version ID. An ID that stays the same for the logically same task across versions. Use this if you ever have to refer to a task - not id , since it'll change between versions. |
name |
String - Name given to the task in the Builder. |
Get a content version
GET /content_versions/:version_id
curl https://api.userflow.com/content_versions/152a63cc-0a49-475a-a8e4-5f89bee94fe6 \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"object": "content_version",
"created_at": "2022-10-17T12:34:56.000+00:00",
"description": "Optional example description",
"edited_at": "2022-06-29T23:45:67.000+00:00",
"number": 3,
"questions": null,
"tasks": null
}
Retrieves a single content version by its ID.
URL arguments
Key | Description |
---|---|
version_id * |
String - Unique identifier for the content version. |
* Required
Response body
Returns the content version object, if found. Responds with 404 Not Found
if not.
List content versions
GET /content_versions
curl https://api.userflow.com/content_versions?content_id=54bce034-1303-4200-a09a-780a2eee355d \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"object": "content_version",
"created_at": "2022-10-17T12:34:56.000+00:00",
"description": "Optional example description",
"edited_at": "2022-06-29T23:45:67.000+00:00",
"number": 3,
"questions": null,
"tasks": null
},
...
{
"id": "c8eeb97b-43aa-47d9-bed2-a3d964900977",
"object": "content_version",
...
}
],
"has_more": true,
"url": "/content_versions",
"next_page_url": "/content_versions?starting_after=c8eeb97b-43aa-47d9-bed2-a3d964900977"
}
Filter by version number:
curl https://api.userflow.com/content_versions?content_id=54bce034-1303-4200-a09a-780a2eee355d&number=3 \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Returns a list of versions for a specific content object.
Query parameters
Key | Description |
---|---|
content_id * |
String - List versions of this content object. Must be supplied - we currently don't support listing versions across content objects. |
limit |
Number - See Pagination. |
number |
Number - Filter by version number to find a specific version. |
order_by |
String or array of strings - See Ordering. Fields that content objects can be ordered by: number . Defaults to number . |
starting_after |
String - See Pagination. |
* Required
Response body
Returns a list object with a data
property that contains an array of content version objects.
Content sessions
A session is a specific user's journey through a specific content object (flow, checklist or launcher). It tracks their progress and records survey answers they provide.
The content session object
Example content session object:
{
"id": "33af21fd-f025-43fc-a492-cf5179b38ee3",
"object": "content_session",
"answers": null,
"completed_at": "2022-10-17T12:35:56.000+00:00",
"completed": true,
"content_id": "54bce034-1303-4200-a09a-780a2eee355d",
"content": null,
"created_at": "2022-10-17T12:34:56.000+00:00",
"group_id": null,
"group": null,
"is_preview": false,
"last_activity_at": "2022-10-17T12:35:56.000+00:00",
"launcher_activated": false,
"progress": "1",
"user_id": "34907ae0-24e0-4261-ac31-3c7299a354c0",
"user": null,
"version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"version": null
}
Key | Description |
---|---|
id |
String - Unique identifier for the content session. |
object |
String - Represents the object's type. Always content_session . |
answers |
Array of content session answer objects - A list of all answers provided by the user to survey questions in the flow. Only flow sessions can have answers. Defaults to null , but can be expanded using ?expand=answers . |
completed_at |
String - ISO 8601 date time representing when the flow/checklist was completed. null if not completed. |
completed |
Boolean - Whether the flow/checklist has been completed. For flows, this means when the user visited a goal step. For checklists, this means that the user completed all tasks. |
content_id |
String - The ID of the content object (i.e. flow/checklist/launcher) that this session is running. |
content |
Content object - Defaults to null , but can be expanded using ?expand=content . |
created_at |
String - ISO 8601 date time representing when the session was first created. |
group_id |
String - The ID of a group, if this session was started under a group. |
group |
Group object - Defaults to null , but can be expanded using ?expand=group . |
is_preview |
Boolean - true if the session was started by a team member previewing a draft version. |
last_activity_at |
String - ISO 8601 date time representing when the user last interacted with the content (e.g. went to the next step, expanded a checklist etc.). |
launcher_activated |
Boolean - If this is a session launcher, will be true if the user has activated the launcher by e.g. hovering over or clicking the target element. |
progress |
Decimal string - A decimal representation of the user's progress through the flow. 1 means that the flow is fully completed (100%). 0.4 means that the user is 40% through. |
user_id |
String - The ID of the user seeing the content. |
user |
User object - Defaults to null , but can be expanded using ?expand=user . |
version_id |
String - The ID of the version that this session is running. |
version |
Content object - Defaults to null , but can be expanded using ?expand=version . |
The content session answer object
Example content session answer object:
{
"id": "23dec03f-3de6-4e89-89e6-eed0785eecd0",
"object": "content_session_answer",
"answer_type": "text",
"answer_value": "I really like your app!",
"created_at": "2022-10-17T12:34:56.000+00:00",
"question_cvid": "540649a1-9443-4b59-90a2-2262e58744f8",
"question_name": "Feedback"
}
Key | Description |
---|---|
id |
String - Unique identifier for the answer. |
object |
String - Represents the object's type. Always content_session_answer . |
answer_type |
String - Represents the data type of the answer. Supported values: number , text , list . |
answer_value |
String - The answer given by the user. If answer_type is text , then answer_value is a string. If answer_type is number , then answer_value is a numeric string. If answer_type is list , answer_value is an array of strings. |
created_at |
String - ISO 8601 date time representing when the answer was provided. |
question_cvid |
String - The Cross-Version ID of the question that was answered (cvid of a question object). |
question_name |
String - The name of the question that was answered (name of a question object). |
Get a content session
GET /content_sessions/:session_id
curl https://api.userflow.com/content_sessions/33af21fd-f025-43fc-a492-cf5179b38ee3 \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "33af21fd-f025-43fc-a492-cf5179b38ee3",
"object": "content_session",
"answers": null,
"completed_at": "2022-10-17T12:35:56.000+00:00",
"completed": true,
"content_id": "54bce034-1303-4200-a09a-780a2eee355d",
"content": null,
"created_at": "2022-10-17T12:34:56.000+00:00",
"group_id": null,
"group": null,
"is_preview": false,
"last_activity_at": "2022-10-17T12:35:56.000+00:00",
"launcher_activated": false,
"progress": "1",
"user_id": "34907ae0-24e0-4261-ac31-3c7299a354c0",
"user": null,
"version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"version": null
}
Retrieves a single content session by its ID.
URL arguments
Key | Description |
---|---|
session_id * |
String - Unique identifier for the content session. |
* Required
Response body
Returns the content session object, if found. Responds with 404 Not Found
if not.
List content sessions
GET /content_sessions
curl https://api.userflow.com/content_sessions?content_id=54bce034-1303-4200-a09a-780a2eee355d \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "33af21fd-f025-43fc-a492-cf5179b38ee3",
"object": "content_session",
"answers": null,
"completed_at": "2022-10-17T12:35:56.000+00:00",
"completed": true,
"content_id": "54bce034-1303-4200-a09a-780a2eee355d",
"content": null,
"created_at": "2022-10-17T12:34:56.000+00:00",
"group_id": null,
"group": null,
"is_preview": false,
"last_activity_at": "2022-10-17T12:35:56.000+00:00",
"launcher_activated": false,
"progress": "1",
"user_id": "34907ae0-24e0-4261-ac31-3c7299a354c0",
"user": null,
"version_id": "152a63cc-0a49-475a-a8e4-5f89bee94fe6",
"version": null
},
...
{
"id": "0a2b7674-ed69-4261-9389-ce68e32077d2",
"object": "content_session",
...
}
],
"has_more": true,
"url": "/content_sessions",
"next_page_url": "/content_sessions?starting_after=0a2b7674-ed69-4261-9389-ce68e32077d2"
}
Check if a specific user has seen a specific flow:
curl https://api.userflow.com/content_sessions?user_id=34907ae0-24e0-4261-ac31-3c7299a354c0&content_id=54bce034-1303-4200-a09a-780a2eee355d&number=3 \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Returns a list of content sessions.
Query parameters
Key | Description |
---|---|
content_id |
String - Only include sessions for this content object. |
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that content objects can be ordered by: created_at , last_activity_at . Defaults to created_at . |
starting_after |
String - See Pagination. |
user_id |
String - Only include sessions for this user. |
Response body
Returns a list object with a data
property that contains an array of content session objects.
Attribute definitions
You can associate any attributes with users/events that you want. Userflow keeps track of all the attributes you use through attribute definitions.
Attribute definitions are automatically created when you send new attributes not seen by Userflow before. It's currently not possible to create/update/delete attribute definitions through this API.
The attribute definition object
Example attribute definition object:
{
"id": "dfb0c3ed-a8d1-4e73-8b95-b307fa322c3a",
"object": "attribute_definition",
"created_at": "2022-10-17T12:34:56.000+00:00",
"data_type": "datetime",
"description": "When user first signed up in your app",
"display_name": "Signed Up",
"name": "signed_up_at",
"scope": "user"
}
Key | Description |
---|---|
id |
String - Unique identifier for the attribute definition. |
object |
String - Represents the object's type. Always attribute_definition . |
created_at |
String - ISO 8601 date time representing when attribute definition was first created. |
data_type |
String - Indicates what type of values can be used for the attributes. See Attribute data types for possible values for custom attributes. Userflow-internal attributes can use the following data types (these attributes are not writable through the API): checklist_task , content , content_session , content_version , flow_step , random_ab , random_number . |
description |
String - A human-readable description of what the attribute is used for. Can be changed in the Userflow Dashboard. |
display_name |
String - A human-readable name for the attribute. Used in most Userflow UI when referring to attributes. Can be changed in the Userflow Dashboard. |
name |
String - The key used when setting the attribute value through the attributes object in e.g. Create or update a user. Cannot be changed once an attribute definition is created. |
scope |
String - Defines which objects this attribute can be associated with. Supported values: event , group , group_membership , user . |
List attribute definitions
GET /attribute_definitions
curl https://api.userflow.com/attribute_definitions \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "dfb0c3ed-a8d1-4e73-8b95-b307fa322c3a",
"object": "attribute_definition",
"created_at": "2022-10-17T12:34:56.000+00:00",
"data_type": "datetime",
"description": "When user first signed up in your app",
"display_name": "Signed Up",
"name": "signed_up_at",
"scope": "user"
},
...
{
"id": "f6293417-f262-4ff6-88b7-3afce56a0de1",
"object": "attribute_definition",
...
}
],
"has_more": true,
"url": "/attribute_definitions",
"next_page_url": "/attribute_definitions?starting_after=f6293417-f262-4ff6-88b7-3afce56a0de1"
}
Filter by scope:
curl https://api.userflow.com/attribute_definitions?scope=user \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Filter by event names:
curl https://api.userflow.com/attribute_definitions?event_name[]=flow_started&event_name[]=flow_ended \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Returns a list of your attribute definitions.
Query parameters
Key | Description |
---|---|
event_name |
String or array of strings - Only include attribute definitions used in the given event(s). |
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that attribute definitions can be ordered by: created_at , display_name , name . Defaults to display_name . |
scope |
String - Only include attribute definitions with the given scope. Supported values: event , group , group_membership , user . |
starting_after |
String - See Pagination. |
Response body
Returns a list object with a data
property that contains an array of attribute definition objects.
Event definitions
You can track any custom event with Userflow that you want. Userflow keeps track of all the events you use through event definitions.
Event definitions are automatically created when you track new events not seen by Userflow before. It's currently not possible to create/update/delete attribute definitions through this API.
For tracking events, see Events.
The event definition object
Example event definition object:
{
"id": "9cf1de05-9a3a-4719-b8a9-75316a5f86ce",
"object": "event_definition",
"created_at": "2022-10-17T12:34:56.000+00:00",
"description": "A Userflow flow was started",
"display_name": "Flow Started",
"name": "flow_started"
}
Key | Description |
---|---|
id |
String - Unique identifier for the event definition. |
object |
String - Represents the object's type. Always event_definition . |
created_at |
String - ISO 8601 date time representing when event definition was first created. |
description |
String - A human-readable description of what the event means. Can be changed in the Userflow Dashboard. |
display_name |
String - A human-readable name for the event. Used in most Userflow UI when referring to events. Can be changed in the Userflow Dashboard. |
name |
String - Used as the name field in event objects. Cannot be changed once an event definition is created. |
Event naming
Event names must consist only of a-z, A-Z, 0-9, underscores, dashes and spaces. We recommend using snake_case
everywhere though. Events' human-friendly display names (e.g. "Subscription Activated" for subscription_activated
) can also be configured in the Userflow UI.
We recommend using event names consisting of a noun and a past-tense verb. Check out this great event naming guide by Segment.
List event definitions
GET /event_definitions
curl https://api.userflow.com/event_definitions \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "9cf1de05-9a3a-4719-b8a9-75316a5f86ce",
"object": "event_definition",
"created_at": "2022-10-17T12:34:56.000+00:00",
"description": "A Userflow flow was started",
"display_name": "Flow Started",
"name": "flow_started"
},
...
{
"id": "1821de25-316f-4b9a-b0e6-52e45f62a012",
"object": "event_definition",
...
}
],
"has_more": true,
"url": "/event_definitions",
"next_page_url": "/event_definitions?starting_after=1821de25-316f-4b9a-b0e6-52e45f62a012"
}
Returns a list of your event definitions.
Query parameters
Key | Description |
---|---|
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that event definitions can be ordered by: created_at , display_name , name . Defaults to display_name . |
starting_after |
String - See Pagination. |
Response body
Returns a list object with a data
property that contains an array of event definition objects.
Webhook subscriptions
You can create webhook subscriptions to be notified when certain events happen in your Userflow account.
When e.g. a user is created or a user event is tracked, Userflow will send a POST
request to a URL of your choosing. Often this URL would hit your own servers, so your back-end can react to the webhook.
The webhook subscription object
Example webhook subscription object:
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"api_version": "2020-01-03",
"created_at": "2022-10-17T12:34:56.000+00:00",
"disabled": false,
"topics": ["user", "event"],
"url": "https://example.com/hooks/userflow"
}
You create webhook subscription objects to subscribe to notifications.
Key | Description |
---|---|
id |
String - Unique identifier for the webhook subscription. |
object |
String - Represents the object's type. Always webhook_subscription . |
api_version |
String - The API version notifications will be sent to url with. See Versioning. |
created_at |
String - ISO 8601 date time representing when the webhook subscription was created. |
disabled |
boolean - Used to temporarily make Userflow stop sending notifications (when set to true ). |
secret |
String - The subscription's secret, used to generate webhook signatures. Only returned at creation (POST /webhook_subscriptions ). |
topics |
Array of strings - The webhook topics your subscription will be notified of. |
url |
String - The URL Userflow should send POST requests to when there are new notifications. |
The webhook notification object
Example webhook notification object for when an event is tracked:
{
"id": "ec3a21f3-88fe-43e8-8711-cfc803aaad54",
"object": "webhook_notification",
"api_version": "2020-01-03",
"created_at": "2022-11-29T12:34:56.000+00:00",
"data": {
"object": {
"id": "4738382",
"object": "event",
"attributes": {
"plan_name": "plus",
"plan_price": 199
},
"created_at": "2022-11-29T12:34:56.000+00:00",
"name": "subscription_activated",
"time": "2022-11-29T12:34:56.000+00:00",
"user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791"
}
},
"topic": "event.tracked.subscription_activated"
}
Example webhook notification object for when a user is updated:
{
"id": "c30d70fd-eb53-4132-bc99-90792677497b",
"object": "webhook_notification",
"api_version": "2020-01-03",
"created_at": "2022-11-29T12:34:56.000+00:00",
"data": {
"object": {
"id": "4738382",
"object": "user",
"attributes": {
"email": "annabelle@example.com",
"email_verified": true,
"name": "Annabelle Terry",
"project_count": 17,
"signed_up_at": "2022-09-29T12:34:56.000+00:00"
},
"created_at": "2022-10-17T12:34:56.000+00:00",
"groups": null,
"memberships": null
},
"previous_attributes": {
"email_verified": false,
"project_count": 14
},
"updated_attributes": {
"email_verified": true,
"project_count": 17
}
},
"topic": "user.updated"
}
Userflow creates webhook notifications when certain things happen in your account (e.g. a user is created), and sends them to your webhook subscriptions.
Key | Description |
---|---|
id |
String - Unique identifier for the webhook notification. |
object |
String - Represents the object's type. Always webhook_notification . |
api_version |
String - The API version of the webhook subscription this notification was sent for. See Versioning. |
created_at |
String - ISO 8601 date time representing when the webhook notification was created. |
data |
Object - Holds details about the notification. |
data.object |
Object - The object of the notification. E.g. for the user.created topic, data.object will be a user object, and for the event.tracked.<name> topic it'll be an event object. |
data.previous_attributes |
Object - Only set in <object>.updated topic notifications. Contains the old values of all changed attributes as they were before the change that caused this notification. |
data.updated_attributes |
Object - Only set in <object>.updated topic notifications. Contains the new values of all changed attributes. |
topic |
String - The notification's webhook topic. |
Webhook topics
Webhook subscriptions only receive notifications for topics they subscribe to.
Topics are namespaced. If you subscribe to e.g. user
, you get'll all user-related notifications such as user.created
and user.updated
.
You can also use *
to subscribe to all topics.
Topic | Description |
---|---|
* |
Matches ALL topics. |
event |
Matches all event-related topics. |
event.tracked |
When any event is tracked. |
event.tracked.<name> |
When a <name> event is tracked. You can use any of name value from your event definitions. Example: event.tracked.flow_started |
group |
Matches all group-related topics. |
group.created |
When a new group is created with Userflow. |
group.updated |
When an existing group is updated. |
user |
Matches all user-related topics. |
user.created |
When a new user is created with Userflow. |
user.updated |
When an existing user is updated. |
Receiving webhooks
Example webhook notification request headers (what Userflow sends to your server):
POST /hooks/userflow
Host: your.app.com
Content-Type: application/json; charset=utf-8
User-Agent: Userflow/1.0 (https://userflow.com/docs/webhooks)
Userflow-Signature: t=1578424823,v1=5cb0c1733224ad819f93dcf4b902cd714c83afd4e9e95412f4bc6cd1b94a3aac
Example webhook notification request body (what Userflow sends to your server):
{
"id": "ec3a21f3-88fe-43e8-8711-cfc803aaad54",
"object": "webhook_notification",
"api_version": "2020-01-03",
"created_at": "2022-11-29T12:34:56.000+00:00",
"data": {
"object": {
"id": "4738382",
"object": "event",
"attributes": {
"plan_name": "plus",
"plan_price": 199
},
"created_at": "2022-11-29T12:34:56.000+00:00",
"name": "subscription_activated",
"time": "2022-11-29T12:34:56.000+00:00",
"user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791"
}
},
"topic": "event.tracked.subscription_activated"
}
Userflow will send a POST
request to your webhook subscription's url
every time there's a new notification that matches your subscription's topics
.
The request body is a webhook notification object.
We strongly recommend that you verify the signature header to avoid fake requests from third parties.
If you handle the webhook successfully, you must return a 2xx
(e.g. 200
) status code to tell Userflow that everything is in order. If Userflow does not receive a 2xx response status code within a timeout of 15 seconds, Userflow will retry the request using exponential backoff. Userflow will give up after 3 days.
If your server has to perform time-consuming operations, we recommend that you enqueue it to run in the background, and acknowledge the webhook with a 200 OK
response immediately. This is to avoid the webhook request from timing out and then being re-delivered unnecessarily again later.
Even though webhooks are sent almost immediately by Userflow, we do not guarantee the order of events. A new user may be created, then shortly after be updated. Your app should expect possibly receiving the user.updated
notification for that user before the user.created
notification.
Webhook signatures
Example
Userflow-Signature
header:
Userflow-Signature: t=1578424823,v1=5cb0c1733224ad819f93dcf4b902cd714c83afd4e9e95412f4bc6cd1b94a3aac
Userflow includes a Userflow-Signature
header to let you verify that webhook notifications are in fact coming from Userflow and not a third party.
The Userflow-Signature
header also includes a timestamp, which mitigates replay attacks, where an attacker intercepts a valid payload and signature, and then re-transmits it later. This timestamp is also part of the signed payload, which means an attacker can't change the timestamp without also invalidating the signature. If a timestamp is too old (e.g. > 5 minutes), we recommend that you ignore the request. We recommend using Network Time Protocol (NTP) to ensure that your server’s clock is accurate (Userflow uses NTP, too).
You need the secret
returned when creating your webhook subscription to verify signatures. Secrets belong to subscriptions, meaning if your account has multiple subscriptions then they'll each have their own unique secret.
The Userflow-Signature
header is on the form t=<timestamp>,v1=<signature>
. <timestamp>
is a UNIX timestamp (in seconds) representing when the signature was generated. Userflow generates a new signature every time we attempt to deliver a notification (including on retries). <signature>
is an HMAC signature. We use v1
as key for the signature to able to support new signature schemes in the future. See example on the right.
Verifying signatures
Example JavaScript implementation:
// This function will throw if the signature is invalid
function verifyUserflowSignature(body, header, secret) {
// Make sure body is a string (not a buffer)
body = Buffer.isBuffer(body) ? body.toString('utf8') : body
// Reject if timestamp is more than 5 minutes ago
let tolerance = 300 // seconds
// Extract header information
let t
let signature
header.split(',').forEach(part => {
let [k, v] = part.split('=')
if (k === 't') {
t = v
} else if (k === 'v1') {
signature = v
}
})
if (!t) {
throw new Error('Userflow-Signature header check failed: Missing t')
}
if (!signature) {
throw new Error(
'Userflow-Signature header check failed: Missing v1 signature'
)
}
// Verify timestamp age
let timestampAge = Math.floor(Date.now() / 1000) - t
if (timestampAge > tolerance) {
throw new Error(
`Userflow-Signature header check failed: Timestamp is more than ${tolerance} seconds ago`
)
}
// Compare signatures
let signedPayload = t + '.' + body
let expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex')
if (signature !== expectedSignature) {
throw new Error(
'Userflow-Signature header check failed: Incorrect signature'
)
}
// Signature is OK!
}
Example of using
verifyUserflowSignature
in Express.js handler:
const secret = 'whsec_...'
// Make sure the handler gets the body as a raw string
const parser = bodyParser.raw({type: 'application/json'})
app.post('/hooks/userflow', parser, (request, response) => {
// Verify signature
try {
verifyUserflowSignature(
request.body,
request.headers['userflow-signature'],
secret
)
} catch (e) {
return response.status(400).json({error: e.message})
}
// TODO: Do your thing with the notification
let notification = JSON.parse(request.body)
// Reply 200 OK to tell Userflow everything is good
return response.json({ok: true})
})
Here's how to verify that the Userflow-Signature
header is valid:
- Obtain the header value, the raw request body (as a raw string, not a parsed JSON object), and your webhook subscription
secret
. - Extract the timestamp and signature from the header: Split the header by
,
, then split each pair by=
, then get the timestamp from the pair with keyt
and the signature from the pair with keyv1
. - If the timestamp is more than e.g. 300 seconds (5 minutes) in the past, then reject the request.
- Build the signed payload by concatenating the timestamp, a period (
.
), and the raw request body (as a string). - Calculate the expected signature by computing an HMAC with the SHA256 hash function, using the webhook subscription
secret
as the key, and the signed payload (from step 4) as the message. - Compare the actual signature (from step 2) with the expected signature (from step 5). Reject the request if they don't match.
Create a webhook subscription
POST /webhook_subscriptions
curl https://api.userflow.com/webhook_subscriptions \
-XPOST \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"api_version": "2020-01-03",
"url": "https://example.com/hooks/userflow",
"topics": ["user", "event"]
}'
Response:
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"api_version": "2020-01-03",
"created_at": "2022-10-17T12:34:56.000+00:00",
"disabled": false,
"secret": "whsec_56nfnf5isvf5pldjcyesd4rxeq",
"topics": ["user", "event"],
"url": "https://example.com/hooks/userflow"
}
Creates a new webhook subscription.
Notifications will immediately begin sending to url
for all events occurring going forward. See Receiving webhooks.
Request body
Key | Description |
---|---|
api_version |
String - The API version notifications will be sent to url with. See Versioning. |
topics |
Array of strings* - The webhook topics your subscription will be notified of. |
url |
String* - The URL Userflow should send POST requests to when there are new notifications. |
* Required
Response body
Returns the created webhook subscription object.
Get a webhook subscription
GET /webhook_subscriptions/:webhook_subscription_id
curl https://api.userflow.com/webhook_subscriptions/72a63cf6-91bf-4260-8258-a45db633d611 \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"api_version": "2020-01-03",
"created_at": "2022-10-17T12:34:56.000+00:00",
"disabled": false,
"topics": ["user", "event"],
"url": "https://example.com/hooks/userflow"
}
Retrieves a webhook subscription.
URL arguments
Key | Description |
---|---|
webhook_subscription_id * |
String - ID of the webhook subscription to retrieve. |
* Required
Response body
Returns the webhook subscription object, if found. Responds with 404 Not Found
if not.
Update a webhook subscription
PATCH /webhook_subscriptions/:webhook_subscription_id
curl https://api.userflow.com/webhook_subscriptions \
-XPATCH \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03' \
-H 'Content-Type: application/json' \
-d '{
"disabled": true
}'
Response:
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"api_version": "2020-01-03",
"created_at": "2022-10-17T12:34:56.000+00:00",
"disabled": true,
"secret": "whsec_56nfnf5isvf5pldjcyesd4rxeq",
"topics": ["user", "event"],
"url": "https://example.com/hooks/userflow"
}
Updates an existing webhook subscription.
URL arguments
Key | Description |
---|---|
webhook_subscription_id * |
String - ID of the webhook subscription to update. |
* Required
Request body
Key | Description |
---|---|
api_version |
String - The API version notifications will be sent to url with. See Versioning. |
topics |
Array of strings - The webhook topics your subscription will be notified of. |
url |
String - The URL Userflow should send POST requests to when there are new notifications. |
disabled |
boolean - Used to temporarily make Userflow stop sending notifications. If you set this to true , notifications will immediately stop being sent. |
Response body
Returns the updated webhook subscription object.
Delete a webhook subscription
DELETE /webhook_subscriptions/:webhook_subscription_id
curl https://api.userflow.com/webhook_subscriptions/c8783643-7f16-4a02-b736-4f5a327f8231 \
-XDELETE \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"deleted": true
}
Permanently deletes a webhook subscription. It cannot be undone.
Webhook notifications will immediately stop being sent to url
.
URL arguments
Key | Description |
---|---|
webhook_subscription_id * |
String - ID of the webhook subscription to delete. |
* Required
Response body
Returns an object with a deleted
key on success (or if the webhook subscription already didn't exist - this call is idempotent).
List webhook subscriptions
GET /webhook_subscriptions
curl https://api.userflow.com/webhook_subscriptions \
-H 'Authorization: Bearer <api-key>' \
-H 'Userflow-Version: 2020-01-03'
Response:
{
"object": "list",
"data": [
{
"id": "c8783643-7f16-4a02-b736-4f5a327f8231",
"object": "webhook_subscription",
"api_version": "2020-01-03",
"created_at": "2022-10-17T12:34:56.000+00:00",
"disabled": false,
"topics": ["user", "event"],
"url": "https://example.com/hooks/userflow"
},
...
{
"id": "0a55b4be-b327-4a2b-a66f-16d1f7bf6519",
"object": "webhook_subscription",
...
}
],
"has_more": true,
"url": "/webhook_subscriptions",
"next_page_url": "/webhook_subscriptions?starting_after=0a55b4be-b327-4a2b-a66f-16d1f7bf6519"
}
Returns a list of your webhook subscriptions.
Query parameters
Key | Description |
---|---|
limit |
Number - See Pagination. |
order_by |
String or array of strings - See Ordering. Fields that webhook subscriptions can be ordered by: created_at , url . Defaults to created_at . |
starting_after |
String - See Pagination. |
Response body
Returns a list object with a data
property that contains an array of webhook subscription objects.