NAV Navbar

Userflow API Reference

Userflow API endpoint:

https://api.getuserflow.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.

Authentication

Include your API key in the Authorization header prefixed with "Bearer ":

curl https://api.getuserflow.com/users \
  -H 'Authorization: Bearer <api-key>'

Example:

curl https://api.getuserflow.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.

API keys grant access to your whole 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.getuserflow.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.

Errors

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 succesful
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.
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.

Example error response:

{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key provided: ...",
    "doc_url": "https://getuserflow.com/docs/api"
  }
}

Error responses contain of 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.

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.

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": "2019-09-29T12:34:56.000+00:00"
  },
  "created_at": "2019-10-17T12:34:56.000+00:00"
}
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.

Create or update a user

POST /users

curl https:/api.getuserflow.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": "2019-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": "2019-09-29T12:34:56.000+00:00"
  },
  "created_at": "2019-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 (see below). Attribute names (the keys) must conform to our Attribute naming rules.

* Required

Response body

Returns the created/updated user object.

Attribute values and operations

Explicitly specify data type:

{
  "id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
  "attributes": {
    "phone": {"set": 12345678, "data_type": "string"}
  }
}

Set attribute, only if not already set:

{
  "id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
  "attributes": {
    "coupon_code": {"set_once": "xyz123"}
  }
}

Increment attributes:

{
  "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}
  }
}

Remove attribute:

{
  "id": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",
  "attributes": {
    "remove_me": null
  }
}

The values in the attributes object 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 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 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.
data_type String - Explicitly tells Userflow what data type to use. Can be one of: string, boolean, number, datetime. You usually don't have to set this, as Userflow will infer the right data type depending on the values you supply.

Get a user

GET /users/:user_id

curl https:/api.getuserflow.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": "2019-09-29T12:34:56.000+00:00"
  },
  "created_at": "2019-10-17T12:34:56.000+00:00"
}

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.getuserflow.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, events and flow history. It cannot be undone.

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.getuserflow.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": "2019-09-29T12:34:56.000+00:00"
      },
      "created_at": "2019-10-17T12:34:56.000+00:00"
    },
    ...
    {
      "id": "10",
      "object": "user",
      ...
    }
  ],
  "has_more": true,
  "url": "/users",
  "next_page_url": "/users?starting_after=10"
}

Filter by email:

curl https:/api.getuserflow.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
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 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.
starting_after String - See Pagination.

Response body

Returns a list object with a data property that contains an array of user objects.

Events

You can track events for users for analytics purposes or to segment and personalize your flows.

The event object

Example event object:

{
  "id": "4738382",
  "object": "event",
  "attributes": {
    "plan_name": "plus",
    "plan_price": 199
  },
  "created_at": "2019-11-29T12:34:56.000+00:00",
  "name": "subscription_activated",
  "time": "2019-11-29T12:34:56.000+00:00",
  "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.
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_id String - ID of the user the event is associated with. Should match the ID the user has in your database.

Track an event

POST /events

curl https:/api.getuserflow.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": "2019-11-29T12:34:56.000+00:00",
  "name": "subscription_activated",
  "time": "2019-11-29T12:34:56.000+00:00",
  "user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791"
}

Stores an event associated with a user.

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.
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. Should match the ID the user has in your database.

* Required

Response body

Returns the created event object.

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": "2019-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. Supported values for custom attributes: string, boolean, number, datetime. Userflow-internal attributes can use the following data types (these attributes are not writable through the API): checklist_task, flow, flow_session, flow_step, flow_version.
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, user.

Attribute naming

You can attach any attributes you want to users and events in the free-form attributes objects/dictionaries.

Attribute names (the keys) must consist only 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.

List attribute definitions

GET /attribute_definitions

curl https:/api.getuserflow.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": "2019-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.getuserflow.com/attribute_definitions?scope=user \
  -H 'Authorization: Bearer <api-key>' \
  -H 'Userflow-Version: 2020-01-03'

Filter by event names:

curl https:/api.getuserflow.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, 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": "2019-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.getuserflow.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": "2019-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": "2019-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:

{
  "id": "ec3a21f3-88fe-43e8-8711-cfc803aaad54",
  "object": "webhook_notification",
  "api_version": "2020-01-03",
  "created_at": "2019-11-29T12:34:56.000+00:00",
  "data": {
    "object": {
      "id": "4738382",
      "object": "event",
      "attributes": {
        "plan_name": "plus",
        "plan_price": 199
      },
      "created_at": "2019-11-29T12:34:56.000+00:00",
      "name": "subscription_activated",
      "time": "2019-11-29T12:34:56.000+00:00",
      "user_id": "c2dfad13-ee7d-4fb2-9b4f-d450716b4791"
    }
  },
  "topic": "event.tracked.subscription_activated"
}

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.
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
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://getuserflow.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": "2019-11-29T12:34:56.000+00:00",
  "data": {
    "object": {
      "id": "4738382",
      "object": "event",
      "attributes": {
        "plan_name": "plus",
        "plan_price": 199
      },
      "created_at": "2019-11-29T12:34:56.000+00:00",
      "name": "subscription_activated",
      "time": "2019-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 receives non-2xx codes (or if your server doesn't respond at all), 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:

  1. Obtain the header value, the raw request body (as a raw string, not a parsed JSON object), and your webhook subscription secret.
  2. 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 key t and the signature from the pair with key v1.
  3. If the timestamp is more than e.g. 300 seconds (5 minutes) in the past, then reject the request.
  4. Build the signed payload by concatenating the timestamp, a period (.), and the raw request body (as a string).
  5. 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.
  6. 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.getuserflow.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": "2019-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.getuserflow.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": "2019-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.getuserflow.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": "2019-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.getuserflow.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.getuserflow.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": "2019-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.