Skip to content
View in Postman (opens in new tab)

Create and update users

post

/users/track

Use this endpoint to record custom events and purchases and update user profile attributes.

Braze processes the data passed through the API at face value, and you should only pass deltas (changing data) to minimize unnecessary data point logging.

Need to update users in bulk?

Use the /users/track/bulk endpoint to send larger batches and reduce request volume.

Track request processing status

Braze processes /users/track requests asynchronously. A successful response means Braze received your request and queued it for processing, but the data might not be on the user profile yet. To confirm when processing finishes, add a group_id to your requests and check the group’s status with the /users/track/status endpoint.

Use request processing status to do the following:

  • Confirm that attribute or subscription updates are on a profile before you trigger a Canvas or campaign that depends on them
  • Confirm that a backfill or import has finished before you launch messaging to the affected users
  • Keep a record that high-priority updates, such as consent changes, finished processing

How it works

  1. Choose a group_id for a set of related requests. For example, use one group_id for every request in a backfill, or a unique group_id for a single request you want to confirm.
  2. Include the group_id at the top level of each /users/track request body. Braze counts each accepted request toward the group.
  3. Call the /users/track/status endpoint with the group_id. When the group’s status is completed, Braze has finished processing every request in the group.

If you send another request with the same group_id after the group is completed, the group’s status returns to processing until Braze finishes processing the new request.

Braze tracks status for each group as a whole. A group is completed when Braze finishes processing all requests in the group, including requests where Braze rejected some objects. Braze lists rejected objects in the errors array of each /users/track response.

Status tracking is available only for the /users/track endpoint. Braze doesn’t track status for requests to the /users/track/bulk endpoint or the /users/track/sync endpoint.

Group ID requirements

A group_id must be 1 to 128 characters and can contain only letters, numbers, periods (.), underscores (_), tildes (~), and hyphens (-).

Group IDs are scoped to a workspace. The same group_id in two workspaces refers to two separate groups.

Use a new group_id for each set of requests you want to track. Reusing a group_id adds requests to the existing group and doesn’t extend its retention period.

Limits and retention

Limit Value
Retention 24 hours

Braze retains group status for 24 hours from the time of the first request with a group_id. After the retention period, the /users/track/status endpoint returns an empty results array for that group_id.
Requests per group 6,000,000
Active groups per workspace 100,000. A group is active until its retention period ends.
/users/track/status rate limit 1,500 requests per minute per workspace. This limit is separate from the /users/track rate limit.

Status tracking errors

Braze can’t track a request’s status in some cases, such as when your workspace has reached its maximum number of active groups. When this happens, the /users/track response is still successful and Braze still processes the request’s attributes, events, and purchases. The response includes an entry in the errors array that describes why Braze isn’t tracking the request:

{
  "message": "success",
  "errors": [
    {
      "type": "request_status_group_full"
    }
  ]
}
Error type Description
invalid_group_id The group_id isn’t a string, is empty, is longer than 128 characters, or contains characters that aren’t allowed. For more information, see Group ID requirements.
request_status_active_group_limit_exceeded Your workspace has reached its limit of active groups. Wait for existing groups to expire, or send the request without a group_id.
request_status_group_full The group has reached its limit of requests. Use a new group_id for additional requests.
request_status_not_tracked Braze couldn’t track the request. For example, the group’s retention period has ended or status tracking isn’t enabled for your workspace.

Frequently asked questions

What happens when multiple profiles with the same email address are found?

If the external_id exists, Braze prioritizes the most recently updated profile with an external ID for updates. If the external_id doesn’t exist, Braze prioritizes the most recently updated profile for updates.

What happens if no profile with the email address exists?

Braze creates a profile and an email-only user and sets the email field to [email protected], as noted in the example request for updating a user profile by email address. Braze does not create an alias.

How do you use /users/track to import legacy user data?

You may submit data through the Braze API for a user who has not yet used your mobile app to generate a user profile. If the user subsequently uses the application, all information following their identification using the SDK is merged with the existing user profile you created using the API call. Any user behavior recorded anonymously by the SDK before identification is lost upon merging with the existing API-generated user profile.

The segmentation tool includes these users regardless of whether they have engaged with the app. If you want to exclude users uploaded using the User API who have not yet engaged with the app, add the Session Count > 0 filter.

How do I avoid creating duplicate user profiles?

Duplicate profiles can occur when a request includes a primary identifier (such as external_id) that doesn’t match any existing profile, alongside an email or phone value that does match an existing profile. Because primary identifiers are used for user lookup, Braze creates a new profile for the unrecognized external_id instead of updating the existing email-only or phone-only profile.

To avoid duplicates:

  • When transitioning users from email-only or phone-only profiles to identified profiles, use the /users/identify endpoint to assign an external_id to the existing profile, rather than sending both to /users/track.
  • If duplicates already exist, merge them using the /users/merge endpoint.

How does /users/track handle duplicate events?

Each event object in the events array represents a single occurrence of a custom event by a user at a designated time. This means each event ingested into Braze has its own event ID, so “duplicate” events are treated as separate, unique events.

How does /users/track handle invalid nested custom attributes?

When a nested custom attribute contains any invalid values (such as invalid time formats or null values), Braze drops all nested custom attribute updates in the request from processing. This applies to all nested structures within that specific attribute. To help ensure successful processing, verify that all values within nested custom attributes are valid before sending.

Are requests to /users/track guaranteed to be processed in order?

When you make multiple separate API calls to /users/track in rapid succession, Braze cannot guarantee that requests are processed in the exact order they are sent or received. This is because Braze uses asynchronous processing to maximize speed and flexibility.

