Integration Pitfalls

Prev Next

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, but the same principle applies whenever an API accepts a request before completing its processing.

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:

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

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 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:

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

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

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:

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

skip_hook controls triggers. It does not override TTL or make an expired event eligible for storage.

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:

{
  "users": [
    {
      "attributes": {
        "email": "sample@example.com",
        "language": "en_US"
      }
    }
  ]
}

Use this structure instead:

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

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

{
  "users": [
    {
      "identifiers": {
        "email": "sample@example.com"
      },
      "attributes": {
        "custom": {
          "favorite_categories": [
            "electronics"
          ]
        }
      }
    }
  ]
}

If the profile already contains:

[
  "fashion",
  "home"
]

The resulting array contains the existing and newly submitted values:

[
  "fashion",
  "home",
  "electronics"
]

Overwrite example

{
  "users": [
    {
      "identifiers": {
        "email": "sample@example.com"
      },
      "not_append": true,
      "attributes": {
        "custom": {
          "favorite_categories": [
            "electronics"
          ]
        }
      }
    }
  ]
}

The resulting array is:

[
  "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.

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.