# label_changed

> Sent when a side of a position changes its label (TRADE, CAUTION, SKIP).

Source: https://firmtape.com/docs/developers/orders/events/label_changed

Sent when a side of a position changes its label (TRADE, CAUTION, SKIP).

`POST your https address` · `event: label_changed`

Plan ORDERS · Payload unversioned · Signed HMAC-SHA256 · Schema label_changed.schema.json

**This event serves:** [ORDERS: Opening read and label changes](https://firmtape.com/docs/orders#positions-alerts), [ORDERS: The POSITIONS webhook](https://firmtape.com/docs/orders#positions-webhook). **Plan:** [ORDERS](https://firmtape.com/features/orders). Free on every finished session. Today's ledger, live, and the live POSITIONS check are the ORDERS plan. Turn it on: The POSITIONS editor: Alert, WEBHOOK, an https address. On the Orders API and Team plans.

## When it is sent

- Checked every minute in session, from the alert's start minute (by default ten minutes before entry) to the close.
- Once per label per side per day.

## Payload

| Field | Meaning | Unit |
| --- | --- | --- |
| `event` ("label_changed") | `label_changed`. The event's name rides in `event` here, not `type` (see the target format). |  |
| `side` (put \| call) | `put` or `call`. |  |
| `label` (TRADE \| CAUTION \| SKIP) | The new label. |  |
| `position_id` (string) | The position, `p_...`. |  |
| `day` (string) | The ET trading day. |  |
| `t` (string) | The minute judged, `HH:MM` ET. |  |
| `check` (object) | The same check `GET /api/positions/check` returns for the position: its phase, the expected move left, and per side the distances, the large orders near the short, the levels around it, the conditions and the label with its reason. |  |

## Delivery

- **Answer**: Any 2xx within 3 seconds. Redirects are not followed.
- **Retries**: One try; no retry.
- **Switched off**: Never switched off; a failed send is logged.
- **Volume**: One event per label change per side per day, and one opening read.
- **Test**: `event: "test"`, from **Send test** in the editor
- **Log**: None yet.

## Request we send

```
POST https://your.server/firmtape
Content-Type: application/json
User-Agent: FirmTape-Positions/1
X-FirmTape-Timestamp: 1760034725
X-FirmTape-Signature: sha256=9b1e...
X-FirmTape-Signature-V0: sha256=9b1e... (deprecated)
```

## Body

```json
{
  "event": "label_changed",
  "position_id": "p_4f2a9c",
  "day": "2026-10-09",
  "t": "13:35",
  "side": "call",
  "label": "SKIP",
  "check": {
    "position_id": "p_4f2a9c"
  }
}
```

## Verify

```js
const crypto = require("node:crypto");
// rawBody: the request body as received, a Buffer or a string, before any JSON parsing
function verify(rawBody, headers, secret) {
  const t = headers["x-firmtape-timestamp"];
  const got = String(headers["x-firmtape-signature"] || "");
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
```

```python
import hmac, hashlib, time

def verify(raw_body: bytes, headers, secret: str) -> bool:
    t = headers.get("X-FirmTape-Timestamp", "")
    got = headers.get("X-FirmTape-Signature", "")
    if not t or abs(time.time() - int(t)) > 300:
        return False
    want = "sha256=" + hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(got, want)
```
