# Integration API best practices

> Use these best practices to build a reliable, secure, and efficient integration with Talon.One.

> For the complete documentation index, see [llms.txt](https://docs.talon.one/llms.txt).

## Resilience

Design your integration to keep operating even when Talon.One is temporarily unreachable.
Apply these patterns in order:

1. **Tight timeouts**: Set a short timeout on every Integration API call. Fail fast rather
   than letting requests queue indefinitely.
1. **Bounded retries with exponential backoff and jitter**: On a timeout or a 5xx error,
   retry a limited number of times with increasing delays and randomized jitter to avoid
   thundering herd during recovery.
1. **Circuit breaker**: After a threshold of consecutive failures, open the circuit: stop
   calling Talon.One entirely and switch directly to your fallback. Close the circuit
   again after a recovery period.
1. **Fallback behavior**: Define what your system does when the circuit is open. Fail open
   to a safe default: no promotions, a generic discount, or a cached response. Prefer
   fallback behaviours that never block the checkout process.
1. **Avoid immediate reads after writes**: After updating a customer session, tracking an
   event, or updating a customer profile, avoid immediately calling endpoints that read
   the updated data. Some endpoints read from replica databases, which can take a short
   time to reflect recent writes. Wait for the write request to complete, then wait up to
   one second before making a read request. If the response still reflects the previous
   state, retry with backoff.

## Performance

### Get more from each response

Certain endpoints allow you to customize the response to get additional data from one
call. Use this to **increase performance** in your integration layer.

When using the
[Update customer session](/integration-api#tag/Customer-sessions/operation/updateCustomerSessionV2)
endpoint or
[Update customer profile](/integration-api#tag/Customer-profiles/operation/updateCustomerProfileV2)
endpoint, you can set the `responseContent` property to a number of values, such as
`customerProfile`, `triggeredCampaigns`, `loyalty` and more.

The response returns an extra property for each entity you use in `responseContent`.

:::note Example
Imagine that we must update a session using the
[Update customer session](/integration-api#tag/Customer-sessions/operation/updateCustomerSessionV2)
endpoint and that our workflow requires us to fetch the customer profile data to complete
some logic after this.

In theory, we should use the `Update customer session` and `Get customer profile data`
endpoints.

We can skip the `Get customer profile data` call by setting the `responseContent` property
to `customerProfile` in the `Update customer session` endpoint to get everything we need
in **one call**.
:::

<Tabs defaultValue="customization" values={[
    {label: 'Customized response', value: 'customization'},
    {label: 'Default response', value: 'default'},
    ]}
    >

  <TabItem value="customization">

  Setting `responseContent=["customerProfile","event"]` in the request returns the
  following example payload:

  ```json
  {
    "customerSession": {
      "integrationId": "2354382gy",
      "created": "2021-08-24T14:15:22Z",
      "applicationId": 32
    },
    "customerProfile": {
      "id": 0,
      "created": "2019-08-24T14:15:22Z",
      "integrationId": "string",
      "accountId": 0,
      "closedSessions": 0,
      "totalSales": 0
    },
    "event": {
      "id": 0,
      "created": "2019-08-24T14:15:22Z",
      "applicationId": 0,
      "profileId": "string",
      "type": "string"
    }
  }
  ```

  </TabItem>

  <TabItem value="default">

  Not setting `responseContent` in the request returns the following default example
  payload:

  ```json
  {
    "customerSession": {
      "integrationId": "2354382gy",
      "created": "2021-08-24T14:15:22Z",
      "applicationId": 32
    }
  }
  ```

  </TabItem>
</Tabs>

### Dry requests

Dry requests are test requests that are evaluated by Talon.One but not executed. These
requests are also referred to as _dry runs_.

To mark an [Integration API](/integration-api) request as dry, add the `dry=true` query parameter
to a supported endpoint. No data about the request or its response is stored in
Talon.One's database.

You can also use the `now` query parameter to simulate a request at a specific point in
time. This is useful for testing time-related campaigns, such as a campaign that triggers
on a specific date or time.

Dry requests help you simulate _what if_ scenarios:

- What if the end user adds this product to their cart?
- What if the end user applies this coupon to their order?
- Will a time-related campaign trigger on a specific future date for a specific session?

You can also check the output of a rule without affecting any of the
[analytics data](/docs/product/campaigns/analytics/overview.md) that Talon.One generates.

<Tabs defaultValue="scenario1" values={[
    {label: 'Scenario 1: Product page preview', value: 'scenario1'},
    {label: 'Scenario 2: Reserved coupon at checkout', value: 'scenario2'},
    {label: 'Scenario 3: Time-related campaign', value: 'scenario3'},
    ]}>

  <TabItem value="scenario1">

  1. Your end user is looking at a product page.
  1. You fire a customer session update, pretending that the end user already put this
     item in their cart.
  1. If the API response includes a per-item discount for this cart item, the discounted
     price gets rendered on the page.

  </TabItem>

  <TabItem value="scenario2">

  1. Your end user puts an item in their basket and proceeds to checkout.
  1. Your end user has reserved coupons.
  1. You fire a customer session update to test if this reserved coupon resulted in a
     discount on this order.
  1. If it does, the coupon gets applied automatically and the end user gets a
     notification.

  </TabItem>

  <TabItem value="scenario3">

  1. Your end user is browsing your store.
  1. You fire a customer session update with the `now` parameter set to a future date to
     test if a time-related campaign triggers on the session update for that date.

  </TabItem>
</Tabs>

- **Send the complete cart state on every dry run.** Bundle thresholds, cross-line-item
  rules, and tiered discounts all require the full cart. Do not send deltas.
- **Call dry runs at meaningful cart changes.** Trigger them when items are added or
  coupons are applied, not on every user interaction such as each keystroke or page load.
- **Implement an intelligent caching solution on your side.** Avoid calling the Talon.One
  API on every single page visit.

## Security

### API key management

- **Store API keys immediately on creation.** A key is shown only once: if you lose it,
  you must create a new one.
- **Use separate keys per environment.** Use sandbox keys for testing and live keys only
  in production.
- **Set an expiration date** when creating every key. Talon.One sends email notifications
  to Admin users 30 days, 14 days, and 24 hours before expiry.
- **Rotate keys on a schedule** aligned with your InfoSec policy.
- **Never embed keys in client-side code.** Always call the Integration API from a backend
  environment.

See [Integration API keys](/docs/product/applications/manage-api-keys.md)
and [Authentication](/integration-api#description/authentication).

### User account management

- [Enable 2FA](/docs/product/account/account-settings/set-up-2fa.md) or
  [implement SSO](/docs/product/account/account-settings/set-up-sso.md).
- (Optional)
  [Whitelist IP addresses](/docs/dev/get-started/integration-checklist.md#whitelist-ip-addresses).
- Apply a strict user and role management policy.

See [Manage users](/docs/product/account/account-settings/manage-users.md) and
[manage roles](/docs/product/account/account-settings/manage-roles.md).

## Customer sessions and customer profiles

### Session integrity

- **Serialize updates per session and profile.** Sending parallel updates to the same
  [customer session](/docs/dev/concepts/entities/customer-sessions.md) or profile causes
  409 conflicts and race conditions. Apply updates sequentially from your backend or
  middleware.
- **Consider how to handle profile IDs and session IDs.** Never use a shared integration ID.
  When it comes to profile IDs, consider the trade-offs
  from the [integration checklist](/docs/dev/get-started/integration-checklist.md#handle-customer-profiles-and-sessions).
- **Always close sessions at checkout.** A session in an open state is not committed. Send
  the final session update with `state: closed` at checkout, right before the user
  proceeds to the payment page.

### Customer profile data

- **Identify profiles with stable IDs.** Avoid using email or phone numbers as profile
  Ids.
- **Consider GDPR.** If GDPR is mandatory in your region, integrate the
  [Delete customer's personal data](/integration-api#tag/Customer-profiles/operation/deleteCustomerData)
  endpoint.

## Idempotency

Idempotency ensures that a request only produces its result once, regardless of how
many time this request is sent. This prevents duplicate processing in the case of
connection errors, failures, or retries.

Talon.One supports idempotent requests for certain [Integration API](/integration-api)
`POST` and `PUT`
endpoints. Other request types such as `GET` and `DELETE` are idempotent by definition.

Where supported, the endpoint description contains a note indicating idempotency
support. If you require idempotent processing for an endpoint where it is not yet
supported, contact your Customer Success Manager.

To make processing idempotent, generate a unique idempotency key and include it in the
`Idempotency-Key` header in requests. You can verify that a request was processed
idempotently by checking whether an idempotency key HTTP header
returned in the response. Header examples: `Idempotency-Key`,
`Idempotent-Replayed` (where the value is set to `true`),
`X-Idempotency-Created-At`, `X-Idempotency-Expires-At`, `X-Idempotency-Fingerprint`.

Also, note the following:

- Requests with the `Idempotency-Key` header are logged in the Talon.One access logs.
- Responses for idempotent requests are stored in the database and expire 24 hours after
  the request is sent.
- Idempotency keys are typically UUID keys and should not exceed 255 characters in length.
- By default, idempotency keys are valid for 24 hours after first use.
- Idempotency is not supported for [dry requests](/docs/dev/integration-api/best-practices.md#dry-requests).
- In the case of replay, where a stored response is re-executed, selected response
  headers are preserved.

## Data extraction

Keep Talon.One's [data retention period](../server-infrastructure-and-data-retention.md)
in mind and implement automated raw data extraction from Talon.One for your own in-house
reporting.

## Related pages

- [Integration API overview](/docs/dev/integration-api/overview.md)
- [Integration API reference docs](/integration-api#description/introduction)
- [Integration API keys](/docs/product/applications/manage-api-keys.md)
- [Integration checklist](/docs/dev/get-started/integration-checklist.md)
- [Management API best practices](/docs/dev/management-api/best-practices.md)
