> For the complete documentation index, see [llms.txt](https://resources.atriptech.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://resources.atriptech.com/api-document/product-guides/extensions-and-integrations/webhook-overview/l2b-state-change-notification.md).

# L2B State Change Notification

Receive L2B policy state changes through a customer Webhook endpoint.

Atlas sends this notification when an L2B policy state changes.

{% hint style="warning" %}
Webhook delivery is best effort. Use `x-l2b-*` API response headers as the real-time source of truth.

Atlas does not guarantee automatic retries or eventual delivery after a failed delivery.
{% endhint %}

Human-readable notification times use Singapore Time, `SGT (UTC+08:00)`.

### Delivery scope

L2B is evaluated independently for each client, carrier, and Action.

Atlas sends notifications for these transitions on rules with online traffic protection enabled:

| Transition          | Meaning                                        |
| ------------------- | ---------------------------------------------- |
| `NORMAL → WARNING`  | Usage is approaching the configured limit.     |
| `WARNING → CONTROL` | Real-time airline requests are now controlled. |
| `CONTROL → WARNING` | Control is released, but usage remains high.   |
| `WARNING → NORMAL`  | Usage has returned to the normal range.        |

`ORDER` is an exception. Its state changes are observed and notified, but `order.do` is not degraded or rejected by L2B control.

### Endpoint requirements

Provide an Atlas-accessible HTTPS endpoint.

| Requirement      | Value                             |
| ---------------- | --------------------------------- |
| Method           | `POST`                            |
| Content type     | `application/json; charset=UTF-8` |
| Encoding         | UTF-8                             |
| Success response | Exact HTTP `200 OK`               |
| Response body    | Optional and ignored              |
| Timestamp format | ISO 8601 with a UTC offset        |

Atlas treats only HTTP `200` as successful. Statuses such as `201`, `202`, and `204` are unsuccessful.

Persist the event or enqueue it reliably before returning `200`. Process longer-running actions asynchronously.

### Event structure

Every L2B event uses the `ClientNotification` envelope:

```json
{
  "cid": "TAC00001",
  "notificationId": "7F34D609B1F84A43B82D238F80904E26",
  "status": -1,
  "type": "l2b.state_changed",
  "data": {}
}
```

| Field            | Type    | Description                                     |
| ---------------- | ------- | ----------------------------------------------- |
| `cid`            | string  | Client code.                                    |
| `notificationId` | string  | Unique notification ID. Use it for idempotency. |
| `status`         | integer | Always `-1`. It is not an L2B policy state.     |
| `type`           | string  | Always `l2b.state_changed`.                     |
| `data`           | object  | State-change details.                           |

#### State-change data

| Field                  | Type    | Required | Description                                               |
| ---------------------- | ------- | -------- | --------------------------------------------------------- |
| `eventId`              | string  | Yes      | State-change event ID. It equals `notificationId`.        |
| `clientCode`           | string  | Yes      | Client code. It equals `cid`.                             |
| `carrier`              | string  | Yes      | Two-letter airline code, such as `MU`.                    |
| `action`               | string  | Yes      | L2B Action, such as `VERIFY` or `GET_OFFERS`.             |
| `previousState`        | string  | Yes      | Previous policy state: `NORMAL`, `WARNING`, or `CONTROL`. |
| `currentState`         | string  | Yes      | New policy state: `NORMAL`, `WARNING`, or `CONTROL`.      |
| `currentL2b`           | string  | Yes      | Current effective L2B.                                    |
| `l2bLimit`             | string  | Yes      | Configured L2B limit.                                     |
| `usagePercent`         | string  | Yes      | Limit usage percentage, without `%`.                      |
| `requestCount`         | integer | Yes      | Requests included in the current window.                  |
| `paidOrderCount`       | integer | Yes      | Paid orders in the current window.                        |
| `windowMinutes`        | integer | Yes      | Rolling-window length in minutes.                         |
| `windowStart`          | string  | Yes      | Window start time with a UTC offset.                      |
| `windowEnd`            | string  | Yes      | Window end and evaluation time with a UTC offset.         |
| `releaseUsagePercent`  | string  | No       | CONTROL release threshold, without `%`.                   |
| `estimatedReleaseTime` | string  | No       | Estimated control-release time.                           |
| `transitionReason`     | string  | No       | Atlas diagnostic reason code.                             |
| `title`                | string  | No       | Display-ready English title.                              |
| `summary`              | string  | No       | Display-ready English summary.                            |
| `notice`               | string  | No       | Detailed English notice.                                  |

Metric fields use strings to preserve decimal precision. Optional fields may be omitted or `null`. Ignore unknown fields for forward compatibility.

`title`, `summary`, and `notice` are display fields. Do not use them for program logic.

### Example event

```json
{
  "cid": "TAC00001",
  "notificationId": "7F34D609B1F84A43B82D238F80904E26",
  "status": -1,
  "type": "l2b.state_changed",
  "data": {
    "eventId": "7F34D609B1F84A43B82D238F80904E26",
    "clientCode": "TAC00001",
    "carrier": "MU",
    "action": "VERIFY",
    "previousState": "WARNING",
    "currentState": "CONTROL",
    "currentL2b": "83.25",
    "l2bLimit": "80",
    "usagePercent": "104.06",
    "requestCount": 333,
    "paidOrderCount": 4,
    "windowMinutes": 1440,
    "windowStart": "2026-09-08T10:00:00+08:00",
    "windowEnd": "2026-09-09T10:00:00+08:00",
    "releaseUsagePercent": "90",
    "estimatedReleaseTime": "2026-09-10T10:15:00+08:00",
    "transitionReason": "CONTROL_CONFIRMED",
    "title": "L2B Control Activated: MU / VERIFY",
    "summary": "Real-time airline requests are now restricted (WARNING -> CONTROL)",
    "notice": "L2B control is now active for MU / VERIFY. Requests in this scope will not be sent to the airline. Where possible, Atlas returns a cached response. Cached data can be less current or accurate. If no suitable cached response exists, Atlas rejects the request. Control is released after usage remains below 90% of the configured limit for the required confirmation period. The estimated release time is 2026-09-10T10:15:00+08:00. This estimate assumes no additional paid orders. New paid orders can bring release forward. Requests processed during control are excluded from the L2B calculation. In an API response, x-l2b-state: DEGRADED indicates a cached result. x-l2b-state: REJECTED indicates no fallback was available."
  }
}
```

### Handle retries and ordering

Use `notificationId` as the idempotency key. Return `200` for an event already processed.

Events can be duplicated, delayed, missing, or out of order. Maintain any local state by `clientCode + carrier + action`. Use `windowEnd` to prevent an older event from replacing a newer state.

Use `previousState` and `currentState` for program logic. Do not parse `title`, `summary`, or `notice`.

`estimatedReleaseTime` is an estimate only. New paid orders or later evaluations can change it.

### Check API response headers

Webhook events report policy transitions. API response headers report each request's actual result.

| `x-l2b-state` | Meaning                                                 |
| ------------- | ------------------------------------------------------- |
| `NORMAL`      | The request is not under L2B control.                   |
| `WARNING`     | The request remains real-time during the warning state. |
| `DEGRADED`    | CONTROL returned a non-real-time fallback result.       |
| `REJECTED`    | CONTROL could not return a fallback result.             |

Always record `x-l2b-current`, `x-l2b-limit`, `x-l2b-window-minutes`, and `x-l2b-state`.

When all three numeric headers are `unknown`, Atlas passes the request through normally.

This does not confirm a normal L2B evaluation for that scope.

### Security and testing

The current protocol defines no signature or fixed authentication header. Confirm any additional authentication during integration.

Validate the event type and required fields. Accept unknown fields. Avoid logging sensitive Webhook URL values.

Before launch, verify HTTPS access, exact HTTP `200` responses, idempotency, out-of-order handling, and API response-header monitoring.