For example, if you send multiple update requests for the same user within seconds of each other—some with null attribute values and others with valid values—the requests containing null values may be processed after requests with valid values, even if sent earlier. This can result in attribute values appearing to revert or not reflect the most recently sent update.

To avoid race conditions when updating user data:

  • Batch updates in a single request: Include all attribute updates for a user in one API call rather than making separate consecutive calls.
  • Add delays between requests: If you must make separate calls for the same user, add a delay (a few seconds) between requests to allow the first request to complete processing before the next one is sent.
  • Avoid overlapping updates for the same field: If two requests update the same attribute with different values, send those updates in one request or separate them with a delay to reduce the chance of out-of-order results.
  • Confirm processing before the next request (beta): Include a group_id in the first request, and wait until the /users/track/status endpoint returns completed before you send the dependent request. For more information, see Track request processing status.

For more information about race conditions and best practices, see Race conditions.

How do I know when Braze has finished processing my request?

Include a group_id in your /users/track requests, then call the /users/track/status endpoint with that group_id. When the group’s status is completed, Braze has finished processing every request in the group. This feature is in beta. For more information, see Track request processing status.

How often should I check a group’s status?

Poll the /users/track/status endpoint at a regular interval, such as every few seconds, and stop when the group’s status is completed. Keep your polling within the endpoint’s rate limit of 1,500 requests per minute per workspace.

Why is my group still processing?

A group stays in processing until Braze finishes processing every accepted request in the group. If a request fails during processing, Braze retries it, and the group stays in processing until the retry succeeds. If you add requests to a group after it’s completed, its status returns to processing. In rare cases, a group can stay in processing until its retention period ends. If a group stays in processing for much longer than your usual processing time, contact Braze Support.

Why does /users/track/status return an empty results array?

The results array is empty when Braze doesn’t find the group in your workspace. This happens when the group’s 24-hour retention period has ended, the group_id doesn’t match the one in your requests, or you sent the requests to a different workspace. It also happens when Braze didn’t track any requests for that group_id. Check each /users/track response for status tracking errors.

Why is my /users/track response slower than I expect?

Successful /users/track calls are usually accepted quickly, but Braze still processes attribute, event, and purchase updates asynchronously. Perceived latency can increase when payloads are large or when network routing to your REST endpoint is slow. If you need a synchronous acknowledgment per user or stricter ordering between calls, see /users/track/sync (limited beta).

Why does /users/track return 429 Too Many Requests?

When your request volume exceeds the rate limit, /users/track returns an HTTP 429 Too Many Requests response. To resume sending requests:

  1. Pause requests for the number of seconds specified in the X-RateLimit-Retry-After response header.
  2. Resume requests at a lower rate to reduce the likelihood of another 429 response.
  3. Combine updates when possible, and follow the request object limit for your account.
  4. Use the API usage dashboard to identify request spikes and 429 response trends. For workspace defaults and other endpoint limits, see API rate limits.

For non-429 responses on supported contracts, use the X-RateLimit-* response headers described in Rate limit headers for Monthly Active Users CY 24-25, Universal MAU, Web MAU, and Mobile MAU to check the remaining requests in the current window.

Why do I get 400 Bad Request with a bad syntax or parse error?

An HTTP 400 with a syntax or parse error typically means the request body is not valid JSON. Common causes include trailing commas, comments inside JSON, single-quoted strings, an extra opening { before the payload, or sending a non-JSON body while the Content-Type header is application/json. Validate payloads with a JSON linter before sending, confirm your HTTP client JSON-encodes objects (rather than concatenating raw strings), and confirm the body is UTF-8 encoded. For other 400 responses (for example, payload size and per-request object limits), refer to Fatal errors & responses and the Endpoint-specific errors table on this page.

Monthly Active Users CY 24-25, Universal MAU, Web MAU, and Mobile MAU

For customers on new pricing, rate limits are enforced at the company level. Customers can set workspace rate limits for hourly limits, but burst limits are still shared between all workspaces.

For customers who have purchased Monthly Active Users CY 24-25, Universal MAU, Web MAU, or Mobile MAU, Braze manages different rate limits on its /users/track endpoint:

  • Hourly rate limits are set according to the expected data ingestion activity on your account, which may correspond to the number of monthly active users you have purchased, industry, seasonality, or other factors.
  • In addition to the hourly limit, Braze enforces a burst limit on the number of requests that can be sent every three seconds.
  • Each request may batch up to 75 updates combined across attribute, event, or purchase objects.

Current limits based on expected ingestion can be found in the dashboard under Settings > APIs and Identifiers > API Usage Dashboard. We may modify rate limits to protect system stability or allow for increased data throughput on your account. Please contact Braze Support or your customer success manager for questions or concerns regarding the hourly or per-second request limit and the needs of your business.

Rate limit headers for Monthly Active Users CY 24-25, Universal MAU, Web MAU, and Mobile MAU

All non-rate-limited (such as non-429) responses contain the following HTTP response headers that indicate the state of the hourly rate limit window to the client. Use these headers to manage your request rate:

Header name Description
X-RateLimit-Limit The number of requests allowed per time period
X-RateLimit-Remaining The approximate number of requests remaining within a window
X-RateLimit-Reset The number of seconds remaining before the current window resets

Note that the RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers are not returned when you hit an HTTP 429 error. When the error occurs, those headers are replaced with an X-RateLimit-Retry-After header that returns an integer indicating the number of seconds before you can start making requests.

You can download the OpenAPI document and use it to validate schemas. To learn how, see Use OpenAPI schemas for request validation.

New Stuff!