--- title: "Integration Pitfalls" slug: "integration-pitfalls" description: "Covers best practices for Insider One API integration, including 200 OK responses, event TTL, skip_hook, identifiers, array updates, and validation." updated: 2026-10-13T12:02:28Z published: 2026-10-13T12:02:28Z canonical: "academy.insiderone.com/integration-pitfalls" --- > ## Documentation Index > Fetch the complete documentation index at: https://academy.insiderone.com/llms.txt > Use this file to discover all available pages before exploring further. # Integration Pitfalls An API request can be accepted even when some of its data isn’t written. Some request settings also have defaults that can unexpectedly change user profiles or trigger live journeys. Use the checks on this page when building and monitoring an Insider One API integration. The examples focus on the [Upsert User Data API,](/v1/docs/upsert-user-data-api) but the same principle applies whenever an API accepts a request before completing its processing. > [!NOTE] > Do not use the HTTP status code as the only measure of integration success. Check the response body and verify the resulting data. ## A 200 OK response does not always mean an event was written A `200 OK` response confirms that Insider One accepted a structurally valid request. It does not guarantee that every event in the request was written to a user profile. One common cause is the event's Time to Live (TTL). Insider One calculates TTL from the event's original `timestamp`, not from the date on which the API receives the event. If the timestamp falls outside the event's retention period, the event is not written to the User Profile. For example, assume that a custom event has a TTL of 90 days: ```json {  "event_name": "store_visit",  "timestamp": "2025-01-01T10:00:00Z" } ``` If you send this event after its TTL has expired, the request can still receive `200 OK`, but the event will not be available on the user's profile. Before sending historical events: 1. Check the TTL of each event type. 2. Compare the event's original `timestamp` with the current date. 3. Exclude expired events from the import. 4. Verify a sample of accepted events on the User Profiles page. > [!NOTE] > The Attributes and Events ingestion graph can show that Insider One received an expired event. This does not mean that the event was retained on the User Profile. ## Always inspect the response body The [Upsert User Data API](/v1/docs/upsert-user-data-api) can return `200 OK` with a `fail` block. This means the request format was accepted, but one or more user records failed validation or processing. For example: ```json {  "data": {    "successful": {},    "fail": {      "count": 1,      "errors": {        "users.0.identifiers.required": [          "either insider_id or identifiers must be specified"        ]      }    }  } } ``` Do not mark a request as successful based only on its HTTP status. Your integration should inspect: - `data.successful.count` for successfully accepted user records. - `data.fail.count` for failed user records. - `data.fail.errors` for the affected fields and validation messages. Treat any response with a `fail.count` greater than `0` as a partial or complete processing failure. Log the error details, correct the invalid data, and retry only the affected records. A successful response typically resembles: ```json {  "data": {    "successful": {      "count": 1    },    "fail": {}  } } ``` ## Set skip_hook explicitly for historical data The `skip_hook` parameter controls whether imported data can trigger active Architect journeys and data streams. Refer to the table below to understand how it behaves: | Value | Behavior | | --- | --- | | `false` | The data can trigger journeys and data streams. | | `true` | The data is written without triggering journeys or data streams. | | Omitted | Treated as `false`. | > [!WARNING] > Insider One also applies `skip_hook` automatically for high-volume S3 and data warehouse ingestion. Sending `skip_hook: false` does not guarantee that journeys will fire for every record in a very large import. If you omit `skip_hook` while importing historical events, those events can trigger live journeys. For example, suppose an active journey uses the `purchase` event as its **On Event** starter. If you import historical purchase events without setting `skip_hook` to `true`, eligible users can enter the journey and receive its messages. Use the following structure for a historical import: ```json {  "skip_hook": true,  "users": [    {      "identifiers": {        "email": "sample@example.com"      },      "events": [        {          "event_name": "purchase",          "timestamp": "2026-06-10T12:00:00Z",          "event_params": {            "event_group_id": "ORDER-123",            "unit_sale_price": 49.99,            "currency": "USD",            "quantity": 1          }        }      ]    }  ] } ``` Set the flag explicitly instead of relying on its default: - Use `"skip_hook": true` for historical imports and backfills that must not trigger live flows. - Use `"skip_hook": false` only when the data should trigger eligible journeys and data streams. > [!NOTE] > `skip_hook` controls triggers. It does not override TTL or make an expired event eligible for storage. > [!NOTE] > **Consent sync loops**: `"skip_hook": true` doesn’t stop webhooks for attribute updates that suppression processing makes afterward. If you sync consent in both directions, limit your Attribute Stream to the attributes you need, and skip write-backs that don’t change a value. ## Put identifiers only in the identifiers object Fields used to identify a user must be placed inside the `identifiers` object. Sending the same field only as an attribute does not make it an identifier for the request. The following structure is **incorrect** when `email` is intended to identify the user: ```json {  "users": [    {      "attributes": {        "email": "sample@example.com",        "language": "en_US"      }    }  ] } ``` Use this structure instead: ```json {  "users": [    {      "identifiers": {        "email": "sample@example.com"      },      "attributes": {        "language": "en_US"      }    }  ] } ``` An identifier can also appear as an attribute when the API supports storing that field on the profile. However, the value used for identity resolution must still be present under `identifiers`. Before sending a request: 1. Confirm which fields are enabled as identifiers in Identity Resolution Management. 2. Place those fields under `users[].identifiers`. 3. Verify that multiple identifiers in the same user object belong to the same person. 4. Use `insider_id` when you need to target a known Insider One profile directly. > [!NOTE] > Each user object must also contain data to write, such as an `attributes` object or an `events` object. An identifiers-only user object is not a valid profile update. ## Do not mix append and overwrite behavior in one request Standard array attributes are appended by default. You can overwrite their existing values by using `not_append` or `append`. ### Append example ```json {  "users": [    {      "identifiers": {        "email": "sample@example.com"      },      "attributes": {        "custom": {          "favorite_categories": [            "electronics"          ]        }      }    }  ] } ``` If the profile already contains: ```json [  "fashion",  "home" ] ``` The resulting array contains the existing and newly submitted values: ```json [  "fashion",  "home",  "electronics" ] ``` ### Overwrite example ```json {  "users": [    {      "identifiers": {        "email": "sample@example.com"      },      "not_append": true,      "attributes": {        "custom": {          "favorite_categories": [            "electronics"          ]        }      }    }  ] } ``` The resulting array is: ```json [  "electronics" ] ``` The append or overwrite selection applies to all standard array attributes covered by the setting. You cannot append one standard array attribute while overwriting another in the same request. If different arrays require different behaviors: 1. Put the arrays that should be appended in one request. 2. Put the arrays that should be overwritten in a separate request. 3. Verify the resulting values on a test profile before running a bulk update. If both `append` and `not_append` are present, `append` takes priority. Use one option consistently to avoid ambiguous payloads. > [!NOTE] > This rule concerns standard array attributes. Array of Objects attributes use action-based operations such as `add`, `merge`, `replace`, and `remove`. ## Pre-launch checklist Before enabling an API integration in production: - Validate historical event timestamps against each event's TTL. - Parse the full response body, including the `successful` and `fail` blocks. - Treat `200 OK` with a non-empty `fail` block as a failed or partially failed operation. - Set `skip_hook` explicitly for every historical import. - Place identity fields under `identifiers`. - Do not send an identifiers-only user object. - Separate append and overwrite operations when arrays need different behavior. - Test with a minimal single-user request. - Confirm the resulting attributes and events on the User Profile. - Log failed records with enough information to correct and retry them. ## Related - [Upsert User Data](/upsert-user-data-api.md) - [Upsert User Data API Integration Wizard](/upsert-user-data-api-integration-wizard.md) - [Data Retention](/data-retention.md) - [How Identifiers Work in Insider One](/how-identifiers-work-in-insider-one.md) - [Manage Array of Objects Attributes via the Upsert API](/manage-array-of-objects-attributes-via-the-upsert-api.md)